gongxiangp2/components/detail-generator/DetailJobRecovery.tsx

37 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
}