|
1 | | -import { prisma } from "@/lib/db"; |
2 | 1 | import { streamText, UIMessage, convertToModelMessages } from "ai"; |
3 | 2 | import { getModel, requiresApiKey, type AIProvider } from "@/lib/ai/models"; |
4 | 3 | import { buildSystemMessage } from "@/lib/ai/prompt"; |
@@ -187,55 +186,59 @@ export async function POST(req: Request) { |
187 | 186 | system: systemMessage, |
188 | 187 | messages: await convertToModelMessages(messages || []), |
189 | 188 | onFinish: async ({ text }) => { |
| 189 | + // 2026-04-17 起对话历史改由后端 /api/chat/sessions/save 统一持久化(事务保证 |
| 190 | + // Chat + Message 一起落库)。原本 prisma 直连 Neon 的方案在 Neon → 自建 PG |
| 191 | + // 切换后会产生前后端双写不同库的脏数据,所以整条路径下掉。 |
190 | 192 | try { |
191 | | - // 等待用户身份解析(与流式传输并行运行,此时大概率已完成) |
192 | | - const userId = await userIdPromise; |
193 | | - |
194 | | - // 1. 保存/更新会话,绑定用户 ID |
195 | | - // update 也写入 userId:覆盖此前匿名创建的记录(用户登录后继续同一 chatId) |
196 | | - await prisma.chat.upsert({ |
197 | | - where: { id: effectiveChatId }, |
198 | | - update: { |
199 | | - updatedAt: new Date(), |
200 | | - ...(userId != null && { userId }), |
201 | | - }, |
202 | | - create: { |
203 | | - id: effectiveChatId, |
204 | | - ...(userId != null && { userId }), |
205 | | - }, |
206 | | - }); |
| 193 | + const backendUrl = process.env.BACKEND_URL; |
| 194 | + if (!backendUrl) { |
| 195 | + console.warn( |
| 196 | + "[Chat History] BACKEND_URL 未配置,跳过持久化(不阻塞流式返回)", |
| 197 | + ); |
| 198 | + return; |
| 199 | + } |
207 | 200 |
|
208 | | - // 2. 保存用户消息 (取最后一条) |
209 | | - // AI SDK v5 中,UIMessage 不再有 content 字段,内容在 parts 数组中 |
| 201 | + // 从 parts 数组中提取最后一条 user 消息的纯文本;AI SDK v5 没有 content |
| 202 | + // 字段,需要自己拼。空消息(role 不是 user 或 parts 为空)就传 null, |
| 203 | + // 后端看到 null/空串会跳过插入,语义和原 Prisma 版 if 判断保持一致。 |
210 | 204 | const safeMessages = messages || []; |
211 | 205 | const lastUserMessage = safeMessages[safeMessages.length - 1]; |
212 | | - if (lastUserMessage && lastUserMessage.role === "user") { |
213 | | - // 从 parts 数组中提取所有文本内容并拼接 |
214 | | - const userContent = Array.isArray(lastUserMessage.parts) |
215 | | - ? lastUserMessage.parts |
216 | | - .filter((part) => part.type === "text") |
217 | | - .map((part) => (part as { type: "text"; text: string }).text) |
218 | | - .join("\n") |
219 | | - : (lastUserMessage as unknown as { content?: string })?.content || |
220 | | - ""; |
221 | | - |
222 | | - await prisma.message.create({ |
223 | | - data: { |
224 | | - chatId: effectiveChatId, |
225 | | - role: "user", |
226 | | - content: userContent, |
227 | | - }, |
228 | | - }); |
229 | | - } |
230 | | - |
231 | | - // 3. 保存 AI 回复 |
232 | | - await prisma.message.create({ |
233 | | - data: { |
234 | | - chatId: effectiveChatId, |
235 | | - role: "assistant", |
236 | | - content: text, |
| 206 | + const userContent = |
| 207 | + lastUserMessage && lastUserMessage.role === "user" |
| 208 | + ? Array.isArray(lastUserMessage.parts) |
| 209 | + ? lastUserMessage.parts |
| 210 | + .filter((part) => part.type === "text") |
| 211 | + .map( |
| 212 | + (part) => (part as { type: "text"; text: string }).text, |
| 213 | + ) |
| 214 | + .join("\n") |
| 215 | + : (lastUserMessage as unknown as { content?: string }) |
| 216 | + ?.content || "" |
| 217 | + : ""; |
| 218 | + |
| 219 | + // 后端用 sa-token 从 header 取 userId 关联会话;匿名请求不带 satoken |
| 220 | + // 时后端自动把 userId 置 NULL,行为与原 prisma.chat.upsert 一致。 |
| 221 | + // 静默 await 用户解析(原代码 await userIdPromise,这里保持阻塞等待 |
| 222 | + // 以确保后端鉴权不会错用过期数据;失败已在 resolveUserId 内降级)。 |
| 223 | + await userIdPromise; |
| 224 | + |
| 225 | + const resp = await fetch(`${backendUrl}/api/chat/sessions/save`, { |
| 226 | + method: "POST", |
| 227 | + headers: { |
| 228 | + "Content-Type": "application/json", |
| 229 | + ...(satoken ? { satoken } : {}), |
237 | 230 | }, |
| 231 | + body: JSON.stringify({ |
| 232 | + chatId: effectiveChatId, |
| 233 | + userMessage: userContent, |
| 234 | + assistantMessage: text, |
| 235 | + }), |
238 | 236 | }); |
| 237 | + if (!resp.ok) { |
| 238 | + console.warn( |
| 239 | + `[Chat History] backend save returned ${resp.status}, history may be lost for chat ${effectiveChatId}`, |
| 240 | + ); |
| 241 | + } |
239 | 242 | } catch (error) { |
240 | 243 | console.error("Failed to save chat history:", error); |
241 | 244 | } |
|
0 commit comments