// 自由创作·任务卡:生成中(shimmer+平滑进度) / 失败(中文错误+重试) / 完成(悬停播放+操作)。 import { useRef } from "react"; import { Download, Heart, RotateCcw, Trash2 } from "lucide-react"; import type { FreeVideoTask } from "../../types"; import { MODE_LABELS, STATUS_LABELS, isInFlight, modelLabel } from "./constants"; import { isPublicGenerationError, presentGenerationError } from "../../generation-error"; function ratioStyle(ratio: string) { const [w, h] = ratio.split(":").map(Number); return { aspectRatio: w && h ? `${w} / ${h}` : "16 / 9" }; } function ratioCopy(ratio: string) { const [w, h] = ratio.split(":").map(Number); if (!w || !h) return ratio; if (w === h) return `方形 ${ratio}`; return `${w > h ? "横屏" : "竖屏"} ${ratio}`; } function statusTag(status: string) { if (status === "succeeded") return { cls: "green", label: "已完成" }; if (status === "failed" || status === "cancelled") return { cls: "danger", label: status === "cancelled" ? "已取消" : "失败" }; return { cls: "blue", label: STATUS_LABELS[status] || "生成中" }; } export function GenerationCard({ task, progress, onOpen, onRetry, onToggleFavorite, onDelete, onDownload }: { task: FreeVideoTask; progress: number; onOpen: () => void; onRetry: () => void; onToggleFavorite: () => void; onDelete: () => void; onDownload: () => void; }) { const videoRef = useRef(null); const inFlight = isInFlight(task.status); const failed = task.status === "failed" || task.status === "cancelled"; const done = task.status === "succeeded" && !!task.video_url; const tag = statusTag(task.status); const errorText = isPublicGenerationError(task.error) ? (() => { const presentation = presentGenerationError(task.error); return `${presentation.title}:${presentation.description}`; })() : task.error_message || "视频生成失败,请重试"; return (
{ if (event.key === "Enter") onOpen(); } : undefined} onMouseEnter={() => { if (done) void videoRef.current?.play().catch(() => undefined); }} onMouseLeave={() => { videoRef.current?.pause(); if (videoRef.current) videoRef.current.currentTime = 0; }} > {inFlight && (
{STATUS_LABELS[task.status] || "生成中"} · {Math.round(Math.min(progress, 95))}%
)} {failed && (
失败

{errorText}

)} {done && ( <>

{task.prompt || "未命名视频"}

{MODE_LABELS[task.mode] || task.mode} · {task.duration} 秒 · {ratioCopy(task.aspect_ratio)}

{tag.label} {modelLabel(task.model)}
{inFlight && (
)}
); }