256 行
8.4 KiB
TypeScript
256 行
8.4 KiB
TypeScript
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");
|
||
}
|