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:
Iqbal
2026-10-23 11:11:23 +07:00
parent 3df72c68ef
commit e00ffe9465
5 changed files with 57 additions and 7 deletions

View File

@@ -8,7 +8,7 @@ import { Empty, PageHead, SkeletonCards, SkeletonLines, SkeletonRows } from '../
import { toast } from '../components/Toast'
import { describeRule, kindLabel, type ContentKind } from '../express/parse'
import {
decideMaterial, download, exportPptx, fileName, isWorking, printPdf, listOf, listResults, materialContent, materialKey, questionsOf, reviewSource, slidesOf, textOf,
decideMaterial, download, exportPptx, fileName, isWorking, printPdf, listOf, listResults, materialContent, materialKey, questionsOf, retryMaterial, reviewSource, slidesOf, textOf,
titleOf, toMarkdown, type FoundSource, type Material, type MaterialContent, type MaterialStatus,
} from '../express/results'
import { MASTERY, scormPackage } from '../express/scorm'
@@ -132,13 +132,34 @@ function ContentCard({ material: m, onOpen }: { material: Material; onOpen: () =
<span title={describeRule(m.rule)}>{describeRule(m.rule)}</span>
{working
? <span>{m.video ? t('res.videoProgress', { n: m.video.progress }) : t('res.working')}</span>
: <button className="text-btn" onClick={onOpen}>{t('res.open')} <Icon name="arrow-right" /></button>}
: <div className="content-card-actions">
{m.retry && <RetryButton material={m} className="text-btn" />}
<button className="text-btn" onClick={onOpen}>{t('res.open')} <Icon name="arrow-right" /></button>
</div>}
</div>
</div>
</article>
)
}
// A failed plan item is produced again with a fresh quote; the result then shows as preparing.
function RetryButton({ material: m, className }: { material: Material; className: string }) {
const queryClient = useQueryClient()
const retry = useMutation({
mutationFn: () => retryMaterial(m),
onSuccess: () => {
toast(t('res.retried'))
queryClient.invalidateQueries({ queryKey: ['results'] })
},
onError: e => toast(e.message, { error: true }),
})
return (
<button className={className} disabled={retry.isPending} onClick={() => retry.mutate()}>
{retry.isPending ? t('res.retrying') : t('res.retry')}
</button>
)
}
function Preview({ material: m, onClose }: { material: Material; onClose: () => void }) {
const ref = useRef<HTMLDialogElement>(null)
const queryClient = useQueryClient()
@@ -190,6 +211,7 @@ function Preview({ material: m, onClose }: { material: Material; onClose: () =>
<div className="modal-foot">
{decide.error ? <small className="error">{decide.error.message}</small> : <small>{t('res.footNote')}</small>}
{m.composeSetId && <a className="btn" href={`${STUDIO_URL}/compose/${m.composeSetId}`} target="_blank" rel="noreferrer">{t('res.openStudio')}</a>}
{m.retry && <RetryButton material={m} className="btn primary" />}
{content.data?.content && <DownloadMenu material={m} content={content.data.content} outputId={content.data.outputId} />}
{m.status === 'ready' && (
<>