import { CopyOutlined, DownOutlined, DownloadOutlined, FileTextOutlined, ReloadOutlined, TrophyOutlined, UploadOutlined } from "@ant-design/icons"; import { useEffect, useMemo, useRef, useState, type ChangeEvent, type KeyboardEvent } from "react"; import { evaluateScript } from "../../api/scriptEvalClient"; interface ScoreDimension { key: string; label: string; maxScore: number; weight: number; description: string; } interface EvalResult { totalScore: number; grade: string; dimensionScores: Record; summary: string; issues: string[]; highlights: string[]; suggestions: string[]; } const RADAR_CENTER = 100; const RADAR_RADIUS = 82; const RADAR_ANGLES = [-90, -30, 30, 90, 150, 210]; const scoreDimensions: ScoreDimension[] = [ { key: "hook", label: "钩子设计", maxScore: 20, weight: 0.2, description: "开篇吸引力、悬念设置、黄金三秒法则", }, { key: "character", label: "角色塑造", maxScore: 18, weight: 0.18, description: "主角弧光、角色辨识度、动机、配角质量", }, { key: "plot", label: "剧情结构", maxScore: 20, weight: 0.2, description: "起承转合、节奏把控、冲突设计", }, { key: "dialogue", label: "台词对白", maxScore: 15, weight: 0.15, description: "语言质感、角色差异化、潜台词", }, { key: "visual", label: "画面表现", maxScore: 15, weight: 0.15, description: "镜头感、空间层次、视觉冲击力", }, { key: "content", label: "内容深度", maxScore: 12, weight: 0.12, description: "主题表达、情感共鸣、社会/人性洞察", }, ]; function radarPoint(angle: number, radius: number) { const radians = (angle * Math.PI) / 180; return { x: RADAR_CENTER + radius * Math.cos(radians), y: RADAR_CENTER + radius * Math.sin(radians), }; } function makeRadarPoints(scores: Record | null) { if (!scores) return "100,100 100,100 100,100 100,100 100,100 100,100"; return scoreDimensions .map((dimension, index) => { const ratio = Math.max(0, Math.min(1, (scores[dimension.key] ?? 0) / dimension.maxScore)); const point = radarPoint(RADAR_ANGLES[index] ?? 0, RADAR_RADIUS * ratio); return `${point.x.toFixed(1)},${point.y.toFixed(1)}`; }) .join(" "); } function RadarPreview({ result }: { result: EvalResult | null }) { return (
); } function formatReportMarkdown(result: EvalResult, script: string): string { const lines: string[] = []; lines.push(`# 剧本评测报告`); lines.push(""); lines.push(`**综合评分**: ${result.totalScore} / 100 · ${result.grade} 级`); lines.push(""); lines.push(`## 概要`); lines.push(result.summary); lines.push(""); lines.push(`## 六维评分`); for (const dim of scoreDimensions) { const score = result.dimensionScores[dim.key] ?? 0; const pct = Math.round((score / dim.maxScore) * 100); lines.push(`- **${dim.label}**: ${score}/${dim.maxScore} (${pct}%) — ${dim.description}`); } if (result.highlights.length > 0) { lines.push(""); lines.push(`## 亮点`); result.highlights.forEach((h) => lines.push(`- ${h}`)); } if (result.issues.length > 0) { lines.push(""); lines.push(`## 扣分点`); result.issues.forEach((i) => lines.push(`- ${i}`)); } if (result.suggestions.length > 0) { lines.push(""); lines.push(`## 优化建议`); result.suggestions.forEach((s) => lines.push(`- ${s}`)); } lines.push(""); lines.push(`---`); lines.push(`*评测时间: ${new Date().toLocaleString("zh-CN")}*`); lines.push(""); lines.push(`
原始剧本 (${script.length} 字)`); lines.push(""); lines.push("```"); lines.push(script.slice(0, 2000) + (script.length > 2000 ? "\n...(已截断)" : "")); lines.push("```"); lines.push("
"); return lines.join("\n"); } function ScriptTokensPage() { const [script, setScript] = useState(""); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const [evalError, setEvalError] = useState(null); const [detailsExpanded, setDetailsExpanded] = useState(true); const [uploadedFile, setUploadedFile] = useState<{ name: string; size: number } | null>(null); const [copied, setCopied] = useState(false); const fileInputRef = useRef(null); useEffect(() => { console.log("[剧本评分] 页面已加载,ScriptTokensPage mounted"); }, []); const hasContent = Boolean(script.trim()); const lineNumbers = useMemo(() => { const count = Math.min(160, Math.max(10, script.split(/\r\n|\r|\n/).length)); return Array.from({ length: count }, (_, index) => index + 1); }, [script]); const handleUploadKeyDown = (event: KeyboardEvent) => { if (event.key !== "Enter" && event.key !== " ") return; event.preventDefault(); fileInputRef.current?.click(); }; const handleFileUpload = async (event: ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; const ext = file.name.slice(file.name.lastIndexOf(".")).toLowerCase(); const readable = [".txt", ".md"].includes(ext) || file.type === "text/plain" || file.type === "text/markdown"; setUploadedFile({ name: file.name, size: file.size }); if (readable) { setScript(await file.text()); } else { setScript( `[已上传文件:${file.name}]\n\n暂不支持解析 ${ext.toUpperCase()} 格式,请上传 TXT 或 MD 文件,或直接粘贴剧本文本后开始评测。`, ); } event.target.value = ""; }; const handleEvaluate = async () => { console.log("[剧本评测] 点击开始评测,hasContent:", hasContent, "script长度:", script.length); if (!hasContent) return; setLoading(true); setResult(null); setEvalError(null); try { console.log("[剧本评测] 开始评测,剧本长度:", script.length, "字符"); const aiResult = await evaluateScript(script); console.log("[剧本评测] 评测完成,结果:", { 总分: aiResult.totalScore, 等级: aiResult.grade, 维度得分: aiResult.dimensionScores, 摘要: aiResult.summary, 亮点: aiResult.highlights, 问题: aiResult.issues, 建议: aiResult.suggestions, }); setResult(aiResult); } catch (err) { console.error("[剧本评测] 评测失败:", err); setEvalError(err instanceof Error ? err.message : "评测服务暂时不可用,请稍后重试"); } setDetailsExpanded(true); setLoading(false); }; const handleReset = () => { setScript(""); setResult(null); setDetailsExpanded(true); setUploadedFile(null); setCopied(false); if (fileInputRef.current) fileInputRef.current.value = ""; }; const handleCopyReport = async () => { if (!result) return; const text = formatReportMarkdown(result, script); try { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { const ta = document.createElement("textarea"); ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0"; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); setCopied(true); setTimeout(() => setCopied(false), 2000); } }; const handleExportMarkdown = () => { if (!result) return; const md = formatReportMarkdown(result, script); const blob = new Blob([md], { type: "text/markdown;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `剧本评测_${result.grade}级_${result.totalScore}分_${new Date().toISOString().slice(0, 10)}.md`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; const scoreStatus = loading ? "评测中" : result ? "评测完成" : "待生成评分"; const scoreHint = result?.summary ?? (hasContent ? "点击「开始评测」生成六维雷达评分和优化路径。" : "粘贴完整剧本后,点击「开始评测」生成六维雷达评分和优化路径。"); return (
fileInputRef.current?.click()} onKeyDown={handleUploadKeyDown} >
{uploadedFile ? uploadedFile.name : "粘贴文本或上传文档"}
{uploadedFile ? `${(uploadedFile.size / 1024).toFixed(1)}KB,已载入文件信息` : "建议包含场景、角色、动作和台词"}