Commit Graph
362 Commits
Author SHA1 Message Date
xuyueandClaude Opus 5.5 11368813cc feat(流程图): 「写代码 / 画流程图」变成工具栏上的切换,AI 的评分和历次分数挪进结果页签,题单里也能画
- 流程图题的工具栏最左边是「写代码 / 画流程图」。原来流程图藏在语言下拉里,叫一个名叫
  Flowchart 的「语言」;画完一道题之后它还被记成语言偏好,下一道能画的题一打开就是画布
- 「交给 AI 点评」交完切到「结果」页签:评的时候写着「AI 正在看你的图」、画布照样能改;
  评完是分数、评语、改进建议、四项得分,顶上一排历次分数,点哪次看哪次。
  原来是提交按钮旁一个 1000px 的弹框,盖住画布,翻历次评分要一页一页点
- 画到 A / S:给「照着它写代码 →」,题单里 1.5 秒后回题单(和代码通过一样),
  课堂条当场标成做完;只在这一页上刚评完的那次才算,进页面读到以前的 A 不跳
- 「把这一版载回画布」只在画布和那一版不一样时出现;载回、进页面读回草稿后都缩放到装下整张图
- 题单入口能画流程图了(原来只有题库入口能画),题单里的代码草稿、流程图草稿各存一份,
  加入题单之前写的、画的不会一进题单就摆在编辑器里
- 手机上不给「画流程图」(节点库是拖放,手机上拖不动),题面里以前画好的那张照样看得到

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 18:19:16 +08:00
xuyueandClaude Opus 5.5 948e9a734d feat(题目): 例子摆成一张「输入 | 输出」的表,三个例子不再占掉大半屏
原来每个例子各占一个小标题加两个框,三个例子就把输入输出说明挤出了一屏;
现在一行一个例子、输入输出对齐,每格右上角带复制。「自己输入数据试试」留在表头旁边

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:48:13 +08:00
xuyueandClaude Opus 5.5 6a0f6399b7 feat(题目): 「我的提交」按第几次列出来,点开就地看代码,一键放回编辑器(Ctrl+Z 能撤回)
- 每条写成「第 N 次 · 结果 · 语言 · 时间」,点一下在抽屉里展开代码,带「放回编辑器」「复制」
  和「完整详情」(新标签打开,测试点、自测猫都在那边)。原来是点编号弹出 70vw 的详情弹窗,
  盖住整个题目页,「复制回到题目」还要整页跳一次
- 放回编辑器是一次普通的整段替换,编辑器的撤销历史记着它,按 Ctrl+Z 就回到原来的代码
- 顶上一句话:「你是班上第 N 个做对的,班上一共 M 人做对了」(没有班级时是全站),
  后面跟「看看他们的写法」
- 加入题单之前交的那几次照旧锁着,改成行内一句说明,点不开
- 协作中的老师不给「放回编辑器」:那会儿编辑器里是学生的代码,放回去会经协作通道直接盖掉

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:48:13 +08:00
xuyueandClaude Opus 5.5 5bb69ecce3 feat(点评): 点评改回轮盘并重新设计——每块直接写着名字,选完就地变成玫瑰图看大家怎么选
上一版把轮盘换成了按钮格子,改回轮盘,但重新画了一个:
- SVG 画的 7 块环形扇区,每块上直接写着表情和名字(最大 16px),不用先把鼠标移过去;
  悬停的那块染上主色、往外弹出,圆心报它的名字
- 选之前哪里都没有人数;选完就地变成玫瑰图:每块从里往外填一段、长短按人数,标「N 人」,
  自己选的那块是主色、弹出、带 ✓,圆心写「你选了 学到了 · 共 N 人评过」
- 没登录、没做对时轮盘照画、整个淡下去,圆心写原因
- 原来的轮盘用 CSS clip-path 加 color-mix(),后者 Chrome 111 才有,机房 105 那档扇区没有底色
- 上一版的防误触(600ms、强制弹窗里不响应 Enter / 空格、不预选)和选完的「下一题」都保留;
  抽屉里能用键盘选

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 17:19:37 +08:00
xuyueandClaude Opus 5.5 c64b0a402b feat(点评): 点评轮盘换成一排大按钮,选之前不显示人数,选完看大家怎么选、直接去下一题
- 七个选项不变,排成 4 + 3 的大按钮(表情 + 15px 的字)。原来的轮盘要把鼠标移到扇区上
  才看得清,每块的字只有 12px 左右;还用了 Chrome 111 才有的 color-mix(),机房 105 那档
  渲染不出扇区底色
- 选之前不显示「N 人选择」,免得跟风;选完换成横条图,标出自己选的那一项
- 防误触:弹出后 600ms 内点不了,强制弹窗里不响应 Enter / 空格,也不预选任何一项
- 选完弹窗不马上关,下面只有一个按钮、焦点就在上面:这节课的「下一题」,或者刚才被拦下
  的换题「继续」,都没有就是「好的」
- 标题从「恭喜你成功提交」改成「做对了!」:交上去不等于做对
- 提交失败留在弹窗里提示「没记上,再点一次」;没做对的人在抽屉里只看到一句说明

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:22:48 +08:00
xuyueandClaude Opus 5.5 116b0a2c95 feat(题目): 左栏顶上标出「这节课做完几道」、题单进度或比赛倒计时,统计 / 点评 / 我的提交收进抽屉
- 上下文条(按入口三选一):课堂 = 「这节课 做完 3/5」+ 每道题号 + 上一题 / 下一题;
  题单 = 题单名 + 进度 +「第 N 题」+ 上一题 / 下一题;比赛 = 比赛名 + 每道题号 +「还剩 38:12」+ 排名。
  题号只放号码,状态用 ✓ / ! 字符区分,不只靠颜色;多于 5 道横向滚动,当前题保持可见。
  通过之后当场 +1,不用刷新
- 左栏只剩「题目 / 结果」两个页签;统计 / 点评 / 我的提交变成页签行右侧的按钮,点开是抽屉,
  只盖题面、编辑器不动,交完判出结果时自动收起。手机上收进页签行的「⋯」
- 流程图并进「题目」页签:参考流程图默认折叠;自己画到 A/S 的那张缩略显示、可放大,
  一进题就能看到(原来要先切到「流程图」这门「语言」),也不再强行切页签
- 「本题已经被你解决啦」两条整宽提示改成标题下的「✓ 已解决 / ! 尝试过」标签;
  时间内存限制只写「限时 1 秒」
- 入口差异收成一张表(problemPageContext):题单里不给抽屉、状态标签、相似题;
  比赛里没有点评和求助。工具栏的「本题提交」挪到「我的提交」抽屉底部
- 顺手修:没有例子的题点「去自测猫」会抛错;「我的提交」没交过时一片空白

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:02:28 +08:00
xuyueandClaude Opus 5.5 45505e8384 feat(题目): 工具栏加「运行例子」、结果页签加「自己输入」,去掉每个例子旁的「测试」和顶栏的分栏切换
Deploy / deploy (push) Canceled after 0s
- 工具栏「运行例子」:一键用全部例子试跑(Judge0,不算提交),结果进「结果 › 运行例子」,
  每个例子 ✓ / ✕,没对上的沿用答案错误那套中文说明;比赛里只摆输出、不给提示句
  (后端在比赛里故意不做例子对比);SQL 题不给
- 「结果」页签分成提交结果 / 运行例子 / 自己输入三段;「自己输入」顶替顶栏「自测」模式
  那个单独的编辑器
- 试跑出错(编译、运行、超时)给中文说明,Judge0 的英文原文折叠起来
- 例子比对两边都去掉末尾空白:原来只 trim 了运行输出,样例末尾带空格的题永远「不通过」
- 去掉题面里每个例子旁的「测试」、顶栏的「双栏 / 自测 / 题目」切换、EditorForTest 和
  screenMode store

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:35:58 +08:00
xuyueandClaude Opus 5.5 f4db7dc81c feat(题目): 判题结果从提交按钮下的浮层挪到左栏「结果」页签——改代码时结果就摆在旁边
原来结果是提交按钮下面的 n-popover:点一下外面就收起、最高 600px、盖住编辑器,
学生没法一边看「正确输出 / 你的输出」一边改代码,还得靠「上次结果」按钮找回来。

- 左栏新增「结果」页签(手机上排在「代码」后面),交上去或者语法检查没过就自动切过去;
  页签标题带状态图标(✕ / ✓ / ! / 判题中转圈),不只靠颜色
- 页签用 show:lazy:切走不卸载,编辑器里的错误标记、AI 提示都留着
- 「题目描述」改叫「题目」;去掉浮层和「上次结果」按钮

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:21:57 +08:00
xuyueandClaude Opus 5.5 5cb7cfa924 refactor(流程图): 评分状态挪进 store——交完切去写代码,这次的评分结果不再丢
Deploy / deploy (push) Canceled after 0s
SubmitFlowchart 只在语言选成 Flowchart 时才挂载,评分的全部状态(WebSocket 订阅、
轮询兜底、3 分钟超时)都长在它身上:交完马上切去写代码,组件一卸载就断开连接,
正在评的那次结果丢了 —— 不提示分数,评到 A/S 也不亮到「我的流程图」。

- oj/store/flowchart.ts:提交、评分跟踪、评完的提示、A/S 亮图、最近一次分数;
  换题和离开页面时收掉
- SubmitFlowchart 只剩从画布取图和评分详情弹框
- 顺手补了登录检查:原来没登录也能点,请求发出去才被拦,只看到一句「提交失败」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:49:45 +08:00
xuyueandClaude Opus 5.5 26e22e2542 refactor(题目): 提交状态挪进 store——手机上切页签、桌面切分栏,判题结果和点评不再丢
提交按钮那个组件(SubmitCode)在编辑器的工具栏里,而提交的全部状态和判完之后的
反应都长在它身上:手机上交完切到「描述」页签(Naive 的页签切走即卸载)、桌面切到
「题目 / 自测」模式,编辑器一卸载,判题结果、AI 提示、失败计数、通过后的点评、
题单里的回题单全丢了。

- oj/store/submission.ts:这次提交、判题跟踪、语法检查报错、结果面板开合、冷却、
  AI 提示(useSubmissionHint)
- SubmissionEffects.vue:挂在题目页这一层,失败计数、标成已解决、烟花、强制点评
  (连同「先点评再换题」的导航拦截)、回题单
- SubmitCode 只剩按钮和结果面板
- WebSocket 通道和判题监控的收尾改成 onScopeDispose:onUnmounted 在 store 里挂不上

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:36:35 +08:00
xuyueandClaude Opus 5.5 a8b83ba762 refactor(题目): 比赛和题单改用题库那个编辑器——题单里的求助终于接得上,一键换中文标点也能用了
比赛和题单原来用的是另一个精简版编辑器(BasicEditor),没有协作绑定、也没有
错误标记扩展:题单里学生点「求助」,老师接单后编辑器绑不上;比赛和题单里
「把中文标点都换成英文」点了没反应、出错行也不标红。三种入口统一成 ProblemEditor,
删掉 BasicEditor。流程图仍然只在题库入口出现(语言列表那道闸没动)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:18:58 +08:00
xuyueandClaude Opus 5.5 0daa74651d fix(题目): 通过后马上点「下一题」或换题,强制点评会被跳过
Deploy / deploy (push) Canceled after 0s
从答案正确到点评弹窗出来有 1.5 秒(外加查一次评过没有),而换题会取消弹窗的
定时器 ——「下一题」在通过的那一刻就出现,学生手快就绕过了强制点评。

现在这段时间里离开这道题的导航(下一题、题号直达、顶栏导航、相似题……)先拦下:
立刻弹点评,选完再去原来要去的地方。只改 query(左栏切页签)不拦;弹窗已经开着时
也不拦,免得点评接口一直失败时把学生关在这一页。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:08:45 +08:00
xuyueandClaude Opus 5.5 1b149a4f3f fix(题目): 在题目页里直接换题(下一题、题号直达)后,例子和语言还停在上一道题
Deploy / deploy (push) Canceled after 0s
题目页换题是同一个组件复用、只换路由参数,几处只在创建时读一次的状态就停在了上一道题:

- 例子:新题下面摆着旧题的例子,「测试」也拿旧例子比对
- 语言:下拉选项不跟着变,也不回退——从 Python 题跳到 SQL 题,语言停在 Python,提交必被拒
- 比赛 / 题单的编辑器只在挂载时载入一次代码
- 流程图:分数按钮、评分弹框、「我的流程图」还是上一道题的,上一道题还没评完的那次会挂到新题上

语言回退挪进 problem store 的 supportedLanguage(),两个编辑器在载入草稿之前调用,
保证先定语言再读这门语言的草稿。没有用给整块加 key 强制重新挂载的办法:课堂协作的
绑定依赖组件复用,重挂载会让协作中切题的老师语言恢复不了。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:15:50 +08:00
xuyue 8ca427b5cd update
Deploy / deploy (push) Canceled after 0s
2026-09-27 23:28:17 +08:00
xuyueandClaude Opus 5.5 a7f6ebaf49 feat(激励): 知识点地图——每个知识点做对几道、在哪一档、再做几道升级,首页一句话提示本周进步
Deploy / deploy (push) Canceled after 0s
现有 38 个成就全是累计量(通过几题、提交几次、连续几天),起步的人人都有、往上的够不着,
而且没有一个在说「你学会了什么」。连续打卡在这里结构上做不到:2025 秋学生最长连续交题
天数的中位数是 1 天,连续 7 天的只有 3 人。

- GET /me/knowledge:题数 ≥10 的知识点标签(10 个),每个给做对几道、档位、周一时的档位、
  下一档门槛;四档 入门 1 / 会了 3 / 熟练 题数 40%(≤10)/ 精通 75%(≤20)。
  回测 2025 秋:中位数学生一学期升 5 档;「入门」一档是给只做对一两道的 71 人加的
- 同班 ≥30% 点亮过的知识点才会出现在「还没碰过」里,免得 Python 班看到「C 语言」
- 个人主页加「我的知识点」,只在看自己时显示;首页一句话:本周升级 > 离下一档最近的 >
  去点亮一个班里在学的,点了去按知识点筛的题目列表

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 23:17:43 +08:00
xuyueandClaude Opus 5.5 a63bf6ea07 feat(题目): 通过之后直接给出这节课的下一题;在题目页里换题时不再带着上一道题的结果和评价弹窗
Deploy / deploy (push) Canceled after 0s
72% 的提交一次就通过,每次通过之后学生都要回首页或再问一遍题号去找下一道。

- 结果面板在通过时显示 LessonNext:取 /me/class-activity(老师布置的优先,否则推断),
  给出排在当前题后面、还没通过的第一道,后面都做完了再从头找;全做完了显示「都做完了」
  并指向题单。当前题不在列表里、比赛、题单入口都不显示
- 评价弹窗关掉后重新打开结果面板:点弹窗时面板会被当成点了外面收起来,「下一题」就在面板里
- 换题(同一组件只换路由参数,有了题号直达和下一题之后成了常规操作)时收掉上一道题的
  结果面板、语法错误、还在跟的提交(新增 useSubmissionMonitor.reset),并取消通过后
  那两个 1.5 秒的定时器——否则会给还没做的新题弹评价,上一道判完时还会把新题标成已解决

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 22:58:19 +08:00
xuyueandClaude Opus 5.5 a6bf845e06 feat(课堂看板): 标出最近几节课经常没交的学生,求助的学生置顶,点一下打开求助列表
Deploy / deploy (push) Canceled after 0s
2025 秋至少上过 5 节课的学生里,12.5% 七成以上的课一道都没交,37% 的学生贡献了
一半以上的「没交」;当堂那份「今天还没交过」把偶尔没来的和一直不动手的排在同一行。

- 看板接口给每个学生加 recentAttended:最近几节课(最多 5 节、不含今天、45 天内;
  一节课 = 同班同日 ≥5 人做同一题)里交过几节,另给 recentLessons
- 「今天还没交过」拆成「经常没交」(≥3 节可比且最多交过 1 节)和其他;表格加一列
  「最近 N 节」;只给老师看,投影模式不出现
- 求助读老师端常驻 WebSocket 的 collabStore.requests(不改后端):求助的学生置顶,
  状态显示「举手了 / 老师在帮」,点一下打开求助列表;求助提示不论布没布置题都显示

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 22:47:24 +08:00
xuyueandClaude Opus 5.5 80106eaa04 feat(课堂): 老师的课堂看板——布置这节课的题、看全班谁还没开始谁卡住了,布置的题直接出现在学生首页
2025 秋平均每节课有 14 个平时在用的学生一道都没交(三分之一),交了 3 次以上还没过的
平均不到 1 个;而老师原来只能一道题一道题打开统计弹窗看。

- 新表 class_lesson(迁移 0023,仅建表):一个班一天一行,存老师布置的 problem.id 与顺序;
  day 存东八区日历日文本,created_by CASCADE
- GET /classroom/board:这个班今天的题 × 全班学生(通过与否不限今天、今天交了几次、
  最后提交时间);不给班级时猜最近两小时提交人数最多的班,不记老师上次选的
- PUT /classroom/lesson:按展示题号布置,对不上的题号点名拒绝,空数组 = 清掉
- /me/class-activity 优先返回老师布置的题(source = teacher),否则照旧推断
- 前端 /classroom:没交过的名单置顶,按 没交过 → 卡住 → 在做 → 做完 排序,15 秒刷新;
  投影模式只显示每题完成人数、不出名字;用户菜单给老师加入口
- 学生首页:老师布置的标题写「今天老师布置的题」,没人交时不显示 0/0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 22:37:59 +08:00
xuyueandClaude Opus 5.5 7e964fdbdd feat(题目): 答案错误时拿公开样例重跑,并排显示正确输出和你的输出,按差异类型给中文提示
Deploy / deploy (push) Canceled after 0s
原来答案错误只有四个字。6901 条历史答案错误在公开样例上重跑过:73% 在样例上就错了,
其中约三分之一只差格式(没输出、input() 提示文字、空格换行、中英文标点、小数位数)。

- judge/run.ts 的 checkSamples:判出答案错误后以内联测试点重跑题目样例(output: true),
  记下第一个没过的样例和学生在它上面的输出,写进 statistic_info.sample_check;
  样例全过则只记 passed。全是公开信息,不碰隐藏测试点;比赛不跑(有期末考试)
- 前端按差异归类(数字先于「多了内容」判,免得 007 被当成多打了文字),
  并排显示输入 / 正确输出 / 你的输出,标出第一处不同的行;只差空白时把空格、换行画出来,
  末尾空白不画(判题本来就忽略)
- 提示给具体值(「正确输出是 84.10,你输出的是 84.1」),input("…") 单独提示

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 22:14:24 +08:00
xuyueandClaude Opus 5.5 db70ab62e8 feat(题目): 运行时错误告诉学生第几行、为什么出错(Python 看回溯,C 看信号),不泄露测试数据
原来学生只看到「运行时错误」四个字:主判题 output: false,Python 回溯没回来;
C 的信号值在 info 里,而 info 只给管理员。

- Python 判出 RE 时,拿第一个失败测试点单独重跑一次(内联测试点 + output: true),
  从回溯取行号(扣掉模板前置行)、异常类型和按句式归的类别。主判题仍不开 output,
  免得每个点最多 16MB 的输出回到 worker
- C / C++ 取失败点的信号和退出码(段错误提示 scanf 漏 &、return 非 0 等)
- 写进 statistic_info.runtime_error;不存异常原文(里面常带测试点输入),名字只在
  出现在学生代码里时才存——raise Exception(input()) 实测只落下类型
- 前端按类型翻成中文、摆出出错那行并在编辑器标红;比赛提交不诊断(ACM 只报对错)
- 没有诊断的 RE 不再说「代码格式写错了」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 22:01:30 +08:00
xuyueandClaude Opus 5.5 2a4405d2ea feat(题目): Python 语法错误改成中文说明,在编辑器里标出位置,中文标点可以一键换成英文
Deploy / deploy (push) Canceled after 0s
中职学生看不懂英文报错。原来提交前用浏览器里的 Skulpt 拦语法错误,只弹一句
「第 N 行存在语法错误」——Skulpt 九成报的是 bad input / bad token,没有列号。

- 提交前的语法检查挪到服务端:/code/format 先用 CPython 编译一遍(只编译不执行),
  api 镜像加 python3-minimal(3.13,与判题机同版本,+17MB),前端删掉 Skulpt
- 报错按规则表翻成中文(pythonError.ts),生产库 2695 条 Python 编译错误覆盖 99.7%;
  提交前拦下的和判题回来的编译错误共用同一张卡片
- 编辑器里标红出错行和 ^ 指的位置
- 中文标点(历史编译错误的 22%)一键换成英文,跳过字符串和注释、处理 f 字符串花括号;
  594 条里修好 593 条,经 tokenize 核对没有改到字符串内容;走编辑器事务,可 Ctrl+Z

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 21:47:01 +08:00
xuyueandClaude Opus 5.5 039992dc86 feat(前台): 顶栏可以输入题号直达;首页列出班里最近一次课一起做的题和自己的完成情况
Deploy / deploy (push) Canceled after 0s
课上老师报题号、全班去找,是 2025 秋七成非比赛提交的来路,界面上原本没有它的入口。

- 顶栏(桌面端)加题号框,首页「找题」同一套逻辑:题号对上直接进题,否则去列表搜
- 新接口 GET /me/class-activity:同班同一天 ≥5 人做过的题,取最近 7 天里最近的一天,
  附带我自己的状态与班里通过人数;首页据此显示「今天班里在做」卡片,没有就不占位

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:58:15 +08:00
xuyueandClaude Opus 5.5 d07459928d feat(标签): 题目列表的标签分成「知识点」「主题」两组,新建的标签在后台标出待归类
Deploy / deploy (push) Canceled after 0s
- 迁移 0022:problem_tag 加 category(knowledge / theme)和 category_confirmed;
  存量 25 个题材、题集类标签归主题,其余归知识点,全部记为已确认
- 前台题目列表按两组显示标签,组内按题目数从多到少
- 后台标签管理:每行切换分类;出题时新建的标签默认知识点、标「待归类」并排在最前,
  页头提示待归类数量,可点「确认」保留默认;加分类筛选
- 新接口 PUT /admin/problem-tags/:id/category,设置分类即视为确认

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:38:43 +08:00
xuyueandClaude Opus 5.5 e1b6a9e9fa feat(前台): 题单卡片显示完成进度,手机上题目列表不再横向溢出,个人主页数据排得更紧凑
- 题单卡片:完成进度条和「已完成 x / y 题」,「已完成」改成绿色,未获得的徽章置灰;
  进度按加入时的快照算,题单后来加了题就注明「新加了 N 题」,不再出现「已完成」却是 5 / 6
- 题目列表:手机上只留状态、编号、题目、难度四列;标签默认收起,选中的标签显示在按钮旁
- 个人主页:六项数据保留,改成三列两行的小卡片,头像缩小

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:27:00 +08:00
xuyueandClaude Opus 5.5 6bf8c6e1d9 style(前台): 列表页去掉表头 emoji、已结束的比赛改成灰色;比赛列表置顶进行中的比赛,排名页先看榜单
- 前台 53 处表头改成纯文字,删掉只剩这一个用途的 utils/renders.ts
- 「已结束」从红色改灰色(比赛列表 / 详情 / 后台 / 首页一起)
- 比赛列表第一页、无筛选时顶部列出进行中和即将开始的比赛
- 内容少的页面页脚贴底,不再悬在半屏
- 排名页两张柱状图挪到最后,第一屏是 Top100、本周进步榜和我的排名

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:13:29 +08:00
xuyueandClaude Opus 5.5 31aa5f6e74 feat(前台): 登录后首页改成个人首页;题目详情页的代码要求挪到最前面、样例并排显示
- 登录后 / 是个人首页:继续学习、最近提交、进行中/即将开始的比赛、公告;
  没登录仍是题目列表,题目列表另有 /problem 地址,顶栏「题目」指向它
- 继续学习按服务端留痕取最近打开的一课,不用 localStorage(机房一机多人)
- 题目详情:标题下加难度与时空限制,代码要求提到最前,小节收紧,
  样例输入/输出并排;工具栏「提交信息」「更多操作」改为「本题提交」「更多」

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 20:08:47 +08:00
xuyueandClaude Opus 5.5 d3823430f9 feat(后台): 侧栏分组带图标、可收起,加顶栏;所有后台页面统一页头
Deploy / deploy (push) Canceled after 0s
- 侧栏按 概览 / 教学内容 / 学情分析 / 站点管理 分组,去掉超管菜单里的重复项;
  菜单项的角色与路由守卫一一对应,没有题目权限的教师不再看到点了就被踢回首页的「题目」
- 顶栏:面包屑、回到前台、明暗切换、用户菜单(退出登录);窄屏侧栏自动收起
- 新增 PageHeader:标题 + 右侧主操作 + 下方筛选栏,编辑页带返回按钮
- 卡点分析 / 年度趋势 / 标签管理从题目列表的按钮挪进侧栏
- 首页改成统计卡片 + 班级排名卡片;设置页按钮移到卡片右上角
- 题目、比赛、题单、用户列表的「选项」列固定在右侧,不再被挤出屏幕

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 19:54:42 +08:00
xuyue d25b5dbe6d update
Deploy / deploy (push) Canceled after 0s
2026-09-27 19:36:28 +08:00
xuyueandClaude Opus 5.5 41cd141769 docs(CLAUDE.md): 修正前端文档里指错的路径,补上提交约定
Deploy / deploy (push) Canceled after 0s
- apps/web/CLAUDE.md 三处 `../CLAUDE.md` 指向不存在的 apps/CLAUDE.md,改成 `../../CLAUDE.md`
- composables 清单补上 aiStream
- 删掉与旧版本对比的措辞(< 94 基线、mermaid-legacy、PUBLIC_OJ_URL)和格式化、时区的历史记录
- 新增「提交」一节:直接在 main 上干活、中文提交信息格式(原来只写在 AGENTS.md,Claude Code 读不到)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 19:27:22 +08:00
xuyueandClaude Opus 5.5 a698ebf585 perf(前端): 首页和题目详情页按需加载,首页 477 → 380KB、详情页 1031 → 682KB(传输量)
Deploy / deploy (push) Canceled after 0s
浏览器实测(preview 构建,生产规模数据):首页解压后 1428 → 1112KB,
未登录的题目详情页 3317 → 2021KB。后端接口 6–8ms,不是瓶颈,没动。

首页:
- 登录速报弹框挂在默认布局上,静态 import 了 md-editor-v3(~160KB JS + 76KB CSS),
  而 AI 分析只在登录后、提交满 3 次才有。MdPreview 改成异步组件,目标是本地包装
  LoginSummaryPreview.vue —— 直接 import("md-editor-v3") 会让 rolldown 把它和
  CodeMirror 语言包并成一个 467KB 的 chunk,排名页反而涨了 560KB(实测)
- 题目列表经 oj/api.ts 还带着 zod(~70KB):那个文件静态 import 了题目详情、提交详情
  两个 schema。改成和请求并行动态加载(单独的 oj/detailSchemas.ts,理由同
  profileSchema.ts),闸门仍是同步的 —— submissionDetailSchema 有 .catch(),
  不能改 contractDeferred

题目详情页:
- skulpt(~226KB gzip)原来一选 Python 就立刻预取。改成登录后才取(没登录交不了),
  且等 requestIdleCallback,不和题面、编辑器抢带宽
- StatisticsPanel 只有老师可见,却被 EditorToolbar 静态 import,每个学生都白拉一份
  chart.js(~68KB gzip)。改成异步组件,打开课堂统计时才加载
- SubmissionResult(带 DataTable)改成异步组件,第一次提交弹出结果面板时才加载

浏览器核过:首页正常;详情页未登录不再加载 skulpt / chart.js / DataTable;学生登录后
skulpt 空闲预取、提交后结果面板正常弹出;老师点课堂统计按需加载图表并正常渲染。
登录速报的 AI 分析与语法错误拦截路径本机没能触发,未在浏览器里核。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:34:32 +08:00
xuyueandClaude Opus 5.5 ca1df38440 chore(web): 构建配置清理 —— 别名读 tsconfig、调高 chunk 警告阈值、删死掉的 browserslistrc
Deploy / deploy (push) Canceled after 0s
- resolve.alias 换成 resolve.tsconfigPaths,别名只在 tsconfig.app.json 写一份
- chunkSizeWarningLimit 调到 1000:超 500 kB 的三个 chunk(skulpt / wangeditor /
  @mermaid-js/parser)都是懒加载,默认阈值下每次构建都报,真涨了反而看不见
- 删 .browserslistrc(chrome >= 90):renderLegacyChunks 关着时 plugin-legacy 不读它,
  vite / oxc 也不读,留着只会和 105 的基线打架

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:15:05 +08:00
xuyueandClaude Opus 5.5 9412ba3cf6 refactor(契约): 展示题号统一叫 problemDisplayId,题单 id 统一叫 problemSetId
Deploy / deploy (push) Canceled after 0s
前后端同时改,部署前开着的旧页面要刷新一次(题号列会是空的、提交筛选会失效)。

- 挂在非题目对象上的展示题号,原来叫 problem(字符串)或 displayId,统一成
  problemDisplayId:提交列表、流程图列表、统计的 lastFailure / items /
  今日最热题、站内信里的提交、后台题单的题目列表。别处的 problem 都是对象
  或数字 id,这几处是字符串,最容易看错
- 列表筛选的 query 参数 ?problemId= 实际传的是展示题号,改成
  ?problemDisplayId=(提交列表、流程图列表、三个统计接口)
- 契约里 problemsetId / problemSetId、problemset / problemSet 两种拼法并存,
  统一成 problemSetId / problemSet,和 ProblemSet* 类型名、problem-sets
  路径、前端路由参数一致。drizzle 的列键跟表名走,没动
- aiDetail.user(用户名字符串,而契约里别的 user 都是对象)→ username。
  ai_analysis.data 里存的历史快照仍是 user,没有代码读回它
- 后台题单题目表的列是按字符串键 'displayId' 取的,类型检查抓不到,一并改

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:08:31 +08:00
xuyueandClaude Opus 5.5 5ae830a7f9 refactor: 一批名不副实的命名改掉(只动一端,不碰契约)
后端:
- getUserByToken → authenticateToken:它会删会话、续 TTL、记在线,不是纯读
- todayStart → dayStart(instant):本来就接受任意时刻,不只是「今天」
- objectValue → asRecord,删掉 4 份一模一样的私有拷贝和 `as toObject` 别名
- 报错文案 "does not exists" / "Already be a public problem" 改通顺

前端:
- utils/types 里 Contest / User / Problem 三个别名删掉:Contest 原来指后台
  比赛、契约里同名的却是学生侧。现在 Contest = 学生侧、AdminContest = 后台,
  后台用户表单叫 AdminUserForm,题目详情直接用 ProblemDetail
- SubmissionInfo → ContestSubmissionInfo,免得和 submission.info 撞名
- utoa / atou → compressToBase64 / decompressFromBase64(实际是 zlib + base64),
  encode / decode → base64EncodeUtf8 / base64DecodeUtf8
- filterResult → toProblemRow、toProblemListItem → toAdminProblemRow,
  行字段 submission / rate → submissionCount / acRate
- 组件:Modal → AddFromLibraryModal、Form → EditorToolbar、
  ContestEditor → BasicEditor(题单也在用它)、rank 的 Index → RankMedal、
  Name → UserNameCell、Password → PasswordCell、ContestType → ContestAccessTag

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:08:21 +08:00
xuyueandClaude Opus 5.5 572a818d49 style: 按 Oxfmt 重新格式化
Deploy / deploy (push) Canceled after 0s
换格式化器带来的差异,只有 5 个文件:长联合类型改成每项一行、以 | 开头。
和上一个提交分开,方便 blame 时跳过(git blame --ignore-rev)。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:37:23 +08:00
xuyueandClaude Opus 5.5 44295188b9 chore: 格式化、lint、任务编排换成 Vite+
Prettier + oxlint + 散在各包的检查脚本,收成 vite-plus(1.0.0-rc.1)一套,配置只有
根目录 vite.config.ts 一份:

- fmt(Oxfmt,替掉 Prettier):semi: false、printWidth: 80。Vite+ 下 Oxfmt 默认 100 列,
  不显式写 80 就是 267 个文件的 diff。范围和原来 Prettier 一致,只管代码 ——
  md / yml / json / toml 排掉(文档表格、compose 注释是手排的),db/meta 快照和
  unplugin 的两个 .d.ts 照旧排掉。.prettierrc.toml / .prettierignore 删除
- lint(Oxlint,vite-plus 自带同版本 1.85.0,单独的 oxlint 依赖删掉):denyWarnings。
  范围从三个 src 目录扩到全仓,多抓出 apps/web/vite.config.ts 里没人用的 loadEnv,删掉
- run.tasks:typecheck:api / typecheck:web / check:routes / check:ast 四个检查 + 汇总的
  verify(vp check + 前四个)。一律经 `bun run <脚本>` 调 —— 任务定义在根包,PATH 是根
  目录的 node_modules/.bin,直接写 tsc 拿到的是根目录的 TS 5.9 而不是 apps/api 的 TS 7,
  实测报一堆假错
- 根脚本:fmt → vp fmt,lint → vp lint,新增 check → vp check。verify 不设同名脚本
  (Vite+ 不许任务和脚本重名),用 `bunx vp run verify`
- dev 仍是 bun run --parallel:常驻进程,缓存用不上

实测:verify 首跑全过;第二遍 5/5 命中缓存、0.25 秒;往 apps/api 塞一个类型错误,
只有 typecheck:api 和 check:routes 重跑并失败、前端直接命中缓存;.vue 里塞类型错误
同样被抓到;denyWarnings 下有一条 warning 退出码就是 1。前端构建、后端单二进制编译照常。

代价:vite-plus 带 vitest、tsgolint 等约 120MB 依赖,只进 Docker builder 阶段、不进运行
镜像,CI 的 --prebuilt 部署不受影响。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:37:23 +08:00
xuyueandClaude Opus 5.5 f8b04af4ea chore: 加 oxlint,现存 19 条 warning 清零
Deploy / deploy (push) Canceled after 0s
原来只有 Prettier,没有任何 lint。根目录加 `bun run lint`(oxlint,覆盖后端、前端、
契约,--deny-warnings:有一条就算失败),CLAUDE.md 常用检查里加上。

现存 19 条顺手清掉:
- 11 处未使用的 import / catch 参数 / 回调参数
- oj/store/problem.ts:`...problem.value?.languages` 展开可能是 undefined
  (外层 if 已经保证非空,去掉 ?.)
- admin/problem/detail.vue:`contestId = contestId` 自赋值,删掉
- judge/sql/engine.ts:注释里的 `/*x*/` 示例原来靠零宽空格防止提前闭合注释,
  改成 `*\/`
- 其余:多余的 !!、new Array(n)、展开里多余的 `|| {}`

两边类型检查、前端构建、后端单二进制编译都过。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:25:27 +08:00
xuyueandClaude Opus 5.5 83b9d23c68 perf(前端): zod 移出首屏包,首屏 JS 721KB → 594KB(gzip 237 → 199KB)
首屏包里带着整个 zod 运行时(~100KB),来路有三条,缺一条都挪不出去(逐条撤回实测过):

- 契约包没有 sideEffects: false,入口从桶里取任何一个值都会把所有 schema 模块带上。
  契约包里没有顶层副作用(核过),加上之后 rolldown 才能按导出摇树
- roles.ts 里的 USER_TYPE / toAdminType 是入口要的,同文件却定义了两个 z.enum。
  schema 挪到 admin.ts(唯一用处),roles.ts 文件头写明不许 import zod
- 启动就要调的 getWebsiteConfig / getAILoginSummary 在 oj/api.ts 里,而那个文件同步
  import 了题目详情、提交详情两个 schema。这两个函数挪到 shared/api.ts;那两道
  闸门保持同步 —— submissionDetailSchema 有 .catch(),放行原文和解析结果不等价
- /me 的契约闸门改成 contractDeferred:先放行原文,schema 按需加载后在后台核、只记
  日志。userProfileSchema 没有 default / transform,两者等价。动态 import 的目标是
  单独的 shared/profileSchema.ts —— 直接 import 契约桶会让 rolldown 重排共享 chunk,
  实测首屏反而涨到 1811KB

浏览器实跑核过:登录后 profile 正常加载、profileSchema 按需拉取、无控制台报错;
故意喂一份不符的数据,后台闸门照常打出 [契约] 分歧日志。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:23:31 +08:00
xuyueandClaude Opus 5.5 6b2da41882 refactor(题单后台): 添加 / 编辑弹窗两两合并成 ProblemModal、BadgeModal
AddBadgeModal / EditBadgeModal、AddProblemModal / EditProblemModal 各是两份八成相同
的组件,改一处表单项另一处总要漏。合并成一个组件,传了 badge / problem 就是编辑、
null 就是添加;两种模式分别 emit create / update,detail.vue 的四个 handler 不变。

表单项、校验、文案、提交的数据形状都照旧(题目弹窗添加时带 problemId、编辑时只显示
标题)。有一处刻意的行为变化:编辑弹窗原来只在换了一条记录时才重新填表,取消后再打开
同一条会看到上次没保存的改动;现在每次打开都按库里的值重新填。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:16:03 +08:00
xuyueandClaude Opus 5.5 f49834f19f refactor(班级 PK): 十个柱状图 computed 合成 barChart 工厂
十张对比图只差标题和取哪一列,原来各写一份 18 行的 computed。合成
barChart(label, key),key 限定为 ClassComparison 里值必为数字的字段,
渲染出的数据结构(labels / datasets / 配色 / borderWidth)逐字不变。
Chart.js 在本文件只注册了一次,没有重复。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:16:03 +08:00
xuyueandClaude Opus 5.5 7a390323fe refactor(前端): 接口错误改成 ApiError 类,catch 不再写 any
拦截器原来 reject 一个裸对象 `{ error, data }`,没有类型也没有类型守卫,于是 46 处
`catch (err: any)` 直接读 `err.data` / `err.error` —— 代码 bug 抛出的 TypeError 也按
接口错误读,弹出来的是 undefined 或英文。

- utils/api.ts:reject 改成 `ApiError extends Error`,字段名沿用 error(错误码)/
  data(文案),存量读法语义不变;加 isApiError / errorCode / errorMessage。
  errorMessage 对非接口错误一律用 fallback,不把 TypeError 的英文甩给学生
- 46 处 catch 全部换掉 any(45 处接口错误 + pythonSyntaxCheck 里 Skulpt 的错误对象)
- 断网等没有响应体的情况,文案从英文 "Request failed" 换成「网络异常,请检查网络后重试」

浏览器实跑核过:登录密码错误仍显示「用户名或密码不正确」(错误码分支)、
接口错误 reject 出来是 ApiError、网络不通时拿到中文文案、非接口错误走 fallback。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:12:40 +08:00
xuyueandClaude Opus 5.5 c43da01ff2 refactor(AI): 四份 SSE 请求代码合成 streamAI + useAIStream
学情分析、班级分析、班级 PK、AI 提示四处原来各有一份几乎一样的 fetch + SSE 解析,
错误处理各漏各的:

- 裸 fetch 绕过了 axios 拦截器,未登录时只显示「AI 分析生成失败」而不弹登录框。
  拦截器里的全站错误处理抽成 handleGlobalApiError,两条路共用
- 班级分析、班级 PK 离开页面不中止流。useAIStream 在作用域销毁时自动 abort
- AI 提示换了一次提交不掐上一条,旧提示会写进新提交的面板

utils/stream.ts 的 streamAI 负责请求 + 解析 + 错误翻译;组件用 useAIStream
(waiting = 第一个字还没到,running = 流没结束);学情分析在 store 里直接用 streamAI。

浏览器实跑核过:学情分析逐字流出、provider 500 时显示后端的固定中文文案、
未登录调用弹出登录框。班级 PK / 班级分析卸载即中止这条没有在浏览器里点过
(本机库里没有班级数据),逻辑在 useAIStream 的 onScopeDispose。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:01:43 +08:00
xuyueandClaude Opus 5.5 ca0dbbf0c5 fix(登录态): 退出和换人登录时整页重载,不留上一个人的数据
signOut 只清了 profile 和 localStorage,AI 分析、学情小结等 store 还在内存里。
机房一台机器轮着用,下一个学生登进来就能看到上一个人的 AI 分析。

- 主动退出:router.replace 换成 window.location.replace("/")
- 被动下线(会话过期、别的标签页登出)仍然原地弹登录框,学生正在写的代码不丢;
  getMyProfile 记住本页最后一个登录的用户,登进来的换了人就整页重载

挨个 store 去 reset 迟早漏一个,重载一次最彻底。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 14:51:52 +08:00
xuyueandClaude Opus 5.5 fb240d4254 feat(限流): 登录、注册、比赛密码防爆破
三处原来都不限次数。学生初始密码弱,/classes/:name/usernames 又能公开列出整班
用户名,等于现成的撞库名单。

services/throttling.ts 加固定窗口计数(INCR + EXPIRE NX,锁定时长就是剩余 TTL),
何时计数由调用方决定:

- 登录按用户名 15 分钟 10 次失败,成功清零;按 IP 15 分钟 100 次失败,成功不清零
  (否则拿自己的号登一次就能重置)。机房一个班共用出口 IP,所以 IP 那道放得宽
- 注册按 IP 每小时 100 个成功注册
- 比赛密码按「人 × 比赛」10 分钟 10 次失败
- IP 取 X-Real-IP:api 端口不对外,Caddy 用 {client_ip} 覆盖写入,可信

前端登录、注册、比赛密码三处按新错误码给中文提示。

本机实跑核过:第 11 次错误登录被拦、成功清零(用户名大小写不敏感)、同 IP 换
100 个用户名被拦且别的 IP 不受影响、比赛密码第 11 次被拦。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 14:51:52 +08:00
xuyueandClaude Opus 5 b16e11e85d fix(题目推荐): 已 AC 过滤下推 SQL、难度按语义排序、难度标签渲染
Deploy / deploy (push) Canceled after 0s
相似题目推荐(GET /problems/:displayId/similar)三个 bug:

- 排除已 AC 的题发生在 limit(5) 之后,候选被筛掉几条就少几条,甚至清零让整块
  不渲染。而这个接口只在刚 AC 或连挂三次时才调用,正是最容易全中的时候。
  (老栈是 .exclude(...) 在切片之前,重写时引入的回归)
- order by difficulty 是 text 字典序,High < Low < Mid,「由易到难」实际是最难的
  先上。改成显式 case 排名,并加上共享标签数作为第一排序键、id 兜稳定序。
- 前端 ref<any[]> 盖住了类型错误:getSimilarProblems 已过 filterResult,难度是
  中文,模板却在比 'Low' / 'High' 再查 DIFFICULTY 表,结果每条推荐都渲染成黄色
  的「中等」。

顺带:比赛题不再发这个请求(接口只在公开题库按 displayId 找,撞号会在比赛中把
题库列给学生);多余的 id in (子查询) 换成 join 中间表。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 09:24:46 +08:00
xuyueandClaude Opus 5 954797a9a5 feat(AI 提示): 提示分级(L0 反问 / L1 定位 / L2 概念)与输出后过滤
Deploy / deploy (push) Canceled after 0s
提示不再一上来就把话说完。等级记在「学生 × 题目」上,没有单独的表 —— 它就是
ai_hint.level 的历史,当前等级 = 这道题上(最近一次 AC 之后)给过的最高一级。

阶梯(services/hint-level.ts)
- 只有学生点「再多一点提示」才升级(请求带 more),不带就按当前等级再生成一次
- 升一级要先再交一次:锚点是「这一级是**什么时候**开出来的」,也就是这一级最早那条
  提示的 ai_hint.create_time,必须有比这个时刻更新的提交才准 +1。锚点不能用提示所在
  那条提交的时间 —— 端点谁的提交 id 都认(只校验归属),拿一条老提交去要提示,锚点
  就退回到那条老提交的时间,连点两下 more 就能从 L0 爬到 L2,一次新提交都不用交
- AC 之后清零;编译失败自成一档(level = -1),既不消耗也不推进阶梯
- HINT_MIN_FAILURES 3 → 1:门槛的活由阶梯接走了,第一次失败只开放 L0,而 L0 只反问、
  什么都不泄露,拦着它没有意义
- canEscalate 由后端算好在 done 事件里给,前端不自己推阶梯

输出后过滤(services/hint-filter.ts)
- 「不要给代码」写在 prompt 里只是软约束,模型忍不住一次就把这一级的意义废掉了。
  所以整段生成、过滤通过才推给前端,逐字显示改由前端模拟 —— 边流式边过滤做不到,
  发现违规时内容已经在学生屏幕上了
- 判定只用客观、低误报的信号:代码块、过长的行内代码、整行不含中文的类代码行、
  和标准答案重合 3 行以上、L0 一句问句都没有
- 违规就重生成一次,只重一次,再不过发写死的兜底话术。重试措辞按档分叉:编译档本来
  就允许给片段,对它说「不要出现任何代码」等于用阶梯的标准把这一档也砍了
- 两次都留痕(filter_attempt / filter_blocked / filter_reason),7.5 的输出过滤触发率
  就是从这三列出来的

services/ai.ts 加 streamWhole:事件形状和 streamChat 一样,前端不分叉。produce 期间
每 15 秒发一行 SSE 注释当心跳 —— 这条流中间有一大段静默(诊断 20s + 生成 60s +
重生成 60s,最坏 140 秒),而 NPM / nginx 的 proxy_read_timeout 默认 60 秒,超了学生
看到「请求失败」,后端却还在烧第二次调用,那条提示照样落库、照样把等级推上去。

prompt 版本另开 3 / 4(阶梯上每一级都换了 system),编译档仍走 1 / 2 的单段式基线,
两批数据不混在一起。迁移 0021 给 ai_hint 加四列,都可空、不带默认值,已有的行留 null
表示「分级上线前」。

实跑
- 阶梯:在 dev 库上用真实行驱动 decideHintLevel。正常路径 S1→S2→S3 走出 L0→L1→L2,
  同级连点 more 不升,AC 之后回 L0,编译档给 L-1 且不推进阶梯。把旧锚点规则复刻出来
  跑同一组数据做对照:只拿最老那条提交反复 POST,旧规则 L0→L1→L2(零新提交),
  新规则钉死在 L0,正常路径两者行为一致
- 过滤:起假 AI 服务端走完整条链路。L1 摊平代码→重生成后合规(attempt 2 / 未拦),
  L0 两次都甩代码块→兜底话术(blocked,reason 两条相连),编译档抄标程→命中「和标准
  答案重合 3 行」且追加的是分叉后的措辞
- streamWhole:produce 拖 16.5 秒收到 1 条心跳;同一份流喂给前端 consumeJSONEventStream
  只解析出 delta + done(注释行被静默跳过,前端零改动);中途 cancel 断开后 produce
  跑完不抛
- api / web typecheck、check:routes、fmt 全过

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 19:03:21 -06:00
xuyueandClaude Opus 5 8b4d8899f9 feat(判题机): 自建镜像升级工具链,语言收敛到 C / C++ / Python
Deploy / deploy (push) Canceled after 0s
上游 QingdaoU/JudgeServer 停更在 2024-04(registry 上的 latest 和 1.6.1 是同一份
镜像,编译器停在 gcc-13),没有新版可拉,所以自己重编。docker/judge/ 是只改工具链
的 Dockerfile 分叉,server/ 和 Judger/ 从上游固定 commit b28aa56 拉,一行没动。

镜像 oj2-judge-2(不在任何 registry 上:本机 build.sh --save → scp → docker load)
- gcc/g++ 13 → 14.2,Python 3.12 → 3.13.5,都是 trixie 默认
- Go / JDK / Node 整套删掉:前端的题目语言复选框从来只给 C / C++ / Python / SQL,
  12 万条提交里 Java 44 条、Golang 15、JavaScript 3,全是很早以前的
- 体积 1.1GB → 433MB;默认走清华源,构建 12 分钟 → 40 秒(--no-mirror 换回官方)
- deploy.sh 加一道自检:镜像不在本机就中止,并打印该跑的三条命令

C 的编译参数加三个 -Wno-error(implicit-function-declaration / int-conversion /
incompatible-pointer-types):gcc-14 把它们从 warning 提成了 error,而 -w 压不住。

语言值统一成 Python(迁移 0019 / 0020)
- 0019:Python3(104527 条提交)与 Python2(3 条)并成 Python,一并改掉 937 道题的
  languages、75 个 template 键、15 个 ast_rules 键、257 条 answers、1235 个用户的
  成就指标 _languages(languages_used 重算,总和 1928 → 1925,少的 3 个是同时用过
  两种 Python 的人)
- 0020:把 Java / JavaScript / Golang 从 84 道题的可选语言里摘掉 —— 不摘的话那些题
  的语言下拉还能选 Java,提交必 SYSTEM_ERROR
- 契约新增 normalizeLanguage() 别名表,判题侧一律走 judgeConfigFor():旧客户端
  localStorage 里的 Python3、迁移前排进队列的任务都还能判;协作的语言归一也走它,
  否则上线那一刻学生页面里的 Python3 会静默落到 C
- 回滚要连数据一起回,只滚代码会让所有 Python 提交变 SYSTEM_ERROR

实跑
- 判题冒烟 docker/judge/smoke.ts 13 条全过:三种语言、六种状态码、gcc 宽松度
- 拿备份里的真实代码逐文件比对新旧镜像的编译结果,0 差异:C 提交 1951 份
  (1725 过 / 226 CE)、C++ 882 份、Python 2000 份、20 篇 C 教程的 93 个代码块。
  不加那三个 -Wno-error 的话,C 有 26 份会从能过变成 CE
- 迁移在灌了 12.4 万行真实数据的一次性库里跑过:0 残留、没有题目被清空;
  dev 库用真正的执行器跑通
- check:ast 56 个 target 全过,前后端 typecheck 均 0

顺带记下一个升级之前就有的坑(现在随 Go 一起消失,写在 README 里):GOCACHE 指向
容器的 tmpfs,判题机重启后第一次 Go 提交是冷构建,Go 1.22 要 5.6 秒 CPU、超过 3 秒
的编译预算,于是重启后第一个交 Go 的学生必吃一次 CE,后面的人缓存热了又都正常。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 06:24:05 -06:00
xuyueandClaude Opus 5 b4c0f89291 fix(个人主页): 总共学习天数改为有提交的天数
Deploy / deploy (push) Has been cancelled
原来是首末提交之间跨了多久,现在按东八区日期对提交去重计数,比赛提交也算。
首末提交时间仍只看比赛外的提交,口径不变。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 07:07:57 -06:00
xuyueandClaude Opus 5 47d8f46bdb feat(AI 提示): 提示落库并收集学生评价,落进 ai_hint
Deploy / deploy (push) Has been cancelled
AI 时代 OJ 设计的 2a:先把现有提示记下来,为后面的分级和两段式诊断攒对照数据。
提示本身的行为不变,发给模型的 prompt 一字未改。
- 迁移 0017 建 ai_hint:模型、prompt 版本、内容、错误、耗时、学生评价;
  成功和失败都记(失败率是 AI 悄悄变差时最先动的数)。挂在 submission 上 CASCADE
- streamChat 第三个参数改成 { onComplete, onError }:onComplete 的返回值并进
  done 事件,提示 id 由此带回前端;班级学情分析那处调用同步改写,行为不变
- 落库失败只记日志,不影响学生拿到提示
- 新增 POST /ai/hint/:id/feedback:只能评自己的提示(别人的一律 404),可以改票
- 前端提示生成完出「有帮助 / 没帮助」,选中的高亮;后端没存上时不出按钮
- 实跑:本地假 LLM 验证成功 / provider 断开 / 缺 AI_KEY 三条路径的落库,
  评价接口六种请求,浏览器里按钮出现、改票、重复点不发请求

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 06:00:10 -06:00
xuyueandClaude Opus 5 a0ef204bd2 feat(提交): 采集编辑过程信号,落进 submission_trace
Deploy / deploy (push) Has been cancelled
AI 时代 OJ 设计的第 1 步:给「可信 AC」和学情分析攒数据,本身不判任何事。
- 契约:提交请求加可选的 trace(活跃时长、键入/粘贴/删除字符数、切后台次数等,
  只有计数、不含按键内容);写成 .optional().catch(undefined),坏了就当没带,
  不让附带数据把提交挡成 400
- 迁移 0016 建 submission_trace,与 submission 一对一、CASCADE;since_prev_ms
  由服务端在同一条 INSERT 里算(排掉自身),bigint —— 实测已有 44 天的间隔,int4 装不下
- 后端写 trace 失败只记日志,不影响提交
- 前端 oj/problem/utils/editTrace.ts 是模块单例(扩展对象不能过 Pinia 的响应式代理),
  只数带 userEvent 的事务:格式化回写 / 载入草稿 / 协作对方的改动天然排除;
  closeBrackets 越过右括号时是原样替换,按 no-op 跳过。比赛编辑器同样挂上
- 实跑:后端四种请求、前端浏览器里键入/粘贴/删除/setCode 回写/切后台/提交后清零,
  计数与预期逐项一致

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 05:46:55 -06:00
xuyueandClaude Sonnet 5 20a6ddc79c feat(自学): 重做学生端页面与教师端学情概览
Deploy / deploy (push) Has been cancelled
学生端:目录 + 居中限宽正文 + 可收起的示例代码栏;目录改成三态圆点,
顶部加总进度;上一课/下一课栏桌面端固定在底部。
教师端:汇总卡片、学生状态标签与筛选(未开始/7 天没学/只读不练等)、
最后学习补「N 天前」、按练习表加「没人一次对/多数人卡住」提示。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 05:07:37 -06:00