import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Alert, Button, Card, Collapse, Descriptions, Empty, Space, Spin, Steps, Table, Tag, Tooltip, Typography, message, } from 'antd'; import { ArrowLeftOutlined, FileImageOutlined, PlayCircleOutlined, VideoCameraOutlined, } from '@ant-design/icons'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { getAdminHotOpeningTaskDetail, getAdminShotProjectDetail } from '../api'; import type { ReplicationProjectDetailOut, ReplicationStepOut } from '../types'; import { formatDate } from '../utils/formatDate'; import JsonCollapse, { JsonBlock } from './adminReplication/components/JsonCollapse'; import MediaPreview from './adminReplication/components/MediaPreview'; import StatusTag, { getModuleLabel, getStatusLabel, getStepCodeLabel } from './adminReplication/components/StatusTag'; import VideoPromptSchemaViewer from './adminReplication/components/VideoPromptSchemaViewer'; type ReplicationModuleType = 'hot_opening_replicate' | 'shot_replicate'; interface AdminReplicationProjectDetailProps { moduleType?: ReplicationModuleType; } const V1_STEP_ORDER = [ 'material_input', 'image_prompt_optimize', 'image_generate', 'video_prompt_optimize', 'video_generate', ]; const V2_STEP_ORDER = [ 'material_input', 'video_prompt_optimize', 'video_generate', ]; const STEP_DESCRIPTIONS: Record = { material_input: '参考素材、项目名称和核心内容点', image_prompt_optimize: '图片 AI 提词优化结果', image_generate: '图片引擎、生成参数和结果图', video_prompt_optimize: '视频 JSON schema、动作流程和最终提词', video_generate: '视频引擎、生成参数、封面和结果视频', }; const safeDate = (value?: string | null): string => (value ? formatDate(value) : '-'); const shortId = (value?: string | null): string => { if (!value) return '-'; return value.length > 18 ? `${value.slice(0, 10)}...${value.slice(-4)}` : value; }; const EmptyText: React.FC<{ text?: string }> = ({ text = '-' }) => ( {text} ); const StepRawJson: React.FC<{ step?: ReplicationStepOut | null }> = ({ step }) => { if (!step) return null; return ; }; const StepHeader: React.FC<{ index: number; stepCode: string; step?: ReplicationStepOut | null; current?: boolean }> = ({ index, stepCode, step, current }) => ( {`第${index}步:${getStepCodeLabel(stepCode)}`} {current ? 当前步骤 : null} {STEP_DESCRIPTIONS[stepCode] || stepCode} {step?.updatedAt ? 更新时间:{safeDate(step.updatedAt)} : null} {step?.chatTaskId ? Chat:{shortId(step.chatTaskId)} : null} ); const buildDefaultActiveKeys = (detail: ReplicationProjectDetailOut | null, stepsByCode: Record): string[] => { if (!detail) return []; const keys = new Set(); keys.add('material_input'); if (detail.currentStepCode) keys.add(detail.currentStepCode); Object.values(stepsByCode).forEach(step => { if (step.status === 'failed' || step.errorMessage) keys.add(step.stepCode); }); if (detail.errorMessage) keys.add(detail.currentStepCode || 'material_input'); return Array.from(keys); }; const renderErrorAlert = (messageText?: string | null, title = '错误信息') => { if (!messageText) return null; return ; }; const renderPromptText = (value?: string | null, empty = '暂无提词') => { if (!value) return ; return {value}; }; const AdminReplicationProjectDetail: React.FC = ({ moduleType = 'shot_replicate' }) => { const { projectId } = useParams<{ projectId: string }>(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const flowVersion: 'v1' | 'v2' = searchParams.get('flow_version') === 'v2' ? 'v2' : 'v1'; const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(false); const load = useCallback(async () => { if (!projectId) return; setLoading(true); try { const res = moduleType === 'hot_opening_replicate' ? await getAdminHotOpeningTaskDetail(projectId, flowVersion) : await getAdminShotProjectDetail(projectId, flowVersion); setDetail(res); } catch (e: any) { message.error(e?.message || '加载复刻项目详情失败'); } finally { setLoading(false); } }, [flowVersion, moduleType, projectId]); useEffect(() => { load(); }, [load]); const stepsByCode = useMemo(() => { const map: Record = {}; (detail?.steps || []).forEach(step => { map[step.stepCode] = step; }); return map; }, [detail]); const moduleValue = detail?.module || moduleType; const moduleName = getModuleLabel(moduleValue); const isV2 = detail?.flowVersion === 'v2'; const effectiveStepOrder = isV2 ? V2_STEP_ORDER : V1_STEP_ORDER; const stepItems = useMemo(() => effectiveStepOrder.map(code => { const step = stepsByCode[code]; let status: 'wait' | 'process' | 'finish' | 'error' = 'wait'; if (step?.status === 'completed') status = 'finish'; else if (step?.status === 'processing') status = 'process'; else if (step?.status === 'failed') status = 'error'; return { title: getStepCodeLabel(code), description: step ? : '未创建', status, }; }), [effectiveStepOrder, stepsByCode]); const defaultActiveKeys = useMemo(() => buildDefaultActiveKeys(detail, stepsByCode), [detail, stepsByCode]); if (loading && !detail) { return
; } if (!detail) { return ( ); } const materialStep = stepsByCode.material_input; const imagePromptStep = stepsByCode.image_prompt_optimize; const imageGenerateStep = stepsByCode.image_generate; const videoPromptStep = stepsByCode.video_prompt_optimize; const videoGenerateStep = stepsByCode.video_generate; const imageResultUrl = detail.imageGeneration?.resultImageUrl || detail.finalImageUrl; const videoCoverUrl = detail.videoGeneration?.resultVideoCoverUrl || detail.finalVideoCoverUrl; const videoResultUrl = detail.videoGeneration?.resultVideoUrl || detail.finalVideoUrl; const collapseItems = [ { key: 'material_input', label: , children: ( {detail.material?.materialStepId || '-'} {detail.material?.sourceProjectName || '-'} {detail.material?.targetProjectName || '-'} {detail.material?.coreContentPoint || '-'}
素材视频}> 素材图片}>
{renderErrorAlert(materialStep?.errorMessage)}
), }, { key: 'image_prompt_optimize', label: , children: ( {detail.imageGeneration?.promptStepId || '-'} {renderPromptText(detail.imageGeneration?.prompt, '暂无图片提示词')} {renderErrorAlert(imagePromptStep?.errorMessage || detail.imageGeneration?.errorMessage)} ), }, { key: 'image_generate', label: , children: ( {detail.imageGeneration?.generateStepId || '-'} {detail.imageGeneration?.chatTaskId || '-'} {detail.imageGeneration?.engineId || '-'} {detail.imageGeneration?.engineName || '-'} {renderErrorAlert(detail.imageGeneration?.errorMessage || imageGenerateStep?.errorMessage)} 图片结果}> ), }, { key: 'video_prompt_optimize', label: , children: ( {detail.videoGeneration?.promptStepId || '-'} {renderErrorAlert(videoPromptStep?.errorMessage || detail.videoGeneration?.errorMessage)} ), }, { key: 'video_generate', label: , children: ( {detail.videoGeneration?.generateStepId || '-'} {detail.videoGeneration?.chatTaskId || '-'} {detail.videoGeneration?.engineId || '-'} {detail.videoGeneration?.engineName || '-'} {renderErrorAlert(detail.videoGeneration?.errorMessage || videoGenerateStep?.errorMessage)}
视频封面}> 最终视频}>
), }, ]; const visibleCollapseItems = isV2 ? collapseItems.filter(item => !['image_prompt_optimize', 'image_generate'].includes(String(item.key))) : collapseItems; return (
{moduleName}项目详情 {detail.module && detail.module !== moduleType ? 接口模块:{getModuleLabel(detail.module)} : null} {detail.id} {detail.userId || '-'} {detail.userName || '-'} {detail.title || '-'} {getStepCodeLabel(detail.currentStepCode)} {String(detail.flowVersion || 'v1').toUpperCase()} {detail.stepCount || effectiveStepOrder.length} {safeDate(detail.createdAt)} {safeDate(detail.updatedAt)} {safeDate(detail.completedAt)} {detail.errorMessage ? : null}
{!isV2 ? ( ) : null}
{getStepCodeLabel(v)} }, { title: '状态', dataIndex: 'status', width: 120, render: (v: string) => }, { title: '版本', dataIndex: 'version', width: 70 }, { title: '当前有效', dataIndex: 'isCurrent', width: 90, render: (v: boolean) => v ? : }, { title: 'Chat任务', dataIndex: 'chatTaskId', width: 160, render: (v: string) => {shortId(v)} }, { title: '创建时间', dataIndex: 'createdAt', width: 170, render: safeDate }, { title: '完成时间', dataIndex: 'completedAt', width: 170, render: safeDate }, { title: '错误', dataIndex: 'errorMessage', ellipsis: true, render: (v: string) => v || '-' }, ]} /> ), }, { key: 'raw-detail', label: '完整详情原始 JSON', children: , }, ]} />
); }; export default AdminReplicationProjectDetail;