merge: 合并 master,保留拖拽上传样式和工具面板样式

This commit is contained in:
OmniAI Developer
2026-06-05 20:04:48 +08:00
23 changed files with 3331 additions and 152 deletions
+110 -2
View File
@@ -26,6 +26,8 @@ interface EvalResult {
totalScore: number;
grade: string;
dimensionScores: Record<string, number>;
subScores?: Record<string, Record<string, number>>;
evidence?: Record<string, string[]>;
summary: string;
issues: string[];
highlights: string[];
@@ -193,6 +195,60 @@ const SCORE_DIMENSIONS: ScoreDimension[] = [
{ key: "content", label: "内容深度", maxScore: 15, hint: "主题表达·情感共鸣·思想内核", detail: "核心设定将科技伦理与人性困境紧密结合,主题表达深刻有力。" },
];
const SUB_SCORE_LABELS: Record<string, string> = {
openingImpact: "开篇冲击",
suspenseChain: "悬念链",
sceneHook: "场内钩子",
structure: "结构完整",
rhythm: "节奏推进",
conflict: "冲突强度",
reversal: "反转效率",
motivation: "动机清晰",
arc: "人物弧光",
voice: "语言辨识",
relationship: "关系张力",
causality: "因果链",
worldRules: "世界规则",
foreshadowing: "伏笔回收",
continuity: "连续性",
sceneDetail: "场景细节",
shotPotential: "镜头潜力",
aigcFeasibility: "AIGC 可实现",
theme: "主题表达",
emotion: "情感共鸣",
marketFit: "市场匹配",
originality: "原创性",
};
function clampScore(score: unknown, maxScore: number): number {
const numeric = Number(score);
if (!Number.isFinite(numeric)) return 0;
return Math.max(0, Math.min(maxScore, numeric));
}
function getDimensionScore(result: EvalResult, dim: ScoreDimension): number {
const value = result.dimensionScores[dim.key] ?? (dim.key === "logic" ? result.dimensionScores.dialogue : undefined);
return clampScore(value, dim.maxScore);
}
function formatSubScoreLabel(key: string): string {
return SUB_SCORE_LABELS[key] ?? key.replace(/([A-Z])/g, " $1").trim();
}
function getDimensionSubScores(result: EvalResult, dim: ScoreDimension): Array<[string, number]> {
const scores = result.subScores?.[dim.key] ?? (dim.key === "logic" ? result.subScores?.dialogue : undefined);
if (!scores) return [];
return Object.entries(scores)
.map(([key, value]) => [key, clampScore(value, dim.maxScore)] as [string, number])
.filter(([, value]) => value > 0)
.slice(0, 5);
}
function getDimensionEvidence(result: EvalResult, dim: ScoreDimension): string[] {
const evidence = result.evidence?.[dim.key] ?? (dim.key === "logic" ? result.evidence?.dialogue : undefined);
return Array.isArray(evidence) ? evidence.map(String).map((item) => item.trim()).filter(Boolean).slice(0, 3) : [];
}
function formatReportMarkdown(result: EvalResult, script: string): string {
const lines: string[] = [];
lines.push(`# 剧本评测报告`);
@@ -204,9 +260,16 @@ function formatReportMarkdown(result: EvalResult, script: string): string {
lines.push("");
lines.push(`## 六维评分`);
for (const dim of SCORE_DIMENSIONS) {
const score = result.dimensionScores[dim.key] ?? 0;
const score = getDimensionScore(result, dim);
const pct = Math.round((score / dim.maxScore) * 100);
const subScores = getDimensionSubScores(result, dim);
const evidence = getDimensionEvidence(result, dim);
const nestedReportLines = [
...subScores.map(([key, value]) => ` - ${formatSubScoreLabel(key)}: ${value}`),
...evidence.map((item) => ` - 证据: ${item}`),
];
lines.push(`- **${dim.label}**: ${score}/${dim.maxScore} (${pct}%) — ${dim.hint}`);
lines.push(...nestedReportLines);
}
if (result.highlights.length > 0) {
lines.push("");
@@ -683,7 +746,7 @@ function ScriptTokensPage() {
</div>
<div className="script-eval-report__chart-grid">
{SCORE_DIMENSIONS.map((dim, dimIndex) => {
const score = result.dimensionScores[dim.key] ?? 0;
const score = getDimensionScore(result, dim);
const pct = Math.max(0, Math.min(1, score / dim.maxScore));
const lossPct = 1 - pct;
const isPerfect = score === dim.maxScore;
@@ -723,6 +786,51 @@ function ScriptTokensPage() {
</div>
</section>
<div className="script-eval-report__detail-grid">
{SCORE_DIMENSIONS.map((dim) => {
const score = getDimensionScore(result, dim);
const pct = Math.round((score / dim.maxScore) * 100);
const subScores = getDimensionSubScores(result, dim);
const evidence = getDimensionEvidence(result, dim);
return (
<section className="script-eval-report__detail-card" key={dim.key}>
<header className="script-eval-report__detail-head">
<div>
<span>{dim.label}</span>
<strong>{score}<small>/{dim.maxScore}</small></strong>
</div>
<em>{pct}%</em>
</header>
<p className="script-eval-report__detail-hint">{dim.hint}</p>
{subScores.length > 0 ? (
<div className="script-eval-report__subscore-list">
{subScores.map(([key, value]) => {
const subPct = Math.max(0, Math.min(100, Math.round((value / dim.maxScore) * 100)));
return (
<div className="script-eval-report__subscore-row" key={key}>
<span>{formatSubScoreLabel(key)}</span>
<div className="script-eval-report__subscore-bar" aria-hidden="true">
<i style={{ width: `${subPct}%` }} />
</div>
<b>{value}</b>
</div>
);
})}
</div>
) : (
<p className="script-eval-report__detail-empty"></p>
)}
{evidence.length > 0 ? (
<ul className="script-eval-report__evidence-list">
{evidence.map((item, index) => <li key={index}>{item}</li>)}
</ul>
) : null}
</section>
);
})}
</div>
<div className="script-eval-report__findings">
{result.highlights.length > 0 ? (
<section className="script-eval-report__finding-group is-highlight">