1a3347f7f9
账号新增 platform 字段,登录/刷新课程/发现课程按平台分发;同时把 JS 脚本收进 script/(目录规范调整)。
11 KiB
11 KiB
OPTIMIZATION
已实现、但还需要你实测确认的事项。确认没问题后把对应条目删掉。
添加账号选择平台
状态:已实现,待实测确认。
做了什么
- 「添加账号」弹窗最上面新增「学习平台」下拉,选项来自
GET /api/platforms(当前只有「莞易学」)。 - 账号记录新增
platform字段(SQLite 老库启动时自动ALTER TABLE补列)。 - 登录(
/api/login)、刷新课程(/api/courses)、发现课程(/api/discover、/api/quiz)、 开始学习(引擎run_one_course)都按账号所选平台取Platform上的实现;选「莞易学」时 仍然自动识别旧版 / 新版课程页。 - 以后新增平台:在
platforms/<名字>.py实现session/login/list_courses/discover/study并注册,下拉里就会多一个选项。
怎么测
- 打开「添加账号」,确认最上面能看到「学习平台 / 莞易学」。
- 正常添加账号并登录,卡片右上角显示所选平台的 label(当前为「莞易学」)。
- 用一个已有账号点「刷新课程」「开始学习」,行为应与改动前一致(旧版 / 新版都能跑)。
如果没生效
- 下拉是空的:
GET /api/platforms应返回[{id, label}];检查platforms/__init__.py的load_builtin()是否导入了平台模块。 - 登录报「平台 xxx 还没有实现登录」:该平台的
Platform.login没注册或 id 对不上。 - 老库启动报
no such column: platform:删掉data/study.db重启(当前账号数据为空), 或确认store._ensure_columns()已执行。
新旧平台合并为「莞易学」
状态:已实现,待实测确认。
做了什么
jspx.dgjy.net(登录入口)和study.dgjy.net(学习入口)在注册表里合并成一个平台:id=guanyixue、label=莞易学。卡片右上角只显示「莞易学」。- 课程发现时按 URL 和页面标记判断代际:旧版
legacy(tier-tree-one/tier3)、 新版spa(Vue SPA //training/*),写进course["generation"]。 - 学习实现按代际分发:
legacy走engine._study_builtin,spa走engine.run_study_course。 - 课程日志里的「平台:」一行会显示
莞易学 · 旧版页面或莞易学 · 新版页面。 GET /api/platforms返回[{id:"guanyixue",label:"莞易学"}],前端用 label 展示。
怎么测
- 分别用一个旧版课程页账号和一个新版课程页账号(或同一账号的两门课)各跑一次自动学习。
- 看卡片「课程日志」:应出现
平台:莞易学 · 旧版页面/平台:莞易学 · 新版页面,不再出现jspx/study平台名。 - 卡片头部徽标应为「莞易学」;打开测验,交卷后后台日志正常,说明
generation分支没走错。
如果没生效
- 日志报「无法识别课程页结构」:把页面快照发出来,可能站点又改版了,
需要补
platforms/detect.py的代际标记。 - 日志报
平台 xxx 没有可用的学习实现:course["platform"]不是guanyixue, 检查platforms/jspx.py的_discover_once()是否被其他分支覆盖了。
前端 React 重构
状态:已实现,待实测确认。
做了什么
- 删除单文件
static/index.html,前端源码迁到web/:React 19 + TypeScript + Vite + Tailwind CSS v4,构建产物输出到static/(index.html+assets/*+favicon.svg)。 - 页面功能保持不变:账号轮询(5s)、卡片内日志(1.5s)、课程勾选、登录 / 刷新 / 开始 / 停止 / 抓包 / 删除、自动交测验 / 反馈表开关、测验弹窗(手动作答 / AI 流式答题 / 再考一次)、 系统设置、系统日志、离线提示、Toast。
- FastAPI 新增
/assets静态挂载和/favicon.svg,/仍返回static/index.html且禁止缓存。
怎么测
- 直接
python app.py,打开页面:账号卡片、状态、课程列表、进度条、日志折叠都正常。 - 走一遍登录 → 刷新课程 → 勾选 → 开始学习 → 停止,观察按钮 loading 和日志面板。
- 打开测验(含单选 / 多选 / 判断 / 填空),提交后看标准答案和「再考一次」。
- 设置里改大模型 / 学习模式并保存,刷新后值仍在;测试连接正常。
- 右上角系统日志可查看、清空;断网后出现「后端未连接」提示。
如果没生效
- 页面空白 / 404
/assets/...:确认static/里有构建产物,且python app.py是在仓库根启动。 - 改了
web/但页面没变:执行cd web && pnpm build,构建产物才会进static/。 - 端口不是 8848 时
pnpm dev的/api代理会打错:改web/vite.config.ts的server.proxy。
首次进入课程自动加入(syncSelf)
状态:已实现,待实测确认。
做了什么
首次进入没加入过的课程时,平台课时树会回 code=500「你还没有加入学习…」,前端把这句话
渲染成挡路的弹层,不点就进不去。真正把账号同步进课程的是页面自己发的这一发:
GET /mod/school/wanpeng/syncSelf?courseCode=<courseCode>
(抓包实录:isJoin=0 needJoin=1 → syncSelf 约 2s → 再拉 getById 就是 isJoin=1。)
POST /course/front/student/join 对 isFreeJoin=0 的慕课课会被拒「该课程无法直接加入,请联系管理员」,
所以现在 ensure_study_join 的顺序是:已加入 → student/join → syncSelf + 轮询 getById(约 11s)
→ 刷新 jspx 课程页复查。course_code 取自 trainCourse/getById 的 courseCode。
怎么测
- 用一个没加入过该课程的账号,或把某课时的
studyLog清掉(制造「首次进入」)。 - 跑一次自动学习,看日志里加入那一段。
预期出现其中一条:
加入培训:同步课程页后已加入(student/join 被拒:…;syncSelf=请求成功)加入培训:刷新课程页后已加入(…)
确认点是:之后的 period/study/log 真记账(课时 completeTime 有值),且全程不需要人工点弹层。
如果没生效
- 日志是
加入培训被拒:…;课程没有 courseCode,无法走同步接口→ 该账号/该课程的getById没返回courseCode,需要再抓一次包定位它的来源 (抓包时重点看syncSelf?courseCode=参数是从哪个响应/本地状态来的)。 - 日志显示 syncSelf 成功但
getById一直isJoin=0→ 同步在服务端没落库,先看syncSelf响应的result.code,再考虑补/mod/gjt/roleList之外的接口。
AI 答题(流式 + 重试 + 日志)
状态:已实现,待实测确认。
做了什么
/api/quiz/answer从「等全部批次算完再返回」改成流式(text/event-stream):每批 5 题算完就推一行,连接一直有数据,反代不会空闲断连。- 每批失败按错误类型决定是否重试一次:超时 / 5xx / 429 / 空内容 / 非 JSON 会重试(第二次关掉
response_format、口头要求 JSON),鉴权类错误直接放弃这一批。 - 一批里只答出部分题时,第二轮只补问没答出来的题。
- 解析加了兜底:标准 JSON、markdown 包裹、截断 JSON、
1.A / 第3题:B / 3. 错误这类自由文本都能识别;多选连写AC会拆成A#C。 - 日志:开始(题数/批数/模型/超时)、每批耗时与题数、失败原因、模型原文摘要、结束统计;同时打到控制台、SQLite(
/api/log/history?key=<账号>)和该账号卡片的「课程日志」。
怎么测
- 打开一个测验,点「AI 答题」,看状态栏是否逐批更新「Ns · 已填 x/y 题」。
- 结束后展开该账号卡片的「课程日志」,应能看到
AI答题 开始/第 1/2 批…/结束若干行;控制台同样能看到。 - 故意把 base_url 改错或断开网络再点一次,日志里应出现
失败 x.xs:LLM 404/超时…,界面提示「详见该账号课程日志」。
如果没生效
- 状态栏一直停在 0/0、日志里没有任何
AI答题行 → 说明请求没到后端(看浏览器 Network 里/api/quiz/answer的状态码)。 - 日志里每批都
失败 … LLM 401→ api_key 不对;LLM 404→ base_url 少了/v1。 - 每批都是
本批只答出 0 题且模型原文很长 → 模型没按要求给题号,可把批次调小(quiz.py里QUIZ_BATCH)或换个指令遵循更好的模型。
模块拆分 + 统一日志源 + 平台插件
状态:已实现,待实测确认。
做了什么
- 代码改成
src/包布局:src/study_tool/下按层分config(常量)、runtime(状态 + 日志)、store(SQLite)、engine(学习引擎)、capture、models、settings_store、llm、quiz、platforms/(平台插件)、routes/(HTTP 接口)。根目录app.py只做启动壳,python app.py/start.bat/ Dockerfile 的启动方式不变;新增pyproject.toml(pip install -e .后可用study-tool命令)。 - 平台插件化:
platforms/base.py定义Platform(id, label, study),platforms/registry.py提供register/get/all,platforms/jspx.py与platforms/study.py各自注册;engine.run_one_course发现课程后改成registry.get(course["platform"]).study(...),不再写死if platform == "study"。新增平台 = 加一个模块 + 一行 import,docs/architecture.md有完整步骤。 - 日志统一走
runtime.log(key, msg, level):控制台、内存(/api/log)、SQLite(/api/log/history)同一份;设置保存、服务启动都会写。 - 新增右上角「系统日志」弹窗,看
key=system的全局日志;卡片「课程日志」看该账号的日志。 - GBK 控制台编不了的字符(⚠ 等)自动降级,不再让 worker 线程抛异常。
怎么测
- 启动服务,控制台应出现
[system] SQLite: …和[system] 控制台启动 -> …。 - 页面右上角「系统日志」→ 看到上面两行;保存一次设置 → 多一行「设置已更新:模型 …」。
- 点账号卡片的「课程日志」→ 学习日志和以前一致;点「清空」后不再出现旧行。
- 跑一门课(旧平台 / 新平台各一次),日志里应出现该课程的流程,说明插件分发走到了对应平台。
python -c "import sys; sys.path.insert(0,'src'); from study_tool.platforms import load_builtin, registry; load_builtin(); print(registry.all())"→ 应看到 jspx / study 两个平台。- 跑一次 AI 答题 → 该账号「课程日志」里是
AI答题 …,控制台同步同一份内容。
如果没生效
- 控制台没有
[system]行 → 跑的是旧进程(端口被占用时新代码会提示后直接退出),先结束旧 PID 再启动。 - 「系统日志」弹窗空白 →
GET /api/log?key=system有 lines 就是前端缓存问题,刷新即可;没有 lines 说明还没产生 system 日志,保存一次设置就能看到。