|
|
@@ -50,8 +50,8 @@ export class ConversationService {
|
|
50
|
50
|
// 创建AbortController用于取消请求
|
|
51
|
51
|
const abortController = new AbortController();
|
|
52
|
52
|
|
|
53
|
|
- // 心跳超时定时器(5分钟)- 用于清理函数访问
|
|
54
|
|
- let heartbeatTimeout: any = null;
|
|
|
53
|
+ // 发送超时定时器 - 控制发送消息到后端的超时(30秒)
|
|
|
54
|
+ let sendTimeout: any = null;
|
|
55
|
55
|
|
|
56
|
56
|
// 使用fetch API以便流式读取SSE响应
|
|
57
|
57
|
fetch('/api/prompt/stream', {
|
|
|
@@ -85,39 +85,27 @@ export class ConversationService {
|
|
85
|
85
|
const decoder = new TextDecoder();
|
|
86
|
86
|
let buffer = '';
|
|
87
|
87
|
|
|
88
|
|
- // 心跳超时定时器(5分钟)- 如果5分钟内未收到任何数据(包括心跳),则认为连接已断开
|
|
89
|
|
- // 使用外层声明的heartbeatTimeout变量
|
|
90
|
|
- const HEARTBEAT_TIMEOUT_MS = 5 * 60 * 1000; // 5分钟
|
|
|
88
|
+ // 发送请求超时(30秒)- 控制发送消息到后端的超时
|
|
|
89
|
+ const SEND_TIMEOUT_MS = 30 * 1000; // 30秒
|
|
|
90
|
+ sendTimeout = setTimeout(() => {
|
|
|
91
|
+ console.error('🔍 [ConversationService] 发送请求超时(30秒未收到初始响应)');
|
|
|
92
|
+ abortController.abort();
|
|
|
93
|
+ this.streamUpdateSubject.next({ type: 'error', data: '发送请求超时,请重试' });
|
|
|
94
|
+ observer.error(new Error('发送请求超时,请重试'));
|
|
|
95
|
+ }, SEND_TIMEOUT_MS);
|
|
91
|
96
|
|
|
92
|
|
- const resetHeartbeatTimeout = () => {
|
|
93
|
|
- // 清除现有定时器
|
|
94
|
|
- if (heartbeatTimeout) {
|
|
95
|
|
- clearTimeout(heartbeatTimeout);
|
|
96
|
|
- }
|
|
97
|
|
- // 设置新的超时定时器
|
|
98
|
|
- heartbeatTimeout = setTimeout(() => {
|
|
99
|
|
- console.error('🔍 [ConversationService] 心跳超时(5分钟未收到数据),主动断开连接');
|
|
100
|
|
- abortController.abort();
|
|
101
|
|
- this.streamUpdateSubject.next({ type: 'error', data: '连接超时,请重试' });
|
|
102
|
|
- observer.error(new Error('连接超时,请重试'));
|
|
103
|
|
- }, HEARTBEAT_TIMEOUT_MS);
|
|
104
|
|
- };
|
|
105
|
|
-
|
|
106
|
|
- // 初始设置心跳超时定时器
|
|
107
|
|
- resetHeartbeatTimeout();
|
|
|
97
|
+ // 请求成功后清除发送超时
|
|
|
98
|
+ clearTimeout(sendTimeout);
|
|
108
|
99
|
|
|
109
|
100
|
const readStream = () => {
|
|
110
|
101
|
reader.read().then(({ done, value }) => {
|
|
111
|
|
- if (done) {
|
|
112
|
|
- console.log('🔍 [ConversationService] 流结束');
|
|
113
|
|
- // 清除心跳超时定时器
|
|
114
|
|
- if (heartbeatTimeout) {
|
|
115
|
|
- clearTimeout(heartbeatTimeout);
|
|
116
|
|
- heartbeatTimeout = null;
|
|
117
|
|
- }
|
|
118
|
|
- this.streamUpdateSubject.next({ type: 'done', data: '' });
|
|
119
|
|
- observer.complete();
|
|
120
|
|
- return;
|
|
|
102
|
+ if (done) {
|
|
|
103
|
+ console.log('🔍 [ConversationService] 流结束');
|
|
|
104
|
+ // 清除发送超时定时器
|
|
|
105
|
+ clearTimeout(sendTimeout);
|
|
|
106
|
+ this.streamUpdateSubject.next({ type: 'done', data: '' });
|
|
|
107
|
+ observer.complete();
|
|
|
108
|
+ return;
|
|
121
|
109
|
}
|
|
122
|
110
|
|
|
123
|
111
|
// 解码数据
|
|
|
@@ -129,8 +117,7 @@ export class ConversationService {
|
|
129
|
117
|
const event = buffer.substring(0, eventEnd);
|
|
130
|
118
|
buffer = buffer.substring(eventEnd + 2); // 移除已处理的事件和两个换行符
|
|
131
|
119
|
|
|
132
|
|
- // 重置心跳超时(收到任何事件,包括注释)
|
|
133
|
|
- resetHeartbeatTimeout();
|
|
|
120
|
+ // 收到事件,继续处理
|
|
134
|
121
|
|
|
135
|
122
|
// 检查是否为注释行(以冒号开头)
|
|
136
|
123
|
if (event.startsWith(':')) {
|
|
|
@@ -147,10 +134,12 @@ export class ConversationService {
|
|
147
|
134
|
console.log('🔍 [ConversationService] 收到SSE数据:', data.substring(0, 100));
|
|
148
|
135
|
|
|
149
|
136
|
if (data === '[DONE]') {
|
|
150
|
|
- console.log('🔍 [ConversationService] 收到DONE标记');
|
|
151
|
|
- this.streamUpdateSubject.next({ type: 'done', data: '' });
|
|
152
|
|
- observer.complete();
|
|
153
|
|
- return;
|
|
|
137
|
+ console.log('🔍 [ConversationService] 收到DONE标记');
|
|
|
138
|
+ // 清除发送超时定时器
|
|
|
139
|
+ clearTimeout(sendTimeout);
|
|
|
140
|
+ this.streamUpdateSubject.next({ type: 'done', data: '' });
|
|
|
141
|
+ observer.complete();
|
|
|
142
|
+ return;
|
|
154
|
143
|
} else {
|
|
155
|
144
|
try {
|
|
156
|
145
|
// 解析JSON格式的SSE数据
|
|
|
@@ -165,13 +154,24 @@ export class ConversationService {
|
|
165
|
154
|
// 处理消息部分更新事件(包含文本内容)
|
|
166
|
155
|
if (payload.type === 'message.part.updated' && payload.properties?.part) {
|
|
167
|
156
|
const part = payload.properties.part;
|
|
168
|
|
- // 支持 text 和 reasoning 类型
|
|
169
|
|
- if ((part.type === 'text' || part.type === 'reasoning') && part.text) {
|
|
170
|
|
- // 优先使用 delta 字段(增量),如果没有则使用完整文本
|
|
171
|
|
- const delta = payload.properties.delta || part.text;
|
|
172
|
|
- console.log('🔍 [ConversationService] 收到部分内容 (类型:', part.type, 'delta:', delta, '):', part.text.substring(0, 50));
|
|
173
|
|
- this.streamUpdateSubject.next({ type: 'text', data: delta });
|
|
174
|
|
- }
|
|
|
157
|
+ // 支持 text、reasoning 和 tool 类型
|
|
|
158
|
+ if ((part.type === 'text' || part.type === 'reasoning' || part.type === 'tool') && part.text) {
|
|
|
159
|
+ // 优先使用 delta 字段(增量),如果没有则使用完整文本
|
|
|
160
|
+ const delta = payload.properties.delta || part.text;
|
|
|
161
|
+
|
|
|
162
|
+ // 映射事件类型到前端消息类型
|
|
|
163
|
+ let frontendType: 'thinking' | 'tool' | 'reply' | 'error';
|
|
|
164
|
+ if (part.type === 'reasoning') {
|
|
|
165
|
+ frontendType = 'thinking';
|
|
|
166
|
+ } else if (part.type === 'tool') {
|
|
|
167
|
+ frontendType = 'tool';
|
|
|
168
|
+ } else {
|
|
|
169
|
+ frontendType = 'reply'; // text 类型
|
|
|
170
|
+ }
|
|
|
171
|
+
|
|
|
172
|
+ console.log('🔍 [ConversationService] 收到部分内容 (类型:', part.type, '=>', frontendType, 'delta:', delta, '):', part.text.substring(0, 50));
|
|
|
173
|
+ this.streamUpdateSubject.next({ type: frontendType, data: delta });
|
|
|
174
|
+ }
|
|
175
|
175
|
}
|
|
176
|
176
|
// 处理消息更新事件(包含完整消息信息)
|
|
177
|
177
|
else if (payload.type === 'message.updated' && payload.properties?.info) {
|
|
|
@@ -197,8 +197,8 @@ export class ConversationService {
|
|
197
|
197
|
}
|
|
198
|
198
|
} catch (e) {
|
|
199
|
199
|
console.error('🔍 [ConversationService] 解析SSE JSON数据失败:', e, '原始数据:', data);
|
|
200
|
|
- // 如果不是JSON,按纯文本处理
|
|
201
|
|
- this.streamUpdateSubject.next({ type: 'text', data });
|
|
|
200
|
+ // 如果不是JSON,按纯文本处理
|
|
|
201
|
+ this.streamUpdateSubject.next({ type: 'reply', data });
|
|
202
|
202
|
}
|
|
203
|
203
|
}
|
|
204
|
204
|
}
|
|
|
@@ -236,17 +236,14 @@ export class ConversationService {
|
|
236
|
236
|
}
|
|
237
|
237
|
});
|
|
238
|
238
|
|
|
239
|
|
- // 清理函数
|
|
240
|
|
- return () => {
|
|
241
|
|
- console.log('🔍 [ConversationService] 清理流式连接,取消请求');
|
|
242
|
|
- // 清除心跳超时定时器
|
|
243
|
|
- if (heartbeatTimeout) {
|
|
244
|
|
- clearTimeout(heartbeatTimeout);
|
|
245
|
|
- heartbeatTimeout = null;
|
|
246
|
|
- }
|
|
247
|
|
- // 取消fetch请求
|
|
248
|
|
- abortController.abort();
|
|
249
|
|
- };
|
|
|
239
|
+ // 清理函数
|
|
|
240
|
+ return () => {
|
|
|
241
|
+ console.log('🔍 [ConversationService] 清理流式连接,取消请求');
|
|
|
242
|
+ // 清除发送超时定时器
|
|
|
243
|
+ clearTimeout(sendTimeout);
|
|
|
244
|
+ // 取消fetch请求
|
|
|
245
|
+ abortController.abort();
|
|
|
246
|
+ };
|
|
250
|
247
|
});
|
|
251
|
248
|
}
|
|
252
|
249
|
|