Files
calx/CLAUDE.md
2026-07-18 16:48:41 +08:00

5.7 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

小学生乘法口诀(九九表)语音背诵小程序。核心交互:自动读题 → 小朋友开口说答案 → 本地离线语音识别判分全程免手hands-free。两种模式顺序闯关、随机挑战随机模式对错题动态加权

只有两份代码文件:index.html(前端单页,含全部 UI/逻辑)和 server.js(后端,零 npm 依赖)。没有构建步骤、没有 package.json、没有 node_modules、没有测试框架。

Commands

# 启动HTTP:8000 + HTTPS:8443首次自动生成自签证书
bash start.sh            # 或 node server.js

# 换电脑 / IP 变了后重新生成自签名证书
bash gen-cert.sh

# 语法检查(无测试框架,改完用这个自查)
node -e "new (require('vm').Script)(require('fs').readFileSync('server.js','utf8'));console.log('server ok')"
node -e "const m=require('fs').readFileSync('index.html','utf8').match(/<script>([\s\S]*?)<\/script>/)[1];new (require('vm').Script)(m);console.log('frontend ok')"

# 端到端验证识别流水线(生成中文语音 → 打 /stt
say -v Tingting "三七二十一" -o /tmp/t.aiff
ffmpeg -y -i /tmp/t.aiff -c:a libopus /tmp/t.webm
curl -s -X POST --data-binary @/tmp/t.webm -H "Content-Type: audio/webm" http://localhost:8000/stt

# 切换到更快的 base 模型
WHISPER_MODEL=models/ggml-base.bin node server.js

改动 index.html 里判分/解析逻辑(judge/parseNumToken/extractLastNumber)后,务必把纯函数抠出来跑一批中文数字用例(二十一/二一/21/三七二十一/答案是二十一 等都应判对),这是最易回归的部分。

运行环境依赖(非 npm

后端 shell out 到两个系统命令,缺一不可:

  • ffmpeg —— 把浏览器录音(桌面 webm/opus、iOS mp4/aac)统一转 16k 单声道 wav
  • whisper-cli —— whisper.cpp 的离线识别二进制(brew install whisper-cpp

模型放在 models/ggml-small.bin(默认,较准)、ggml-base.bin(较快)。识别全程离线,不联网、不上云。

Architecture

后端 server.js(纯 Node http/https,无框架无依赖)

  • 同一个 handler(req,res) 同时挂在 HTTP(8000) 和 HTTPS(8443) 上。HTTPS 用 certs/ 里的自签证书,缺证书则只起 HTTP。
  • 路由:GET /index.htmlGET /certiOS 下载证书)、GET /healthPOST /stt
  • POST /stt 流水线(transcribe()):收原始 body Buffer → 按 Content-Type 定扩展名写临时文件 → ffmpeg 转 wav → whisper-cli -l zh 输出 txt → 去标点/空白 → 返回 {text}前端只拿 text,判分在前端做,后端不碰口诀逻辑。
  • --prompt 用中文数字示例做偏置,提升「得数」识别率。

前端 index.html(单文件,三个「页面」用 .hide 切换home / game / end

  • 口诀数据buildProblems() 生成 45 条三角表a≤b每条带 weight(随机模式加权用)。kouOf()/numToCn() 生成传统口诀读法(如 三四十二二五一十)。
  • 判分核心(改这里要特别小心,容错是重点需求):judge(transcript,a,b,p) 先剥掉标点/运算词/口语填充词、再剥掉句首因数前缀(正反序、中/数字),然后 parseNumToken/extractLastNumber 把中文数字或阿拉伯数字解析成整数与乘积比对。要同时容忍 二十一/二一/21/完整口诀/句尾数字等说法。
  • 免手录音闭环(关键状态机):autoAsk()TTS 读题)→ startRecordCycle()MediaRecorder 录音 + vadTick() 音量静音检测自动断句)→ stopRecord()onRecStop()(上传 /sttjudgeresolve)。没听清会自动重录,上限 CONFIG.maxAttempts
  • resolve(correct,timedOut) 是所有作答路径(语音/键盘/超时)的唯一汇合点:更新分数/进度、随机模式调 updateWeight()、触发正/负反馈(positive/negative + 音效 beep + 彩带 + 结果卡 afterSheet)、再 nextQuestion()(顺序模式答错则 retrySame 重问同题)。
  • 随机错题加权 updateWeight():答错/超时大幅提权、答对且快降权、答对但慢小幅提权;pickWeighted() 按权重随机抽题 → 错题/慢题更常出现,练熟自动减少。权重同时看对错用时elapsed vs CONFIG.slowMs)。
  • 降级链:不支持录音 / 麦克风不可用 → 自动切数字键盘(switchToPadpadMode/autoActive 两个开关联动),键盘作答同样汇入 resolve()

前端所有可调参数集中在 index.html 顶部的 CONFIG

读题开关、限时秒数、随机题数、错题加权系数、VAD 灵敏度(vadThreshold/vadSilenceMs)、重录次数等。首页「⚙️ 家长设置」面板直接绑定其中常用项。

平台约束(重要,别踩坑)

  • iOS 语音必须 HTTPS + 受信任证书。iOS 上所有浏览器都是 WebKithttp://IP 下苹果直接禁麦克风、不弹授权;且浏览器自带的 SpeechRecognition 在 iOS 基本不可用 —— 这正是要走「录音上传后端识别」而非浏览器识别的原因。iOS 首次需下载 /cert 并在「设置 → 通用 → 证书信任设置」里开完全信任,详见 README.md
  • 音频/麦克风/TTS 必须在用户手势内解锁ensureAudio() 在首页点「开始」(startGame)时调用一次 getUserMedia + AudioContext.resume() + 静音 TTS之后才能全自动。
  • 证书 SAN 里写死了本机 LAN IPIP 变了必须 gen-cert.sh 重新生成。