Add Retry for failed results
A failed plan item gets a Retry button on its card and in the preview. It asks the engine for a fresh quote for that item and starts it again, while the engine still allows another attempt (three per item). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
import { request } from '../api/client'
|
||||
import { t } from '../i18n'
|
||||
import { kindLabel, type ContentKind, type Rule } from './parse'
|
||||
import { expressAgents, PLAN_ACTIVE, productionStatus, type PlanItem, type Schedule } from './schedules'
|
||||
import { expressAgents, MAX_ATTEMPTS, PLAN_ACTIVE, productionStatus, retryItem, type PlanItem, type Schedule } from './schedules'
|
||||
|
||||
const AGENTS = '/organizations/current/agents'
|
||||
|
||||
@@ -39,6 +39,8 @@ export interface Material {
|
||||
sources: string[]
|
||||
createdAt: string
|
||||
rule: Rule
|
||||
/** The plan item to produce again, when the engine allows another attempt. */
|
||||
retry?: { planId: number; itemId: number }
|
||||
video?: { url: string | null; progress: number; credits: number | null }
|
||||
}
|
||||
|
||||
@@ -93,9 +95,9 @@ async function materialsOf(schedule: Schedule): Promise<Material[]> {
|
||||
request<RawMaterial[]>(`${AGENTS}/${agent.id}/material-drafts`),
|
||||
productionStatus(agent.id).catch(() => null),
|
||||
])
|
||||
const items = new Map<number, PlanItem>()
|
||||
const items = new Map<number, PlanItem & { planId: number }>()
|
||||
for (const plan of status?.plans ?? []) {
|
||||
for (const item of plan.items ?? []) if (item.run_id) items.set(item.run_id, item)
|
||||
for (const item of plan.items ?? []) if (item.run_id) items.set(item.run_id, { ...item, planId: plan.id })
|
||||
}
|
||||
// One run makes one result; its drafts are its parts (one, or the lesson + quiz of a SCORM module).
|
||||
const byRun = new Map<number, RawMaterial[]>()
|
||||
@@ -104,11 +106,13 @@ async function materialsOf(schedule: Schedule): Promise<Material[]> {
|
||||
// The lesson comes first: it carries the title, sources and Compose set of the whole module.
|
||||
group.sort((a, b) => Number(b.output_kind === 'summary') - Number(a.output_kind === 'summary'))
|
||||
const [d] = group
|
||||
const item = items.get(d.run_id)
|
||||
const parts = group.map(g => ({ id: g.id, revision: g.revision, kind: g.output_kind, status: g.status }))
|
||||
return {
|
||||
id: d.id, agentId: agent.id, kind: kind === 'audio' || kind === 'scorm' ? kind : d.output_kind,
|
||||
status: withPlanItem(combined(parts), items.get(d.run_id)?.status), revision: d.revision, parts,
|
||||
planTitle: items.get(d.run_id)?.title ?? null,
|
||||
status: withPlanItem(combined(parts), item?.status), revision: d.revision, parts,
|
||||
planTitle: item?.title ?? null,
|
||||
retry: item?.status === 'failed' && item.attempt_count < MAX_ATTEMPTS ? { planId: item.planId, itemId: item.id } : undefined,
|
||||
composeSetId: d.compose_set_id, sources: d.sources.map(s => s.title), createdAt: d.created_at, rule,
|
||||
}
|
||||
})
|
||||
@@ -192,6 +196,8 @@ export async function materialContent(m: Material): Promise<{ content: MaterialC
|
||||
export const exportPptx = (deckId: number) => request<Blob>(`/slide-deck/${deckId}/export`, { method: 'POST', as: 'blob' })
|
||||
|
||||
// Every part waiting for review gets the same decision, so a SCORM module is approved as one.
|
||||
export const retryMaterial = (m: Material) => retryItem(m.agentId, m.retry!.planId, m.retry!.itemId)
|
||||
|
||||
export async function decideMaterial(m: Material, decision: 'accept' | 'reject') {
|
||||
for (const p of m.parts.filter(p => p.status === 'ready')) {
|
||||
if (m.kind === 'video') await request(`${AGENTS}/${m.agentId}/video-drafts/${p.id}/review`, { method: 'POST', json: { decision } })
|
||||
|
||||
Reference in New Issue
Block a user