From 9c6c01eba11ec3be0ccc7ee3d71779ee3f2c5398 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 26 Oct 2026 23:59:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=B9=E7=89=88=E9=98=B6=E6=AE=B5=20?= =?UTF-8?q?2=EF=BC=9A=E6=8C=91=E6=88=98=E5=92=8C=E8=87=AA=E7=94=B1?= =?UTF-8?q?=E5=88=9B=E4=BD=9C=E5=85=B1=E7=94=A8=E5=B7=A5=E4=BD=9C=E5=8F=B0?= =?UTF-8?q?=EF=BC=8C=E5=AD=A6=E7=94=9F=E8=87=AA=E5=B7=B1=E9=80=89=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新的 Workbench:左边题目(可收起)+ AI 对话,右边预览 / 代码(可编辑),底部版本条; 挑战页和自由创作页都用它,原来的“手动提交”“历史对话”标签换成“粘贴外部代码”和“全部历史” - 不再每轮 AI 回复自动提交:学生点“交这一版”,可交多次,成绩仍取最高分;已交的版本不能重复点 - 手改代码、粘贴外部 AI 代码都存成版本(POST /prompt/versions,source 为 edit / manual), 在版本条和提示词链里标“手改”“外部 AI”;它们进入 AI 上下文,AI 在手改后的代码上继续改; 以前手动提交的回复正文为空时用代码补上;edit 的占位提问不参与提示词层级分类 - 互评从全屏弹窗改成对话里的卡片,打分前不显示作者,每个分数写明含义 - 编辑器代码按任务各存一份(useTaskCode),挑战页“清空”不再清掉教程的代码; 提交列表“复制到编辑器”对挑战写入该任务自己的代码 - 抽出 utils/code.ts(整理代码、拆分整页 HTML);删除 RandomRatingModal、ExternalAIPanel Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 3 +- client/src/api.ts | 14 + client/src/components/ai/ExternalAIPanel.vue | 182 ------- .../src/components/ai/PromptHistoryPanel.vue | 5 +- client/src/components/ai/PromptPanel.vue | 163 +++--- .../src/components/ai/RandomRatingModal.vue | 144 ------ client/src/components/editor/Editors.vue | 77 +-- client/src/components/editor/Preview.vue | 11 +- .../src/components/submissions/ChainModal.vue | 5 +- client/src/components/workbench/CodeTabs.vue | 83 +++ .../components/workbench/PasteCodeModal.vue | 79 +++ .../src/components/workbench/RatingCard.vue | 257 ++++++++++ .../src/components/workbench/VersionBar.vue | 166 ++++++ client/src/components/workbench/Workbench.vue | 474 ++++++++++++++++++ client/src/composables/useTaskCode.ts | 56 +++ client/src/global.css | 36 ++ client/src/pages/ChallengeDetail.vue | 468 ++++++----------- client/src/pages/CreationDetail.vue | 300 +++-------- client/src/pages/Submissions.vue | 13 +- client/src/store/prompt.ts | 54 +- client/src/utils/code.ts | 66 +++ client/src/utils/helper.ts | 7 + server/scripts/classify-prompts.ts | 2 +- server/src/routes/prompt.ts | 53 +- server/src/services/classifier.ts | 4 +- server/src/services/prompt.ts | 17 + server/src/ws.ts | 17 +- server/tests/api.test.ts | 28 ++ server/tests/ws.test.ts | 33 +- 29 files changed, 1748 insertions(+), 1069 deletions(-) delete mode 100644 client/src/components/ai/ExternalAIPanel.vue delete mode 100644 client/src/components/ai/RandomRatingModal.vue create mode 100644 client/src/components/workbench/CodeTabs.vue create mode 100644 client/src/components/workbench/PasteCodeModal.vue create mode 100644 client/src/components/workbench/RatingCard.vue create mode 100644 client/src/components/workbench/VersionBar.vue create mode 100644 client/src/components/workbench/Workbench.vue create mode 100644 client/src/composables/useTaskCode.ts create mode 100644 client/src/utils/code.ts diff --git a/CLAUDE.md b/CLAUDE.md index c5ece64..ba7d56e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,7 @@ The backend was rewritten from Django in 2026-10 to cut memory use (~25 MB vs. 1 - `src/auth.ts` — Django-compatible password hashing; JWT login, no cookies: `POST /account/login` returns a 15-min HS256 `access_token` (sent as `Authorization: Bearer`, signed with `JWT_SECRET`, random per process if unset) and a 14-day opaque `refresh_token` (only its sha256 is stored, in `auth_refresh_token`; `POST /account/refresh` trades it for a new access token, logout / password change / deactivation delete it). An invalid or expired Bearer token is a 401 even on public endpoints so the client knows to refresh. `requireUser/requireAdmin/requireSuper` (401 / 403) - `src/routes/` — `account`, `tasks` (tutorial / challenge / creation), `files` (task assets + image upload), `submission`, `showcase` (mounted under `/submission/showcase`), `prompt` - `src/services/` — `score.ts` (rating weights super×0.5, admin×0.3, normal×0.2, then Bayesian smoothing with C=3 and zones), `gradebook.ts`, `llm.ts` (OpenAI-compatible streaming via fetch; DeepSeek default, ARK for doubao models; chat context is the last 20 messages with older code blocks elided), `classifier.ts` (Bloom-level L1–L6 prompt tagging, runs in background), `cascade.ts` (Django's CASCADE deletes done by hand, plus the shared delete rules: `hasRatingsOrAwards`, `deleteRound`), `pyround.ts` (`pyRound`/`pySum` reproduce Python's `round()` and `sum()` so scores match the old system bit-for-bit) - - `src/ws.ts` — `/ws/prompt//?token=` (code generation chat) and `/ws/guidance//?token=...` (prompt coaching); one message per connection at a time (a message sent while the AI is still replying gets an error, payloads capped at 64 KB); on disconnect the pending user message is deleted and the LLM request aborted + - `src/ws.ts` — `/ws/prompt//?token=` (code generation chat; the AI context and the page's chat include `conversation`, `edit` and `manual` messages) and `/ws/guidance//?token=...` (prompt coaching); one message per connection at a time (a message sent while the AI is still replying gets an error, payloads capped at 64 KB); on disconnect the pending user message is deleted and the LLM request aborted - `scripts/` — one-off CLIs: `create-superuser.ts`, `classify-prompts.ts`, `recalculate-scores.ts` - Roles: `super`, `admin`, `normal`. Task model is multi-table: `task_task` + one of `task_tutorial` / `task_challenge` / `task_creation` (`task_ptr_id`) @@ -29,6 +29,7 @@ The backend was rewritten from Django in 2026-10 to cut memory use (~25 MB vs. 1 - `src/router.ts` — Vue Router; `meta.auth` needs login, `meta.role` (`staff` = admin+super, `super`) is checked in the guard after `store/session.loadUser()`, `meta.bare` pages render without the top bar - `src/theme.ts` — the only place colors are defined: one palette (light + dark values) becomes both `--wp-*` CSS variables and Naive UI `themeOverrides`; use `var(--wp-...)` in styles, never hard-coded colors. No `color-mix()` (Chrome 105) - `src/icons.ts` — `` renders bundled `lucide-vue-next` icons (works offline); register new names in its map. Fonts are bundled too (`@fontsource/jetbrains-mono`) + - `components/workbench/Workbench.vue` — shared challenge / creation page: left = brief + AI chat (`PromptPanel`), right = preview / code (`CodeTabs`) + `VersionBar`. Every assistant message with code is a version; hand edits and pasted external-AI code are saved as versions via `POST /prompt/versions` (`source` `edit` / `manual`, stored as a user+assistant pair). Challenges are submitted only when the student clicks “交这一版” (`POST /submission/` with `message_id`), never automatically. Editor code is per task (`composables/useTaskCode.ts`, localStorage `web-code::`); the tutorial page still uses the shared `store/editors.ts` - `components/layout/AppShell.vue` — top bar for every page; `pages/Dashboard.vue` is the teaching sidebar (“教学”) under `/dashboard` - `src/store/` — Reactive state (plain Vue refs, not Vuex/Pinia); `prompt.ts` / `guidance.ts` own the WebSockets - `src/pages/`, `src/components/` — user management uses `components/dashboard/UserFormModal.vue` (there is no Django Admin anymore) diff --git a/client/src/api.ts b/client/src/api.ts index a196ad1..780dafc 100644 --- a/client/src/api.ts +++ b/client/src/api.ts @@ -395,6 +395,20 @@ export const Gradebook = { } export const Prompt = { + // 保存一个不是 AI 生成的版本:手改的代码,或者带提示词的外部 AI 代码 + async saveVersion( + taskId: number, + code: { html: string; css: string; js: string }, + prompt?: string, + ): Promise<{ message_id: number; user_message_id: number; source: string }> { + const res = await http.post("/prompt/versions", { + task_id: taskId, + ...code, + prompt: prompt || undefined, + }) + return res.data + }, + async listConversations(taskId?: number, userId?: number) { const params: Record = {} if (taskId) params.task_id = taskId diff --git a/client/src/components/ai/ExternalAIPanel.vue b/client/src/components/ai/ExternalAIPanel.vue deleted file mode 100644 index 71ea019..0000000 --- a/client/src/components/ai/ExternalAIPanel.vue +++ /dev/null @@ -1,182 +0,0 @@ - - - - - diff --git a/client/src/components/ai/PromptHistoryPanel.vue b/client/src/components/ai/PromptHistoryPanel.vue index bd6bf97..9f6ec45 100644 --- a/client/src/components/ai/PromptHistoryPanel.vue +++ b/client/src/components/ai/PromptHistoryPanel.vue @@ -70,9 +70,9 @@ - {{ item.source === "manual" ? "手动提交" : "AI 对话" }} + {{ sourceLabel(item.source) }} @@ -133,6 +133,7 @@ import { onMounted, ref, watch } from "vue" import { Icon } from "../../icons" import { useMessage } from "naive-ui" import { renderMarkdown } from "../../utils/markdown" +import { sourceLabel } from "../../utils/helper" import { Prompt } from "../../api" import type { PromptHistoryItem } from "../../utils/type" import { parseTime } from "../../utils/helper" diff --git a/client/src/components/ai/PromptPanel.vue b/client/src/components/ai/PromptPanel.vue index 45369fc..33214d7 100644 --- a/client/src/components/ai/PromptPanel.vue +++ b/client/src/components/ai/PromptPanel.vue @@ -18,8 +18,15 @@ -
-
我
+
+ 你手动修改了代码 +
+
+
+ 我 + 用外部 AI +
-
AI
-
-
- - {{ pair.assistantMsg.submitted ? "已提交" : "提交此版本" }} - + +
+ v{{ versionOf(pair.assistantMsg) }} + 已提交
@@ -62,6 +62,7 @@ >
AI 正在生成中…
+
@@ -80,18 +81,22 @@ style="flex: 1; min-width: 0" @keydown.enter.exact.prevent="send" /> - -