Retry failed videos with a new run and explain documents that fail after their build

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iqbal
2026-10-27 21:48:00 +07:00
parent 8a02f68589
commit 9f9fc93dfe
4 changed files with 19 additions and 7 deletions

View File

@@ -5,7 +5,7 @@
import { request } from '../api/client'
import { t } from '../i18n'
import { kindLabel, type ContentKind, type Rule } from './parse'
import { expressAgents, MAX_ATTEMPTS, PLAN_ACTIVE, productionStatus, retryItem, type PlanItem, type Schedule } from './schedules'
import { expressAgents, MAX_ATTEMPTS, PLAN_ACTIVE, productionStatus, retryItem, runNow, type PlanItem, type Schedule } from './schedules'
const AGENTS = '/organizations/current/agents'
@@ -39,8 +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 }
/** How to produce it again, when the engine allows another attempt: the plan item, or (video) a new run. */
retry?: { planId: number; itemId: number } | { run: true }
video?: { url: string | null; progress: number; credits: number | null }
}
@@ -147,11 +147,15 @@ function videoStatus(s: string): MaterialStatus {
return 'generating'
}
async function videosOf({ agent, rule }: Schedule): Promise<Material[]> {
async function videosOf({ agent, rule, active }: Schedule): Promise<Material[]> {
const drafts = await request<RawVideo[]>(`${AGENTS}/${agent.id}/video-drafts`)
// A video draft cannot be retried itself; a new run of its schedule makes it again. Offered on the
// newest draft only, so older failures don't each start another (paid) run.
const newest = Math.max(...drafts.map(d => d.id))
return drafts.map(d => {
const status = videoStatus(d.status)
return {
retry: status === 'failed' && active && d.id === newest ? { run: true } as const : undefined,
id: d.id, agentId: agent.id, kind: 'video', status, revision: 0,
parts: [{ id: d.id, revision: 0, kind: 'video', status }],
planTitle: d.title, composeSetId: null, sources: d.sources.map(s => s.title), createdAt: d.created_at, rule,
@@ -195,9 +199,10 @@ export async function materialContent(m: Material): Promise<{ content: MaterialC
// The engine renders slide decks to PPTX itself; other kinds have no file export there.
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 const retryMaterial = ({ agentId, retry }: Material) =>
retry && 'run' in retry ? runNow(agentId) : retryItem(agentId, retry!.planId, retry!.itemId)
// Every part waiting for review gets the same decision, so a SCORM module is approved as one.
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 } })