目录

AI 智训平台

基于「体能训练动作模式数据库」(1,324 个动作 + GIF 动图)的 AI 对话训练平台。深色玻璃拟态 + Spline 3D 舞台,移动端优先,并适配刘海屏 / 挖孔屏 / 圆角屏。

目录结构

AISmartTrainingPlatform/
├── data/            # 现成动作数据库集(exercises.json / name_zh.json)
├── images/          # 1,324 张动作缩略图
├── videos/          # 1,324 个动作 GIF
├── server/          # 后端(Node.js + Express + SQLite)
│   ├── data/app.db  # 首次启动自动从 data/exercises.json 导入生成
│   ├── src/
│   │   ├── index.js              # 入口:API + 媒体静态资源 + 前端托管
│   │   ├── db.js / import.js     # SQLite 初始化与数据导入(含难度派生)
│   │   ├── routes/               # auth / exercises / sessions / chat
│   │   └── ai/                   # LLM 代理、系统提示词、JSON 归一化校验
│   ├── test-api.mjs              # 接口冒烟测试(18 项)
│   └── test-normalize.mjs        # AI JSON 归一化单测(11 项)
├── web/             # 前端(React 18 + Vite + Tailwind CSS 3 + shadcn 结构 + framer-motion + Spline 3D)
    ├── scripts/                   # 一次性数据转换脚本(body-paths 几何生成)
    └── src/
        ├── components/ui/         # shadcn 基础组件(card / spotlight / splite)
        ├── components/            # AppShell / 动作卡片 / 详情抽屉 / 消息渲染 / BodyMap
        └── pages/                 # 登录 / 首页 / 聊天 / 动作库 / 教练库 / 打卡 / 我的

快速开始

# 1. 启动后端(首次启动自动导入 1,324 条动作到 SQLite)
cd server
copy .env.example .env    # 填入 AI_BASE_URL / AI_API_KEY / AI_MODEL
npm install
npm start                 # http://localhost:3210

# 2. 启动前端(开发模式,局域网真机可访问)
cd web
npm install
npm run dev               # http://localhost:5173(已代理 /api 与 /media 到 3210)

# 3. 生产部署(单端口)
cd web && npm run build   # 产物在 web/dist
cd ../server && npm start # 直接访问 http://<局域网IP>:3210

环境变量(server/.env)

变量 说明
PORT 后端端口,默认 3210,监听 0.0.0.0 供局域网访问
JWT_SECRET 登录令牌密钥,部署时必须更换
AI_BASE_URL / AI_API_KEY / AI_MODEL OpenAI 兼容 AI 接口(如 DeepSeek),仅后端持有,不出现在前端
ALLOWED_ORIGINS CORS 白名单,公网部署时收紧
PASSWORD_ENC_KEY 管理员查看明文密码用的可逆密钥(缺省回退 JWT_SECRET)
CAPTCHA_SECRET 注册验证码签名密钥(缺省回退 JWT_SECRET)

版本号(与 Git tag 同步)

页面上的 vX.Y.Z 由构建时从 Git 标签注入,不再手写:

  • web/vite.config.js 在 build 时执行 git describe --tags --exact-match,HEAD 正好在 tag 上则显示该 tag;
  • 不在 tag 上 → 1.13.0+2.abc1234(最近 tag + 提交数 + 提交号);工作区有改动 → 追加 -dirty;
  • 没有 .git(源码包构建)→ 回退到 web/package.json 的 version;CI 可用 APP_VERSION 环境变量强制指定;
  • 构建日志会打印 [version] 1.14.0 @ c507971 (tag v1.14.0)。

发版流程:提交 → git tag -a v1.14.0 -m "..." → git push origin master v1.14.0。页面版本会自动跟随,只需保证服务器拉取时带上标签(deploy/update.sh 已包含 git fetch --tags --force)。

核心设计

  • AI JSON 协议:AI 只输出 {type, title, summary, exercises[{exercise_id, sets, reps, rest_seconds, tips}], ...};名称/GIF/肌群/分步要点等由后端按 exercise_id 从数据库回填,杜绝幻觉动作;不存在的 ID 自动剔除并写入 warnings。
  • 教练人设:会话绑定教练的人设同时约束「语气与称呼 / 训练侧重 / 特色内容」三层(prompt.js 末尾另有输出前自检),同一需求下不同教练会给出不同的动作组合、组次与休息风格;coachFor 严格校验公开可见或本人归属。
  • 教练身份:教练以 UUID 为唯一身份;编辑/重新发布通过 PATCH /api/coaches/:id 始终沿用原 ID,不允许生成同名副本;同名但真正不同的 agent 允许并存,以 ID 区分。创建时勾选「发布到广场」会直接写入 visibility=public。
  • 兜底:JSON 解析失败降级为 text 类型文本展示,前端永不崩溃;AI 未配置/超时/429 返回 error 类型消息(含友好提示)。
  • 异形屏:viewport-fit=cover + safe-area-inset-*(顶栏 / 底栏 / 左右安全区)+ 100dvh + visualViewport 监听软键盘。
  • 难度为派生字段:数据集本身无难度,导入时按器械复杂度与动作类型关键词启发式生成(初级 841 / 中级 286 / 高级 197)。

界面与设计系统

  • 设计令牌:tailwind.config.js 中的 ink(深空底色)/ fog(冷灰文字)/ volt(训练信号绿)三组色阶;唯一强调色,避免通用 AI 紫蓝渐变。
  • 应用外壳:桌面端 264px 侧栏(导航 + 最近对话 + 账号),侧栏整体上下滚动(隐藏滚动条),「最近对话」在自身区域内显示独立滚动条;移动端 64px 顶栏(品牌固定「科大智训」,无灰色英文眉标)+ 底部 5 项导航,主页面内容距顶栏统一 16px;路由切换统一回到页面顶部;底栏使用实色背景 + touch-manipulation + touchend 兜底导航,降低部分设备上的点击丢失。聊天页切换为 100dvh 内部滚动 + 输入框贴底。会话抽屉采用同一套滚动机制。
  • 组件层:components/ui/card.tsx、spotlight.tsx、splite.tsx 为 shadcn 结构组件;AppShell、ExerciseCard、ExerciseDetailSheet、MessageView、SessionDrawer 全部复用同一套玻璃材质与按钮体系。
  • 3D 舞台:桌面端使用 Spline 3D 机器人(场景与运行时 WASM 全部本地化:/robot/scene.splinecode + /spline/2.0.114/*.wasm,不依赖 prod.spline.design / unpkg.com);桌面首帧先显示透明宽幅海报 robot-poster-wide.png,3D 就绪后淡入,重复打开首页不再空白等待;/assets 构建产物 1 年 immutable,/robot、/spline 30 天缓存。加载前检测 WebGL,9s 未就绪自动重挂一次、再 7s 失败降级为 OrbFallback;onLoad 后校验 canvas 尺寸,WebGL 上下文丢失自动重建。移动端(<1024px)直接展示透明静态海报 public/robot/robot-poster.png,完全不加载 3D 运行时/WASM,老旧手机也能即时显示且不卡顿。仅首页/登录页展示,打卡/我的不挂载 WebGL 背景。
  • 动作媒体:数据库存的是 images/xxx.jpg / videos/xxx.gif,src/media.js 统一映射到后端的 /media/...;动作库网格悬停即播放 GIF,详情页优先 GIF、失败逐级回退。
  • 登录 / 注册:全端统一为「机器人全屏背景 + 底部亚克力表单」,不再左右分屏;移动端隐藏营销标题,只保留机器人 + 紧凑表单,390×844 一屏完整显示。登录/注册共用固定 68px 槽位,两种模式卡片等高(实测 396px),切换时机器人背景(fixed)不发生位移。

检索层(Top-N 候选注入)

系统提示词不注入全量 1,324 条目录,而是:

  1. extractIntent 从用户消息提取意图(身体部位/器械/目标肌群/难度 4 类中文词典 + 剩余词片段名称匹配),支持否定短语剔除(”我没有哑铃”不会把哑铃加入正向意图,”没有器械”映射为自重);
  2. 每条动作按 意图命中加权评分(器械+5 / 部位+4 / 主目标肌群+3 / 名称片段+3 / 协同肌群+1~2 / 难度+1);
  3. 取 Top-60(CANDIDATE_LIMIT)注入提示词;不足 36 条(CANDIDATE_MIN_FILL)时按部位轮转 backfill,保证”全身/20分钟”类开放需求可跨部位召回;
  4. 同时注入全库概览统计(部位/器械分布计数),让 AI 了解全库边界而不注入全量目录;
  5. 上下文合并检索:检索 query = 最近 3 条 user 消息 + 当前消息(”练上肢”→”我没有哑铃”仍能召回上肢候选)。

效果:提示词中目录部分体积缩减 >80%,且召回质量有单测保障(test-retrieval.mjs)。

流式输出方案(SSE)

  • 端点:POST /api/chat/:id/messages/stream(需 Bearer Token),Content-Type: text/event-stream。
  • 事件序列:
    • meta → {user} 首帧确认用户消息;
    • reasoning → {text, full} 思考型模型的思考过程增量(DeepSeek reasoning_content / OpenAI 风格 reasoning,非思考模型不触发);
    • delta → {text, full} AI 原文增量(前端实时展示生成过程,等宽字体+渐隐遮罩);
    • retry → {attempt, problems} 格式稽核打回,前端重置”生成中”面板;
    • done → {assistant} 归一化后的结构化 payload(与非流式完全一致,前端替换为方案卡片渲染)。**AI 调用失败时同样走 done**,payload 为 type: "error",避免前端误判为通道失败而降级重试、把同一句话重复入库;
    • error → 仅用于传输层异常(如 SSE 通道自身出错)。
  • 思考过程持久化:messages.thinking 列(追加式迁移),历史消息可回看;流式期间展示可折叠”深入思考中”面板,正式回答开始后自动收起。
  • 流式结构化输出的折中:LLM 输出 JSON 需完整后才能 Schema 校验/回填/剔除幻觉,因此流式阶段展示原始生成文本,done 后切换为结构化渲染。
  • 前端 api.chatStream 解析 SSE;流式失败自动降级调用非流式端点 /api/chat/:id/messages。
  • 15s 心跳注释帧防代理断连;X-Accel-Buffering: no 禁用 Nginx 缓冲(公网部署友好)。

打卡统计与肌群热区

  • 单次时长上限:一次训练最长 10 小时(MAX_WORKOUT_SECONDS)。到时自动冻结计时(惰性触发),后端存储与前端展示均按上限截断;历史异常数据在启动时自动修正。后台「打卡监控」Tab 可查看全部未结束训练,支持手动暂停或直接结束(保存并计入统计 / 放弃且不计入统计)。
  • 年度热图多口径:/api/workouts/stats 的 heatmap 同时返回每日 volume / calories / duration / sessions;前端支持容量/时长/热量切换,任何有打卡记录(sessions > 0)的日子至少点亮 1 级——纯自重或计时训练(容量为 0)绝不显示为”未训练”。
  • 肌群热区(18 肌群):后端 server/src/training/muscles.js 把动作数据集的 target / muscle_group / secondary_muscles 归并到 18 个可绘制肌群,按有效组数聚合(主目标权重 1、次级 0.4、热身组不计),一次遍历产出近 7 天 / 近 30 天 / 全部三个窗口。
  • 人体图:几何数据转换自 MIT 许可的 Melih Colpan/MuscleMap(见 NOTICE.md),由 web/scripts/convert-body-paths.mjs 生成 web/src/lib/body-paths.js(≈95KB,动态 import 懒加载);渲染组件 BodyMap.jsx 与色阶为自研实现。
  • 三种模式:训练量(相对分档,读作平衡度)/ 恢复中(24/48/72h 绝对阈值,红橙黄绿)/ 未训练(周期内未练高亮),另支持男/女体型与周期切换。

训练档案模块(用户个性化数据)

三个子模块共用一套数据,独立页面 /growth(侧栏「训练档案」/ 我的 → 训练档案):

子模块 谁来维护 存储
训练目标 用户手工创建(目标/指标/目标值/截止日期/优先级/状态) user_goals
成长档案 训练记录由打卡自动并入(只读),饮食、身体指标、非健身房运动由用户手工记录 growth_records + workouts 系列表合并读取
用户画像 系统根据目标 + 档案 + 训练记录自动推导,只读,数据变化时自动重建 user_personas(source_hash 判断是否过期)

存储策略(避免重复):训练明细继续只存 workouts / workout_exercises / workout_sets,成长档案在读取时 UNION 合并,不复制落库;连续打卡复用 streaks,等级由 training/level.js 实时推导;user_personas 只保存摘要投影(summary/traits/stats)与输入指纹。

上下文策略(AI 如何拿到数据):系统提示词不注入画像/档案原文,只注入:

  1. 一份紧凑的「用户数据 API 目录」(9 个只读接口:goals.list、growth.records、growth.timeline、workouts.list、workouts.best、body.latest、streak、persona、overview);
  2. 数据存量计数(目标 N 个 / 档案 N 条 / 训练 N 次 / 画像是否已生成)。

AI 判断需要个人数据时,整条回复输出 {"type":"data_request","requests":[...]};后端 chatService 识别后调用 ai/userData.js 执行(强制 user_id 隔离、行数裁剪),把结果作为一次性回执注入,再让 AI 输出正式方案。最多 2 轮、每轮最多 3 个接口,结果超过 6000 字符会截断。normalize.js 保留该内部协议,formatGuard 不会把它当最终消息展示。

这样:与个人无关的问题(动作讲解、替代动作)零额外 token;需要个性化时才付出一次查询 + 一次重写的成本,且历史数据不会长期占据上下文。

上下文压缩策略

维度 策略
保留轮数 最多最近 20 条消息(HISTORY_MAX_MESSAGES),且受 token 预算约束
user 消息 原文保留(发送时已限制 ≤2000 字)
assistant 消息 压缩为 context_text:方案标题+概要+每个动作的 id 名称:组数×次数,休息Xs,≤1800 字符;坏 JSON 降级消息用原文截断
token 预算 历史 5000 token(HISTORY_TOKEN_BUDGET)+ 当前消息;从最新往回整轮累加,超预算丢弃最旧整轮(至少保留当前轮)
token 估算 estimateTokens:CJK 字符 ≈1 token/字,非 CJK ≈1 token/4 字符(保守高估,宁可少带不超限)
检索上下文 送入检索层的 query 取最近 3 条 user 消息拼接,保证指代与追加条件可召回
不压缩的部分 系统提示词每轮重建(候选随最新意图刷新),历史中的方案细节靠 context_text 保持可追溯

等级与用户主页

  • 等级系统(Lv.1–100):经验 = 训练分钟数 × 2 + 提问次数 × 3(训练为主、提问为辅);升级门槛为 4 × (等级-1)²,前期快、后期慢,满级 100。「我的」页面展示等级进度、训练总时长、提问次数。
  • 用户主页(/u/:id):排行榜中的每位用户可点击进入主页,展示公开资料(UID、等级、训练总时长、提问次数、打卡次数、总容量、最近训练),不含电话/邮箱等隐私字段。
  • 公告邮箱:用户可回看全部历史公告,并可自行删除(announcement_deletes,仅影响自己,不影响他人)。
  • 后台对话管理:管理员可删除单个对话或清空某用户全部对话,用户的会话数/提问次数/额度用量随查询实时同步。

用户反馈

  • 提交:「我的 → 反馈建议」支持选择「问题反馈 / 功能建议 / 其他」,填写内容(≤1000 字)提交,并查看历史反馈与管理员回复;未回复的反馈可由用户自行撤回(10 分钟内最多 5 条,防刷)。
  • 后台:管理员在「后台 → 用户反馈」按待回复/已回复筛选、按用户名或内容搜索,直接输入回复并发送;回复后用户端即时可见,支持修改或清空回复(清空后回到待回复)。
  • 权限:反馈只对本人与管理员可见;接口 GET/POST/DELETE /api/feedback 与 GET/PATCH /api/admin/feedback。

版本、公告与安全

  • 版本号:web/src/version.js 的 APP_VERSION(与 web/package.json 同步,当前 1.1.0)展示在桌面侧栏底部、登录页与「我的」页脚,便于用户感知更新。

  • 公告系统:

    • 类型:version(版本更新,可标注版本号)/ daily(日常平台公告);
    • 用户登录后自动拉取未读公告,必须逐条或一次性确认后才能关闭(AnnouncementCenter);确认状态写入 announcement_reads;
    • 管理员在「后台 → 公告管理」中发布、编辑、删除公告,并查看已读人数统计;未发布(草稿)公告对用户不可见。
  • 注册验证码:GET /api/auth/captcha 返回 SVG 图形验证码;服务端以 HMAC 无状态生成(5 分钟有效期、一次性消费),注册必须携带 captcha_id + captcha_code,用于抵御脚本批量注册。CAPTCHA_SECRET 建议部署时独立设置。

  • 登录日志:每次登录(成功/失败)记录时间、IP、User-Agent 与失败原因;管理员可在用户列表直接看到「最近登录时间/IP」,并在编辑弹窗中查看该用户的登录记录与成功/失败统计(反向代理场景通过 X-Forwarded-For 取真实 IP)。

  • PWA:public/manifest.webmanifest + public/sw.js;HTML 导航网络优先(断网回退缓存壳),/assets 与图标缓存优先,/api、/media、/robot 一律直连网络。仅在 HTTPS / localhost 等安全上下文注册 Service Worker。

媒体加载容错(图片/GIF)

  • 根因修复:数据库中的图片路径是 images/xxx.jpg / videos/xxx.gif,前端旧实现只加前导斜杠得到 /images/...,会命中 SPA 回退并返回 200 text/html,<img> 解码失败——这就是“图片永远加载不出来”的原因。现在 src/media.js 统一映射为 /media/images/...、/media/videos/...,并追加版本参数击穿历史错误缓存。
  • 服务端路由:/media/images/*、/media/videos/* 精确目录 → /media/* 智能兜底(按 basename + 扩展名在两个目录解析)→ 404 JSON;同时保留 /images/*、/videos/* 兼容别名,便于已缓存旧 URL 的客户端恢复。
  • SPA 回退排除 /api、/media、/images、/videos;任何静态资源后缀(jpg/png/gif/webp/js/css…)未命中时直接 404,绝不再返回 200 HTML。
  • 前端媒体容错链:缩略图 → 同名 GIF → 带版本/时间戳破缓存重试 → /api/exercises/:id/media(服务端读取文件并返回 base64 data URL)→ 内置 SVG 占位。API 通道是对抗浏览器缓存 / 代理 / 扩展拦截的终极兜底。
  • 动作库卡片悬停按需挂载 GIF(不额外浪费带宽),详情页优先 GIF;所有筛选下拉与肌群标签均为中文。

测试

cd server
node test-retrieval.mjs    # 检索层 16 项
node test-chat-flow.mjs    # 对话联调 39 项(Mock LLM,含思考过程/教练人设,无需 API Key)
node test-api.mjs          # 接口冒烟 18 项(需先启动服务器)
node test-normalize.mjs    # JSON 归一化 11 项
node test-workouts-api.mjs # 打卡 API(含热图多口径 / 肌群聚合)
node test-admin-controls.mjs # 后台权限(含教练库管理员权限)
node test-announcements.mjs # 公告系统 / 注册验证码 / 登录日志(30 项)
node test-feedback-admin.mjs # 反馈 / 管理员回复 / 私有教练开聊 / 管理员结束训练(24 项)
node test-coach-identity.mjs # 教练更新保持 ID / 同名不同 ID / 会话绑定(14 项)
node test-growth-pure.mjs  # 训练档案:画像推导纯函数(14 项,无需启动服务)
node test-levels-home.mjs  # 等级 / 用户主页 / 时长上限 / 打卡监控 / 对话删除(32 项)

联调记录:server/test-records/chat-flow-report-2026-10-28.md

关于
133.2 MB
邀请码
    Gitlink(确实开源)
  • 加入我们
  • 官网邮箱:gitlink@ccf.org.cn
  • QQ群
  • QQ群
  • 公众号
  • 公众号

版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9 京公网安备 11010802047560号