Files
omniai-web/src/api/scriptEvalClient.ts
T

109 lines
4.1 KiB
TypeScript
Raw Normal View History

2026-06-02 12:38:01 +08:00
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) : [],
};
}