109 lines
4.1 KiB
TypeScript
109 lines
4.1 KiB
TypeScript
|
|
import { buildApiUrl, buildAuthHeaders } from "./serverConnection";
|
||
|
|
|
||
|
|
export interface ScriptEvalResult {
|
||
|
|
totalScore: number;
|
||
|
|
grade: string;
|
||
|
|
dimensionScores: Record<string, number>;
|
||
|
|
summary: string;
|
||
|
|
issues: string[];
|
||
|
|
highlights: string[];
|
||
|
|
suggestions: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
const EVAL_SYSTEM_PROMPT = `你是一位专业的剧本评测专家。请对用户提供的剧本进行六维评分分析,并以严格的 JSON 格式返回结果。
|
||
|
|
|
||
|
|
六个评分维度:
|
||
|
|
1. hook(钩子设计,满分20):开篇吸引力、悬念设置、黄金三秒法则
|
||
|
|
2. character(角色塑造,满分15):人物立体度、动机合理性、弧光设计
|
||
|
|
3. plot(剧情结构,满分20):起承转合、节奏把控、冲突设计
|
||
|
|
4. dialogue(台词对白,满分15):语言质感、角色差异化、潜台词
|
||
|
|
5. visual(画面表现,满分15):镜头感、空间层次、视觉冲击力
|
||
|
|
6. content(内容深度,满分15):主题表达、情感共鸣、思想内核
|
||
|
|
|
||
|
|
请严格按以下 JSON 格式返回(不要包含任何其他文字):
|
||
|
|
{
|
||
|
|
"dimensionScores": { "hook": 数字, "character": 数字, "plot": 数字, "dialogue": 数字, "visual": 数字, "content": 数字 },
|
||
|
|
"summary": "一句话总结评价",
|
||
|
|
"issues": ["问题1", "问题2", ...],
|
||
|
|
"highlights": ["亮点1", "亮点2", ...],
|
||
|
|
"suggestions": ["建议1", "建议2", ...]
|
||
|
|
}`;
|
||
|
|
|
||
|
|
const DIMENSION_WEIGHTS: Record<string, { maxScore: number; weight: number }> = {
|
||
|
|
hook: { maxScore: 20, weight: 0.2 },
|
||
|
|
character: { maxScore: 15, weight: 0.15 },
|
||
|
|
plot: { maxScore: 20, weight: 0.2 },
|
||
|
|
dialogue: { maxScore: 15, weight: 0.15 },
|
||
|
|
visual: { maxScore: 15, weight: 0.15 },
|
||
|
|
content: { maxScore: 15, weight: 0.15 },
|
||
|
|
};
|
||
|
|
|
||
|
|
function computeTotalAndGrade(scores: Record<string, number>): { totalScore: number; grade: string } {
|
||
|
|
const totalScore = Math.round(
|
||
|
|
Object.entries(DIMENSION_WEIGHTS).reduce((sum, [key, dim]) => {
|
||
|
|
const score = Math.max(0, Math.min(dim.maxScore, scores[key] ?? 0));
|
||
|
|
return sum + (score / dim.maxScore) * 100 * dim.weight;
|
||
|
|
}, 0),
|
||
|
|
);
|
||
|
|
const grade = totalScore >= 90 ? "S" : totalScore >= 80 ? "A" : totalScore >= 70 ? "B" : totalScore >= 60 ? "C" : "D";
|
||
|
|
return { totalScore, grade };
|
||
|
|
}
|
||
|
|
|
||
|
|
function extractJson(text: string): unknown {
|
||
|
|
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
||
|
|
const raw = fenced ? fenced[1].trim() : text.trim();
|
||
|
|
return JSON.parse(raw);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function evaluateScript(script: string, signal?: AbortSignal): Promise<ScriptEvalResult> {
|
||
|
|
const res = await fetch(buildApiUrl("ai/chat"), {
|
||
|
|
method: "POST",
|
||
|
|
headers: buildAuthHeaders(),
|
||
|
|
body: JSON.stringify({
|
||
|
|
model: "qwen3.7-max",
|
||
|
|
messages: [
|
||
|
|
{ role: "system", content: EVAL_SYSTEM_PROMPT },
|
||
|
|
{ role: "user", content: `请评测以下剧本:\n\n${script.slice(0, 8000)}` },
|
||
|
|
],
|
||
|
|
stream: false,
|
||
|
|
temperature: 0.3,
|
||
|
|
}),
|
||
|
|
signal,
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!res.ok) {
|
||
|
|
throw new Error(`评测请求失败 (${res.status})`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const payload = await res.json();
|
||
|
|
const content: string = payload?.choices?.[0]?.message?.content
|
||
|
|
?? payload?.result?.content
|
||
|
|
?? payload?.content
|
||
|
|
?? payload?.text
|
||
|
|
?? (typeof payload === "string" ? payload : "");
|
||
|
|
|
||
|
|
if (!content) throw new Error("模型未返回有效内容");
|
||
|
|
|
||
|
|
const parsed = extractJson(content) as Record<string, unknown>;
|
||
|
|
const dimensionScores: Record<string, number> = {};
|
||
|
|
const rawScores = parsed.dimensionScores as Record<string, number> | undefined;
|
||
|
|
if (!rawScores || typeof rawScores !== "object") throw new Error("评分格式异常");
|
||
|
|
|
||
|
|
for (const key of Object.keys(DIMENSION_WEIGHTS)) {
|
||
|
|
const val = Number(rawScores[key] ?? 0);
|
||
|
|
dimensionScores[key] = Math.max(0, Math.min(DIMENSION_WEIGHTS[key].maxScore, val));
|
||
|
|
}
|
||
|
|
|
||
|
|
const { totalScore, grade } = computeTotalAndGrade(dimensionScores);
|
||
|
|
|
||
|
|
return {
|
||
|
|
totalScore,
|
||
|
|
grade,
|
||
|
|
dimensionScores,
|
||
|
|
summary: String(parsed.summary || ""),
|
||
|
|
issues: Array.isArray(parsed.issues) ? parsed.issues.map(String) : [],
|
||
|
|
highlights: Array.isArray(parsed.highlights) ? parsed.highlights.map(String) : [],
|
||
|
|
suggestions: Array.isArray(parsed.suggestions) ? parsed.suggestions.map(String) : [],
|
||
|
|
};
|
||
|
|
}
|