diff --git a/src/app/api/ai/chat/route.ts b/src/app/api/ai/chat/route.ts index e2d107f..5a68901 100644 --- a/src/app/api/ai/chat/route.ts +++ b/src/app/api/ai/chat/route.ts @@ -73,13 +73,22 @@ function transformToSSE( return new ReadableStream({ async start(controller) { + let buffer = ''; // 缓冲区,用于保存不完整的行 try { while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); - const lines = chunk.split('\n').filter((line) => line.trim() !== ''); + // 将新chunk与缓冲区拼接 + const text = buffer + chunk; + // 按换行符分割,最后一个元素可能是不完整的行 + const parts = text.split('\n'); + // 保存最后一个不完整的行到缓冲区 + buffer = parts.pop() || ''; + + // 处理完整的行 + const lines = parts.filter((line) => line.trim() !== ''); for (const line of lines) { if (line.startsWith('data: ')) { @@ -126,6 +135,34 @@ function transformToSSE( } } } + + // 处理缓冲区中剩余的数据 + if (buffer.trim()) { + const line = buffer.trim(); + if (line.startsWith('data: ')) { + const data = line.slice(6).trim(); + if (data && data !== '[DONE]') { + try { + const json = JSON.parse(data); + let text = ''; + if (provider === 'claude') { + if (json.type === 'content_block_delta') { + text = json.delta?.text || ''; + } + } else { + text = json.choices?.[0]?.delta?.content || ''; + } + if (text) { + controller.enqueue( + new TextEncoder().encode(`data: ${JSON.stringify({ text })}\n\n`) + ); + } + } catch (e) { + console.error('Parse final buffer error:', e); + } + } + } + } } catch (error) { console.error('Stream error:', error); controller.error(error); diff --git a/src/components/AIChatPanel.tsx b/src/components/AIChatPanel.tsx index ddaf4a9..9299b1a 100644 --- a/src/components/AIChatPanel.tsx +++ b/src/components/AIChatPanel.tsx @@ -211,13 +211,22 @@ export default function AIChatPanel({ } let assistantMessage = ''; + let buffer = ''; // 缓冲区,用于保存不完整的行 while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); - const lines = chunk.split('\n').filter((line) => line.trim() !== ''); + // 将新chunk与缓冲区拼接 + const text = buffer + chunk; + // 按换行符分割,最后一个元素可能是不完整的行 + const parts = text.split('\n'); + // 保存最后一个不完整的行到缓冲区 + buffer = parts.pop() || ''; + + // 处理完整的行 + const lines = parts.filter((line) => line.trim() !== ''); for (const line of lines) { if (line.startsWith('data: ')) { @@ -250,6 +259,33 @@ export default function AIChatPanel({ } } } + + // 处理缓冲区中剩余的数据 + if (buffer.trim()) { + const line = buffer.trim(); + if (line.startsWith('data: ')) { + const data = line.slice(6); + if (data && data !== '[DONE]') { + try { + const json = JSON.parse(data); + const text = json.text || ''; + if (text) { + assistantMessage += text; + setMessages((prev) => { + const newMessages = [...prev]; + newMessages[newMessages.length - 1] = { + role: 'assistant', + content: assistantMessage, + }; + return newMessages; + }); + } + } catch (e) { + console.error('解析最终缓冲区数据失败:', e); + } + } + } + } } else { // 处理非流式响应 const data = await response.json(); diff --git a/src/lib/ai-orchestrator.ts b/src/lib/ai-orchestrator.ts index 9238c9c..66b90a2 100644 --- a/src/lib/ai-orchestrator.ts +++ b/src/lib/ai-orchestrator.ts @@ -346,14 +346,19 @@ function formatSearchResults( * 清理可能被代码块包裹的JSON字符串 */ function cleanJsonResponse(content: string): string { - // 去除可能的markdown代码块标记 let cleaned = content.trim(); - // 移除 ```json 或 ``` 开头 - cleaned = cleaned.replace(/^```(?:json)?\s*\n?/i, ''); - - // 移除 ``` 结尾 - cleaned = cleaned.replace(/\n?```\s*$/, ''); + // 尝试提取代码块中的内容(支持前面有说明文字的情况) + const codeBlockMatch = cleaned.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/i); + if (codeBlockMatch) { + cleaned = codeBlockMatch[1].trim(); + } else { + // 如果没有代码块,尝试提取第一个 { 到最后一个 } 之间的内容 + const jsonMatch = cleaned.match(/\{[\s\S]*\}/); + if (jsonMatch) { + cleaned = jsonMatch[0]; + } + } return cleaned.trim(); } @@ -690,8 +695,15 @@ export async function orchestrateDataSources( } // 4. 构建系统提示词 + // 获取UTC+8时区的日期 + const now = new Date(); + const utc8Date = new Date(now.getTime() + 8 * 60 * 60 * 1000); + const today = utc8Date.toISOString().split('T')[0]; // YYYY-MM-DD格式 let systemPrompt = `你是 MoonTVPlus 的 AI 影视助手,专门帮助用户发现和了解影视内容。 +## 当前日期 +${today} + ## 你的能力 - 提供影视推荐(基于豆瓣热门榜单和TMDB数据) - 回答影视相关问题(剧情、演员、评分等)