@@ -26,8 +26,65 @@ interface ChatRequest {
2626 chatId ?: string ;
2727}
2828
29+ import { resolveUserId } from "@/lib/server-auth" ;
30+
2931export async function POST ( req : Request ) {
32+ // 1. 克隆请求,因为如果代理失败,后面的代码还需要读取 req.json()
33+ const proxyReq = req . clone ( ) ;
34+
35+ // ====== 尝试优雅降级代理到 Java 后端 ======
36+ try {
37+ const backendUrl = process . env . BACKEND_URL ;
38+ if ( ! backendUrl ) throw new Error ( "BACKEND_URL is not configured." ) ;
39+ const controller = new AbortController ( ) ;
40+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 5000 ) ; // 5秒超时
41+
42+ // 原封不动把前端的参数丢给 Java
43+ let proxyRes : Response ;
44+ try {
45+ proxyRes = await fetch ( `${ backendUrl } /openai/responses/stream` , {
46+ method : "POST" ,
47+ headers : {
48+ "Content-Type" : "application/json" ,
49+ // 浏览器侧用 x-satoken 传递 token,转发给后端时改回后端期望的 satoken
50+ ...( req . headers . get ( "x-satoken" )
51+ ? { satoken : req . headers . get ( "x-satoken" ) ! }
52+ : { } ) ,
53+ } ,
54+ body : await proxyReq . text ( ) ,
55+ signal : controller . signal ,
56+ } ) ;
57+ } finally {
58+ // 无论成功还是抛出(网络错误/超时中断),都清除定时器
59+ clearTimeout ( timeoutId ) ;
60+ }
61+
62+ // 如果 Java 后端返回成功,则直接把它的流传回浏览器,提前结束
63+ if ( proxyRes . ok && proxyRes . body ) {
64+ console . log (
65+ "[Chat Fallback Proxy] 🚀 Java Backend responded successfully. Piping stream..." ,
66+ ) ;
67+ return new Response ( proxyRes . body , {
68+ headers : {
69+ "Content-Type" :
70+ proxyRes . headers . get ( "Content-Type" ) || "text/plain; charset=utf-8" ,
71+ } ,
72+ } ) ;
73+ } else {
74+ console . warn (
75+ `[Chat Fallback Proxy] ⚠️ Java Backend returned status: ${ proxyRes . status } , fallback to local Next.js inference.` ,
76+ ) ;
77+ }
78+ } catch ( error ) {
79+ console . warn (
80+ `[Chat Fallback Proxy] ❌ Java Backend unavailable or timed out, fallback to local Next.js inference. Error:` ,
81+ error ,
82+ ) ;
83+ }
84+ // ====== 代理失败,继续往下走,启用备选方案(本地直连 AI)======
85+
3086 try {
87+ // 先把 body 消费掉,再并行验证用户身份
3188 const {
3289 messages,
3390 system,
@@ -37,6 +94,9 @@ export async function POST(req: Request) {
3794 chatId,
3895 } : ChatRequest = await req . json ( ) ;
3996
97+ // 并行解析用户身份(不阻塞主流程,失败静默降级为匿名)
98+ const userIdPromise = resolveUserId ( req ) ;
99+
40100 // 对指定Provider验证key是否存在
41101 if ( requiresApiKey ( provider ) && ( ! apiKey || apiKey . trim ( ) === "" ) ) {
42102 return Response . json (
@@ -90,22 +150,30 @@ export async function POST(req: Request) {
90150 // 根据Provider获取 AI 模型实例
91151 const model = getModel ( provider , apiKey ) ;
92152
93- // 确保有 chatId (如果前端没传,就生成一个临时的,虽然这会导致每次请求都是新会话)
94- // 理想情况是前端应该维护 chatId
95153 const effectiveChatId = chatId || crypto . randomUUID ( ) ;
96154
97155 // 生成流式响应
98156 const result = streamText ( {
99157 model : model ,
100158 system : systemMessage ,
101- messages : convertToModelMessages ( messages || [ ] ) ,
159+ messages : await convertToModelMessages ( messages || [ ] ) ,
102160 onFinish : async ( { text } ) => {
103161 try {
104- // 1. 保存/更新会话
162+ // 等待用户身份解析(与流式传输并行运行,此时大概率已完成)
163+ const userId = await userIdPromise ;
164+
165+ // 1. 保存/更新会话,绑定用户 ID
166+ // update 也写入 userId:覆盖此前匿名创建的记录(用户登录后继续同一 chatId)
105167 await prisma . chat . upsert ( {
106168 where : { id : effectiveChatId } ,
107- update : { updatedAt : new Date ( ) } ,
108- create : { id : effectiveChatId } ,
169+ update : {
170+ updatedAt : new Date ( ) ,
171+ ...( userId != null && { userId } ) ,
172+ } ,
173+ create : {
174+ id : effectiveChatId ,
175+ ...( userId != null && { userId } ) ,
176+ } ,
109177 } ) ;
110178
111179 // 2. 保存用户消息 (取最后一条)
0 commit comments