37 lines
3.8 KiB
TypeScript
37 lines
3.8 KiB
TypeScript
'use client'
|
||
|
||
import { useId, useRef, useState } from 'react'
|
||
import type { DetailJob } from '@/lib/detail-generator/types'
|
||
import { detailRequest } from './client'
|
||
import { detailButtonClass, detailFieldClass, detailPrimaryClass } from './DetailProductForm'
|
||
|
||
export default function DetailJobRecovery({ job, disabled = false, dirty = false, onUpdated }: { job: DetailJob; disabled?: boolean; dirty?: boolean; onUpdated: () => Promise<void> }) {
|
||
const [requestId, setRequestId] = useState(''), [pending, setPending] = useState(false), [error, setError] = useState('')
|
||
const busy = useRef(false), fieldId = useId()
|
||
const canContinue = job.recovery?.canContinue === true, canQuery = job.recovery?.canQuery === true
|
||
const blocked = disabled || dirty || pending
|
||
async function recover(action: 'query' | 'continue') {
|
||
if (busy.current || blocked) return
|
||
busy.current = true; setPending(true); setError('')
|
||
try {
|
||
await detailRequest(`/jobs/${encodeURIComponent(job.id)}/recover`, { method: 'POST', body: JSON.stringify({ action, ...(action === 'query' && job.recovery?.needsRequestId ? { requestId: requestId.trim() } : {}) }) })
|
||
await onUpdated()
|
||
} catch (cause) { setError(cause instanceof Error ? cause.message : '操作中断,请刷新状态;原请求不会重新提交') } finally { busy.current = false; setPending(false) }
|
||
}
|
||
return <div role="region" aria-label="模型任务恢复" aria-busy={pending} className="mb-5 min-w-0 rounded-xl border border-[#fed7aa] bg-[#fffbeb] p-4 text-sm text-[#9a3412]">
|
||
<p role="status" className="font-semibold">{canContinue ? job.status === 'failed' ? '卖点文案未完成' : '资料分析已取回' : '模型结果待确认'}</p>
|
||
<p className="mt-1 break-words">{canContinue ? job.status === 'failed' ? '原文案调用已结束。重试只调用文案模型,保留已完成的分析。' : '事实已保存。继续文案将调用文案模型,不重复分析。' : job.error || '原请求结果尚未确认,请勿重复生成。'}</p>
|
||
{canContinue && job.status === 'failed' && job.error && <p className="mt-1 break-words">{job.error}</p>}
|
||
{job.requestId && <p className="mt-2 break-all text-xs">请求编号:{job.requestId}</p>}
|
||
{canQuery && job.recovery?.needsRequestId && <label htmlFor={fieldId} className="mt-3 block space-y-1.5 text-sm"><span>原客户请求编号</span><input id={fieldId} className={`${detailFieldClass} !min-h-11 !text-base placeholder:!text-[#66758b]`} value={requestId} disabled={blocked} maxLength={36} placeholder="填写 98k 已核实的原请求编号" onChange={event => setRequestId(event.target.value)} /></label>}
|
||
{(canQuery || canContinue || error) && <div className="mt-3 flex flex-wrap gap-2">
|
||
{canQuery && <button type="button" className={`${detailButtonClass} !min-h-11`} disabled={blocked || !!job.recovery?.needsRequestId && !/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(requestId.trim())} onClick={() => void recover('query')}>{pending ? '取回中…' : '取回原结果'}</button>}
|
||
{canContinue && <button type="button" className={`${detailPrimaryClass} !min-h-11 !bg-[#0050cb] hover:!bg-[#003da3]`} disabled={blocked} onClick={() => void recover('continue')}>{pending ? '提交中…' : job.status === 'failed' ? '重试文案' : '继续文案'}</button>}
|
||
{error && <button type="button" className={`${detailButtonClass} !min-h-11`} disabled={blocked} onClick={() => void onUpdated()}>刷新状态</button>}
|
||
</div>}
|
||
{canQuery && <p className="mt-2 text-xs">只查询原响应,不重新调用模型;未留存仍需核查。</p>}
|
||
{dirty && (canQuery || canContinue) && <p className="mt-2 text-xs">请先撤销未保存修改,再恢复原版本的任务。</p>}
|
||
{error && <p role="alert" className="mt-2 break-words text-sm text-[#b42318]">{error}</p>}
|
||
</div>
|
||
}
|