import { useState } from 'react'; import type { AdminRecord } from '../types'; import { ReferenceList } from './ReferenceList'; import { rewriteTosUrl } from '../lib/api'; import { showToast } from './Toast'; const STATUS_MAP: Record = { completed: { label: '已完成', color: 'var(--color-success)', bg: 'var(--color-success-bg)' }, failed: { label: '失败', color: 'var(--color-danger)', bg: 'var(--color-danger-bg)' }, processing: { label: '生成中', color: 'var(--color-info)', bg: 'var(--color-info-bg)' }, queued: { label: '排队中', color: 'var(--color-info)', bg: 'var(--color-info-bg)' }, }; const MODE_MAP: Record = { universal: '全能参考', keyframe: '首尾帧' }; interface Props { record: AdminRecord; onClose: () => void; showTeam?: boolean; showCost?: boolean; } export function RecordDetailModal({ record: r, onClose, showTeam, showCost }: Props) { const st = STATUS_MAP[r.status] || STATUS_MAP.processing; const [debugOpen, setDebugOpen] = useState(false); // 仅当转换后的 prompt 与原文不同(即 prompt 里有 @ 被转为「图片N」)才单独显示一栏 const hasConvertedPrompt = !!(r.api_prompt && r.api_prompt !== r.prompt); const handleCopyTaskId = () => { if (!r.ark_task_id) return; navigator.clipboard.writeText(r.ark_task_id).then(() => showToast('已复制')); }; const elapsed = (() => { if (!r.completed_at) return '-'; const ms = new Date(r.completed_at).getTime() - new Date(r.created_at).getTime(); if (ms < 0) return '-'; const sec = Math.round(ms / 1000); if (sec < 60) return `${sec}秒`; const min = Math.floor(sec / 60); const s = sec % 60; return `${min}分${s > 0 ? s + '秒' : ''}`; })(); const refs = r.reference_urls || []; return ( <>
e.stopPropagation()}> {/* Header */}
任务详情
{/* Body — 左:视频+参考素材 / 右:信息+提示词 */}
{/* ── 左侧:视频 + 参考素材 ── */}
{refs.length > 0 && ( <>
参考素材({refs.length})
)}
{/* ── 右侧:信息 + 提示词 ── */}
{/* Status */}
{st.label}
{/* Error */} {r.status === 'failed' && r.error_message && (
失败原因
{r.error_message}
{r.raw_error && r.raw_error !== r.error_message && (
原始错误:{r.raw_error}
)}
)} {/* Info Grid */}
基本信息
{r.ark_task_id && } {r.username && } {showTeam && r.team_name && } {showCost && } {r.seed != null && r.seed !== -1 && }
{/* Prompt */}
提示词
{r.prompt || '(无提示词)'}
{/* 调试信息(开发/客服参考)— 默认收起 */}
{debugOpen && (
{hasConvertedPrompt && ( <>
实际发给火山(@素材名被自动转换为「图片N/视频N/音频N」):
{r.api_prompt}
)} {r.ark_task_id && (
火山 Task ID: {r.ark_task_id}
)} {r.status === 'failed' && r.raw_error && ( <>
原始错误:
{r.raw_error}
)} {!hasConvertedPrompt && !r.ark_task_id && (r.status !== 'failed' || !r.raw_error) && (
(暂无调试信息)
)}
)}
); } /** * 左侧媒体区 — 根据任务状态决定显示什么: * - completed + result_url → 视频播放器(controls,不自动播放) * - completed - result_url → "视频已生成"占位 * - failed → RGB 故障字 "生成失败" + 错误原因摘要 + 斜纹底纹 * - processing / queued → 旋转 spinner + 文字 */ function MediaArea({ record: r }: { record: AdminRecord }) { return (
{r.status === 'completed' && r.result_url ? (
); } /** * RGB 故障字失败态 — "生成失败"主标题用 cyan/magenta text-shadow 偏移 * 模拟坏掉的 CRT 信号丢失;副标题等宽字体显示错误摘要。 */ function FailureGlitch({ errorMessage }: { errorMessage?: string }) { const msg = (errorMessage || 'Generation failed').slice(0, 80); return (