feat: async task pipeline for media and llm workflows

这个提交包含在:
cryptocommuniums-afk
2026-03-15 00:12:26 +08:00
父节点 1cc863e60e
当前提交 20e183d2da
修改 36 个文件,包含 1961 行新增339 行删除

255
server/prompts.ts 普通文件
查看文件

@@ -0,0 +1,255 @@
type RecentScore = {
score: number | null;
issues: unknown;
exerciseType: string | null;
shotCount: number | null;
strokeConsistency: number | null;
footworkScore: number | null;
};
type RecentAnalysis = {
score: number | null;
issues: unknown;
corrections: unknown;
shotCount: number | null;
strokeConsistency: number | null;
footworkScore: number | null;
fluidityScore: number | null;
};
function skillLevelLabel(skillLevel: "beginner" | "intermediate" | "advanced") {
switch (skillLevel) {
case "intermediate":
return "中级";
case "advanced":
return "高级";
default:
return "初级";
}
}
export function buildTrainingPlanPrompt(input: {
skillLevel: "beginner" | "intermediate" | "advanced";
durationDays: number;
focusAreas?: string[];
recentScores: RecentScore[];
}) {
return [
`你是一位专业网球教练。请为一位${skillLevelLabel(input.skillLevel)}水平的网球学员生成 ${input.durationDays} 天训练计划。`,
"训练条件与要求:",
"- 训练以个人可执行为主,可使用球拍、弹力带、标志盘、墙面等常见器材。",
"- 每天训练 30-60 分钟,结构要清晰:热身、专项、脚步、力量/稳定、放松。",
"- 输出内容要适合直接执行,不写空话,不写营销语,不写额外说明。",
input.focusAreas?.length ? `- 重点关注:${input.focusAreas.join("、")}` : "- 如未指定重点,请自动平衡技术、脚步和体能。",
input.recentScores.length > 0
? `- 用户最近分析摘要:${JSON.stringify(input.recentScores)}`
: "- 暂无历史分析数据,请基于该水平的常见薄弱项设计。",
"每个训练项都要给出目标、动作描述、组次/次数、关键提示,避免重复堆砌。",
].join("\n");
}
export function buildAdjustedTrainingPlanPrompt(input: {
currentExercises: unknown;
recentAnalyses: RecentAnalysis[];
}) {
return [
"你是一位专业网球教练,需要根据最近训练分析结果调整现有训练计划。",
`当前计划:${JSON.stringify(input.currentExercises)}`,
`最近分析结果:${JSON.stringify(input.recentAnalyses)}`,
"请优先修复最近最频繁、最影响击球质量的问题。",
"要求:",
"- 保留原计划中仍然有效的训练项,不要全部推倒重来。",
"- 增加动作纠正、脚步节奏、稳定性和专项力量训练。",
"- adjustmentNotes 需要说明为什么这样调整,以及下一阶段重点。",
"- 输出仅返回结构化 JSON。",
].join("\n");
}
export function buildTextCorrectionPrompt(input: {
exerciseType: string;
poseMetrics: unknown;
detectedIssues: unknown;
}) {
return [
"你是一位网球技术教练与动作纠正分析师。",
`动作类型:${input.exerciseType}`,
`姿态指标:${JSON.stringify(input.poseMetrics)}`,
`已检测问题:${JSON.stringify(input.detectedIssues)}`,
"请用中文输出专业、直接、可执行的纠正建议,使用 Markdown。",
"内容结构必须包括:",
"1. 动作概览",
"2. 最高优先级的 3 个修正点",
"3. 每个修正点对应的练习方法、感受提示、完成标准",
"4. 下一次拍摄或训练时的注意事项",
].join("\n");
}
export const multimodalCorrectionSchema = {
type: "object",
properties: {
summary: { type: "string" },
overallScore: { type: "number" },
confidence: { type: "number" },
phaseFindings: {
type: "array",
items: {
type: "object",
properties: {
phase: { type: "string" },
score: { type: "number" },
observation: { type: "string" },
impact: { type: "string" },
},
required: ["phase", "score", "observation", "impact"],
additionalProperties: false,
},
},
bodyPartFindings: {
type: "array",
items: {
type: "object",
properties: {
bodyPart: { type: "string" },
issue: { type: "string" },
recommendation: { type: "string" },
},
required: ["bodyPart", "issue", "recommendation"],
additionalProperties: false,
},
},
priorityFixes: {
type: "array",
items: {
type: "object",
properties: {
title: { type: "string" },
why: { type: "string" },
howToPractice: { type: "string" },
successMetric: { type: "string" },
},
required: ["title", "why", "howToPractice", "successMetric"],
additionalProperties: false,
},
},
drills: {
type: "array",
items: {
type: "object",
properties: {
name: { type: "string" },
purpose: { type: "string" },
durationMinutes: { type: "number" },
steps: {
type: "array",
items: { type: "string" },
},
coachingCues: {
type: "array",
items: { type: "string" },
},
},
required: ["name", "purpose", "durationMinutes", "steps", "coachingCues"],
additionalProperties: false,
},
},
safetyRisks: {
type: "array",
items: { type: "string" },
},
nextSessionFocus: {
type: "array",
items: { type: "string" },
},
recommendedCaptureTips: {
type: "array",
items: { type: "string" },
},
},
required: [
"summary",
"overallScore",
"confidence",
"phaseFindings",
"bodyPartFindings",
"priorityFixes",
"drills",
"safetyRisks",
"nextSessionFocus",
"recommendedCaptureTips",
],
additionalProperties: false,
};
export function buildMultimodalCorrectionPrompt(input: {
exerciseType: string;
poseMetrics: unknown;
detectedIssues: unknown;
imageCount: number;
}) {
return [
"你是一位专业网球技术教练,正在审阅学员的动作截图。",
`动作类型:${input.exerciseType}`,
`结构化姿态指标:${JSON.stringify(input.poseMetrics)}`,
`已有问题标签:${JSON.stringify(input.detectedIssues)}`,
`本次共提供 ${input.imageCount} 张关键帧图片。`,
"请严格依据图片和结构化指标交叉判断,不要编造看不到的动作细节。",
"分析要求:",
"- 识别准备、引拍、击球/发力、收拍几个阶段的质量。",
"- 指出躯干、肩髋、击球臂、非持拍手、重心和脚步的主要问题。",
"- priorityFixes 只保留最重要、最值得优先修正的项目。",
"- drills 要足够具体,适合下一次训练直接执行。",
"- recommendedCaptureTips 说明下次如何补拍,以便提高判断准确度。",
"输出仅返回 JSON,不要附加解释。",
].join("\n");
}
export function renderMultimodalCorrectionMarkdown(report: {
summary: string;
overallScore: number;
confidence: number;
priorityFixes: Array<{ title: string; why: string; howToPractice: string; successMetric: string }>;
drills: Array<{ name: string; purpose: string; durationMinutes: number; coachingCues: string[] }>;
safetyRisks: string[];
nextSessionFocus: string[];
recommendedCaptureTips: string[];
}) {
const priorityFixes = report.priorityFixes
.map((item, index) => [
`${index + 1}. ${item.title}`,
`- 原因:${item.why}`,
`- 练习:${item.howToPractice}`,
`- 达标:${item.successMetric}`,
].join("\n"))
.join("\n");
const drills = report.drills
.map((item) => [
`- ${item.name}${item.durationMinutes} 分钟)`,
` 目的:${item.purpose}`,
` 口令:${item.coachingCues.join(";")}`,
].join("\n"))
.join("\n");
return [
`## 动作概览`,
report.summary,
"",
`- 综合评分:${Math.round(report.overallScore)}/100`,
`- 置信度:${Math.round(report.confidence)}%`,
"",
"## 优先修正",
priorityFixes || "- 暂无",
"",
"## 推荐练习",
drills || "- 暂无",
"",
"## 风险提醒",
report.safetyRisks.length > 0 ? report.safetyRisks.map(item => `- ${item}`).join("\n") : "- 暂无明显风险",
"",
"## 下次训练重点",
report.nextSessionFocus.length > 0 ? report.nextSessionFocus.map(item => `- ${item}`).join("\n") : "- 保持当前节奏",
"",
"## 下次拍摄建议",
report.recommendedCaptureTips.length > 0 ? report.recommendedCaptureTips.map(item => `- ${item}`).join("\n") : "- 保持当前拍摄方式",
].join("\n");
}