Skip to content

Commit a2ca2ee

Browse files
committed
feat: 添加流式响应支持
## 新增功能 1. **流式聊天控制器**: StreamingAIChatController.java - 支持SSE (Server-Sent Events) 流式响应 - 添加/chat/stream端点用于流式聊天 - 添加/chat/stream/test端点用于测试 - 添加/chat/compatible兼容端点 2. **前端流式支持**: - 更新frontend-config.js添加流式端点配置 - 更新前端HTML支持流式响应显示 - 添加打字指示器和流式文本动画 - 实现流式响应回退机制 3. **技术特性**: - 使用HttpURLConnection处理流式HTTP请求 - 实现SSE事件流解析 - 支持实时文本逐字显示 - 保持向后兼容性 ## 端点说明 - - 流式聊天 (SSE) - - 流式测试 - - 兼容端点 - 原有端点保持不变,确保向后兼容 ## 前端改进 - 实时显示AI回复过程 - 添加打字动画效果 - 流式响应失败时自动回退 - 优化用户体验
1 parent 19ee084 commit a2ca2ee

File tree

3 files changed

+942
-2
lines changed

3 files changed

+942
-2
lines changed

frontend-config.js

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
const config = {
33
API_BASE_URL: process.env.REACT_APP_API_URL || 'http://81.70.234.241:8080/api/v1',
44
ENDPOINTS: {
5-
CHAT: '/chat/text',
5+
CHAT: '/chat/text', // 非流式聊天
6+
CHAT_STREAM: '/chat/stream', // 流式聊天
7+
CHAT_COMPATIBLE: '/chat/compatible', // 兼容端点
8+
CHAT_TEST: '/chat/stream/test', // 流式测试
69
PING: '/ping',
710
STATUS: '/status',
811
ECHO: '/echo'
@@ -14,7 +17,87 @@ const getApiUrl = (endpoint) => {
1417
return `${config.API_BASE_URL}${config.ENDPOINTS[endpoint] || endpoint}`;
1518
};
1619

20+
// 流式响应处理器
21+
class StreamHandler {
22+
constructor(options = {}) {
23+
this.onChunk = options.onChunk || (() => {});
24+
this.onComplete = options.onComplete || (() => {});
25+
this.onError = options.onError || (() => {});
26+
this.fullResponse = '';
27+
}
28+
29+
// 处理SSE流
30+
async processStream(url, data) {
31+
try {
32+
const response = await fetch(url, {
33+
method: 'POST',
34+
headers: {
35+
'Content-Type': 'application/json',
36+
},
37+
body: JSON.stringify(data)
38+
});
39+
40+
if (!response.ok) {
41+
throw new Error(`HTTP error! status: ${response.status}`);
42+
}
43+
44+
const reader = response.body.getReader();
45+
const decoder = new TextDecoder('utf-8');
46+
let buffer = '';
47+
48+
while (true) {
49+
const { done, value } = await reader.read();
50+
51+
if (done) {
52+
this.onComplete(this.fullResponse);
53+
break;
54+
}
55+
56+
buffer += decoder.decode(value, { stream: true });
57+
const lines = buffer.split('\n');
58+
buffer = lines.pop() || '';
59+
60+
for (const line of lines) {
61+
if (line.startsWith('data: ')) {
62+
const dataStr = line.substring(6);
63+
64+
if (dataStr === '[DONE]') {
65+
this.onComplete(this.fullResponse);
66+
return;
67+
}
68+
69+
try {
70+
const eventData = JSON.parse(dataStr);
71+
72+
if (eventData.chunk) {
73+
this.fullResponse += eventData.chunk;
74+
this.onChunk(eventData.chunk, this.fullResponse, eventData);
75+
} else if (eventData.error) {
76+
this.onError(eventData.error);
77+
} else if (eventData.complete) {
78+
this.onComplete(this.fullResponse);
79+
}
80+
} catch (e) {
81+
console.warn('解析流式数据失败:', e, dataStr);
82+
}
83+
}
84+
}
85+
}
86+
} catch (error) {
87+
this.onError(error.message);
88+
}
89+
}
90+
91+
// 简单的流式请求
92+
static async simpleStream(url, data, onChunk) {
93+
const handler = new StreamHandler({ onChunk });
94+
await handler.processStream(url, data);
95+
return handler.fullResponse;
96+
}
97+
}
98+
1799
export default {
18100
...config,
19-
getApiUrl
101+
getApiUrl,
102+
StreamHandler
20103
};

0 commit comments

Comments
 (0)