feat: 改版阶段 2:挑战和自由创作共用工作台,学生自己选版本提交
- 新的 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 <noreply@anthropic.com>
This commit is contained in:
1 parent
ff1eb2982e
commit
9c6c01eba1
29 files changed
+1748
-1069
No files matched your search
@@ -226,6 +226,34 @@ describe("提交与删除", () => {
|
||||
expect(all(`SELECT * FROM prompt_message`)).toEqual([])
|
||||
})
|
||||
|
||||
test("保存手改 / 外部 AI 版本,交这一版时关联到它", async () => {
|
||||
makeUser("stu")
|
||||
const task = makeTask("T")
|
||||
const stu = await client("stu")
|
||||
const edit = await stu("POST", "/api/prompt/versions", { task_id: task, html: "<p>1", css: "", js: "" })
|
||||
expect(edit.json.source).toBe("edit")
|
||||
const ext = await stu("POST", "/api/prompt/versions", {
|
||||
task_id: task,
|
||||
html: "<p>2",
|
||||
css: "",
|
||||
js: "",
|
||||
prompt: "用外部 AI 做的",
|
||||
})
|
||||
expect(ext.json.source).toBe("manual")
|
||||
const history = await stu("GET", `/api/prompt/history/${task}`)
|
||||
expect(history.json.map((h: any) => [h.source, h.prompt])).toEqual([
|
||||
["edit", "(手动修改了代码)"],
|
||||
["manual", "用外部 AI 做的"],
|
||||
])
|
||||
|
||||
const sub = await stu("POST", "/api/submission/", { task_id: task, html: "<p>1", message_id: edit.json.message_id })
|
||||
const linked = get(`SELECT submission_id FROM prompt_message WHERE id = $id`, { id: edit.json.message_id })
|
||||
expect(linked.submission_id).toBe(sub.json.id.replaceAll("-", ""))
|
||||
// 同一个版本再交一次,更新原来的提交,不新建
|
||||
await stu("POST", "/api/submission/", { task_id: task, html: "<p>1!", message_id: edit.json.message_id })
|
||||
expect(all(`SELECT html FROM submission_submission`)).toEqual([{ html: "<p>1!" }])
|
||||
})
|
||||
|
||||
test("删除一轮对话:连带提问和关联提交;被评过分的不能删", async () => {
|
||||
makeUser("stu")
|
||||
makeUser("other")
|
||||
|
||||
+32
-1
@@ -8,9 +8,12 @@ import { client, freshDb, makeTask, makeUser } from "./helpers"
|
||||
|
||||
// 假的模型接口:每段之间停一下,方便测试中途断开
|
||||
const PIECES = ["好的", "```html\n<p>hi</p>\n```"]
|
||||
// 最近一次发给模型的请求
|
||||
let lastRequest: any = null
|
||||
const llm = Bun.serve({
|
||||
port: 0,
|
||||
fetch() {
|
||||
async fetch(req) {
|
||||
lastRequest = await req.json()
|
||||
const enc = new TextEncoder()
|
||||
const stream = new ReadableStream({
|
||||
async start(ctrl) {
|
||||
@@ -126,6 +129,34 @@ describe("WebSocket 代码生成", () => {
|
||||
expect(all(`SELECT * FROM prompt_message`)).toEqual([])
|
||||
})
|
||||
|
||||
test("手改的版本进入对话,AI 下一轮在它上面改", async () => {
|
||||
makeUser("stu")
|
||||
const task = makeTask("挑战")
|
||||
const stu = await client("stu")
|
||||
const c = (await connect(`/ws/prompt/${task}/?token=${stu.tokens.access_token}`))!
|
||||
await c.next("init")
|
||||
c.ws.send(JSON.stringify({ type: "message", content: "做个页面" }))
|
||||
await c.next("complete")
|
||||
|
||||
const saved = await stu("POST", "/api/prompt/versions", {
|
||||
task_id: task,
|
||||
html: "<p>我改过的</p>",
|
||||
css: "p { color: red }",
|
||||
js: "",
|
||||
})
|
||||
expect(saved.json.source).toBe("edit")
|
||||
|
||||
c.ws.send(JSON.stringify({ type: "message", content: "再加个按钮" }))
|
||||
await c.next("complete")
|
||||
const sent = lastRequest.messages.map((m: any) => `${m.role}:${m.content}`)
|
||||
// system、第一轮、手改那一轮、这一轮的提问
|
||||
expect(sent.at(-3)).toBe("user:(手动修改了代码)")
|
||||
expect(sent.at(-2)).toContain("<p>我改过的</p>")
|
||||
expect(sent.at(-2)).toContain("p { color: red }")
|
||||
expect(sent.at(-1)).toBe("user:再加个按钮")
|
||||
c.ws.close()
|
||||
})
|
||||
|
||||
test("token 无效或任务不存在时握手失败", async () => {
|
||||
const { task, token } = await setup()
|
||||
expect(await connect(`/ws/prompt/${task}/?token=bad`)).toBeNull()
|
||||
|
||||
Reference in new issue
Block a user