From 9fee688d95bc8daa8ef69b177cc5bff98d559c92 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Tue, 27 Oct 2026 21:13:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20AI=20=E5=AF=B9=E8=AF=9D=E5=92=8C?= =?UTF-8?q?=E5=BC=95=E5=AF=BC=E9=87=8C=E7=9A=84=E6=8F=90=E9=97=AE=E5=9B=9E?= =?UTF-8?q?=E5=A4=8D=E5=AE=8C=E5=90=8E=E8=87=AA=E5=8A=A8=E6=89=93=E5=B1=82?= =?UTF-8?q?=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧后端在消息保存后自动分类,改写成 Bun 时漏了:只有外部 AI 版本和手动提交带的提示词会分类, 学生在 AI 对话和引导里发的提问都没有层级。现在回复存库后在后台分类(失败或断开的提问不分类)。 Co-Authored-By: Claude Opus 5.5 --- server/src/ws.ts | 10 ++++++++-- server/tests/ws.test.ts | 23 +++++++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/server/src/ws.ts b/server/src/ws.ts index a9b90b1..bb573f4 100644 --- a/server/src/ws.ts +++ b/server/src/ws.ts @@ -15,6 +15,7 @@ import { userFacingError, type ChatMessage, } from "./services/llm" +import { classifyMessage, inBackground } from "./services/classifier" type Kind = "prompt" | "guidance" @@ -116,11 +117,12 @@ async function handlePrompt(ws: WS, data: any) { const model = String(data.model ?? "") if (!prompt) return - ws.data.pendingUserMessageId = insertMessage({ + const userMessageId = insertMessage({ conversation_id: ws.data.conversationId, role: "user", content: prompt, }) + ws.data.pendingUserMessageId = userMessageId try { const history: ChatMessage[] = conversationHistory(ws.data.conversationId).map((m) => ({ role: m.role as ChatMessage["role"], @@ -146,6 +148,8 @@ async function handlePrompt(ws: WS, data: any) { code_css: code.css, code_js: code.js, }) + // 有了回复,这条提问才留下来;在后台打 L1–L6 层级 + inBackground(classifyMessage(userMessageId)) ws.data.pendingUserMessageId = null send(ws, { type: "complete", code, message_id: messageId }) } finally { @@ -157,12 +161,13 @@ async function handleGuidance(ws: WS, data: any) { const prompt = String(data.content ?? "").trim() if (!prompt) return - ws.data.pendingUserMessageId = insertMessage({ + const userMessageId = insertMessage({ conversation_id: ws.data.conversationId, role: "user", content: prompt, source: "guidance", }) + ws.data.pendingUserMessageId = userMessageId ws.data.guidanceHistory.push({ role: "user", content: prompt }) try { let full: string @@ -185,6 +190,7 @@ async function handleGuidance(ws: WS, data: any) { content: clean, source: "guidance", }) + inBackground(classifyMessage(userMessageId)) ws.data.pendingUserMessageId = null send(ws, { type: "complete", message_id: messageId, is_ready: isReady }) } finally { diff --git a/server/tests/ws.test.ts b/server/tests/ws.test.ts index 94f0b41..6cc17db 100644 --- a/server/tests/ws.test.ts +++ b/server/tests/ws.test.ts @@ -8,12 +8,18 @@ import { client, freshDb, makeTask, makeUser } from "./helpers" // 假的模型接口:每段之间停一下,方便测试中途断开 const PIECES = ["好的", "```html\n

hi

\n```"] -// 最近一次发给模型的请求 +// 最近一次发给模型的请求;分类(非流式)单独记,固定回“3” let lastRequest: any = null +let lastClassify: any = null const llm = Bun.serve({ port: 0, async fetch(req) { - lastRequest = await req.json() + const body: any = await req.json() + if (!body.stream) { + lastClassify = body + return Response.json({ choices: [{ message: { content: "3" } }] }) + } + lastRequest = body const enc = new TextEncoder() const stream = new ReadableStream({ async start(ctrl) { @@ -106,6 +112,19 @@ describe("WebSocket 代码生成", () => { c.ws.close() }) + test("回复完后在后台给提问打层级", async () => { + const { task, token } = await setup() + const c = (await connect(`/ws/prompt/${task}/?token=${token}`))! + await c.next("init") + c.ws.send(JSON.stringify({ type: "message", content: "做个页面" })) + await c.next("complete") + const level = () => all(`SELECT prompt_level FROM prompt_message WHERE role = 'user'`)[0]?.prompt_level + for (let i = 0; i < 50 && level() == null; i++) await Bun.sleep(20) + expect(level()).toBe(3) + expect(lastClassify.thinking).toEqual({ type: "disabled" }) + c.ws.close() + }) + test("AI 回复中再发消息会被拒绝", async () => { const { task, token } = await setup() const c = (await connect(`/ws/prompt/${task}/?token=${token}`))!