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"); }