@kernelift/ai-chat 2.1.1 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +72 -62
- package/SSE-Client.md +492 -492
- package/dist/ai-chat.css +1 -1
- package/dist/index.d.ts +3 -6
- package/dist/index.js +641 -637
- package/package.json +1 -1
package/SSE-Client.md
CHANGED
|
@@ -1,492 +1,492 @@
|
|
|
1
|
-
# SSEClient 使用参考手册
|
|
2
|
-
|
|
3
|
-
## 目录
|
|
4
|
-
|
|
5
|
-
- [概述](#概述)
|
|
6
|
-
- [安装与导入](#安装与导入)
|
|
7
|
-
- [API 参考](#api-参考)
|
|
8
|
-
- [事件处理](#事件处理)
|
|
9
|
-
- [使用示例](#使用示例)
|
|
10
|
-
- [错误处理](#错误处理)
|
|
11
|
-
- [注意事项](#注意事项)
|
|
12
|
-
|
|
13
|
-
## 概述
|
|
14
|
-
|
|
15
|
-
`SSEClient` 是一个用于处理服务器发送事件(Server-Sent Events)的客户端类,支持流式数据传输,特别适用于实时内容更新、工具调用状态监控等场景。
|
|
16
|
-
|
|
17
|
-
### 主要特性
|
|
18
|
-
|
|
19
|
-
- 🔄 **流式数据处理** - 实时处理分块传输的内容
|
|
20
|
-
- 🛠️ **工具调用监控** - 跟踪工具调用的增量更新
|
|
21
|
-
- ⚡ **事件驱动** - 基于事件的处理机制
|
|
22
|
-
- 🚫 **错误处理** - 完善的错误处理机制
|
|
23
|
-
- 🔗 **连接管理** - 支持主动断开连接
|
|
24
|
-
|
|
25
|
-
## 安装与导入
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
import { SSEClient } from './sse-client';
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## API 参考
|
|
32
|
-
|
|
33
|
-
### 构造函数
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
new SSEClient(token: string, baseUrl: string, method?: 'POST' | 'GET')
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
**参数:**
|
|
40
|
-
|
|
41
|
-
- `token` (string) - 认证令牌,用于 Authorization 头
|
|
42
|
-
- `baseUrl` (string) - 基础 URL
|
|
43
|
-
- `method` ('POST' | 'GET', 可选) - 请求方法,默认为 `'GET'`
|
|
44
|
-
|
|
45
|
-
**示例:**
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
// GET 请求(默认)
|
|
49
|
-
const client = new SSEClient('your-auth-token', 'https://api.example.com');
|
|
50
|
-
|
|
51
|
-
// POST 请求
|
|
52
|
-
const client = new SSEClient('your-auth-token', 'https://api.example.com', 'POST');
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### 方法
|
|
56
|
-
|
|
57
|
-
#### connect()
|
|
58
|
-
|
|
59
|
-
建立 SSE 连接并开始处理事件流。
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
async connect(
|
|
63
|
-
url: string,
|
|
64
|
-
handlers: {
|
|
65
|
-
onMessage: (message: string) => void;
|
|
66
|
-
onContent: (content: string) => void;
|
|
67
|
-
onToolCallDelta: (data: any) => void;
|
|
68
|
-
onComplete: (finalData: { content: string; toolCalls: any[] }) => void;
|
|
69
|
-
onError: (error: Error) => void;
|
|
70
|
-
onDone?: () => void;
|
|
71
|
-
},
|
|
72
|
-
options?: RequestInit
|
|
73
|
-
): Promise<void>
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**参数:**
|
|
77
|
-
|
|
78
|
-
- `url` (string) - 要连接的相对 URL
|
|
79
|
-
- `handlers` (object) - 事件处理器对象
|
|
80
|
-
- `onMessage` - 消息事件回调
|
|
81
|
-
- `onContent` - 内容更新回调
|
|
82
|
-
- `onToolCallDelta` - 工具调用增量更新回调
|
|
83
|
-
- `onComplete` - 完成事件回调
|
|
84
|
-
- `onError` - 错误处理回调
|
|
85
|
-
- `onDone` (可选) - 连接完成回调
|
|
86
|
-
- `options` (RequestInit, 可选) - 额外的 fetch 选项,包括 `body` 用于 POST 请求的请求体数据
|
|
87
|
-
|
|
88
|
-
#### disconnect()
|
|
89
|
-
|
|
90
|
-
立即断开 SSE 连接。
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
disconnect(): void
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## 事件处理
|
|
97
|
-
|
|
98
|
-
### 事件类型
|
|
99
|
-
|
|
100
|
-
| 事件类型 | 数据类型 | 描述 |
|
|
101
|
-
| --------------- | --------------------------------------- | -------------------- |
|
|
102
|
-
| `message` | `string` | 通用消息事件 |
|
|
103
|
-
| `content` | `string` | 内容增量更新 |
|
|
104
|
-
| `toolCallDelta` | `any` | 工具调用状态增量更新 |
|
|
105
|
-
| `complete` | `{ content: string; toolCalls: any[] }` | 流式传输完成 |
|
|
106
|
-
| `error` | `Error` | 错误事件 |
|
|
107
|
-
|
|
108
|
-
### 处理器配置
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
const handlers = {
|
|
112
|
-
// 处理消息事件
|
|
113
|
-
onMessage: (message: string) => {
|
|
114
|
-
console.log('收到消息:', message);
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
// 处理内容流
|
|
118
|
-
onContent: (content: string) => {
|
|
119
|
-
console.log('收到内容:', content);
|
|
120
|
-
},
|
|
121
|
-
|
|
122
|
-
// 处理工具调用增量
|
|
123
|
-
onToolCallDelta: (data: any) => {
|
|
124
|
-
console.log('工具调用更新:', data);
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
// 处理完成事件
|
|
128
|
-
onComplete: (finalData: { content: string; toolCalls: any[] }) => {
|
|
129
|
-
console.log('传输完成:', finalData);
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
// 处理错误
|
|
133
|
-
onError: (error: Error) => {
|
|
134
|
-
console.error('发生错误:', error);
|
|
135
|
-
},
|
|
136
|
-
|
|
137
|
-
// 连接完全结束
|
|
138
|
-
onDone: () => {
|
|
139
|
-
console.log('连接结束');
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## 使用示例
|
|
145
|
-
|
|
146
|
-
### 基础使用
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
// 创建客户端实例(GET 请求)
|
|
150
|
-
const client = new SSEClient('your-token', 'https://api.example.com');
|
|
151
|
-
|
|
152
|
-
// 或者创建 POST 请求客户端
|
|
153
|
-
const postClient = new SSEClient('your-token', 'https://api.example.com', 'POST');
|
|
154
|
-
|
|
155
|
-
// 定义事件处理器
|
|
156
|
-
const handlers = {
|
|
157
|
-
onMessage: (message) => {
|
|
158
|
-
console.log('消息:', message);
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
onContent: (content) => {
|
|
162
|
-
document.getElementById('output').innerHTML += content;
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
onToolCallDelta: (data) => {
|
|
166
|
-
console.log('工具调用状态:', data);
|
|
167
|
-
},
|
|
168
|
-
|
|
169
|
-
onComplete: (finalData) => {
|
|
170
|
-
console.log('完整响应:', finalData);
|
|
171
|
-
// 可以在这里进行后续处理
|
|
172
|
-
},
|
|
173
|
-
|
|
174
|
-
onError: (error) => {
|
|
175
|
-
console.error('连接错误:', error);
|
|
176
|
-
alert('连接发生错误: ' + error.message);
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
onDone: () => {
|
|
180
|
-
console.log('连接正常结束');
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
// GET 请求建立连接
|
|
185
|
-
client.connect('/api/stream', handlers, {
|
|
186
|
-
headers: {
|
|
187
|
-
'X-Custom-Header': 'value'
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// POST 请求建立连接(带请求体)
|
|
192
|
-
postClient.connect('/api/stream', handlers, {
|
|
193
|
-
headers: {
|
|
194
|
-
'X-Custom-Header': 'value'
|
|
195
|
-
},
|
|
196
|
-
body: JSON.stringify({
|
|
197
|
-
prompt: 'Hello, world!',
|
|
198
|
-
options: { temperature: 0.7 }
|
|
199
|
-
})
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// 在需要时断开连接
|
|
203
|
-
// client.disconnect();
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
### React 组件中使用
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
import React, { useState, useEffect, useRef } from 'react';
|
|
210
|
-
import { SSEClient } from './sse-client';
|
|
211
|
-
|
|
212
|
-
const StreamComponent: React.FC = () => {
|
|
213
|
-
const [content, setContent] = useState('');
|
|
214
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
215
|
-
const clientRef = useRef<SSEClient | null>(null);
|
|
216
|
-
|
|
217
|
-
const startStream = async () => {
|
|
218
|
-
setIsLoading(true);
|
|
219
|
-
setContent('');
|
|
220
|
-
|
|
221
|
-
// 使用 POST 方法创建客户端
|
|
222
|
-
const client = new SSEClient('your-token', 'https://api.example.com', 'POST');
|
|
223
|
-
clientRef.current = client;
|
|
224
|
-
|
|
225
|
-
const handlers = {
|
|
226
|
-
onMessage: (message: string) => {
|
|
227
|
-
console.log('消息:', message);
|
|
228
|
-
},
|
|
229
|
-
|
|
230
|
-
onContent: (newContent: string) => {
|
|
231
|
-
setContent(prev => prev + newContent);
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
onToolCallDelta: (data: any) => {
|
|
235
|
-
// 处理工具调用
|
|
236
|
-
console.log('工具调用:', data);
|
|
237
|
-
},
|
|
238
|
-
|
|
239
|
-
onComplete: (finalData: { content: string; toolCalls: any[] }) => {
|
|
240
|
-
console.log('完成:', finalData);
|
|
241
|
-
setIsLoading(false);
|
|
242
|
-
},
|
|
243
|
-
|
|
244
|
-
onError: (error: Error) => {
|
|
245
|
-
console.error('错误:', error);
|
|
246
|
-
setIsLoading(false);
|
|
247
|
-
},
|
|
248
|
-
|
|
249
|
-
onDone: () => {
|
|
250
|
-
setIsLoading(false);
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
try {
|
|
255
|
-
await client.connect('/api/chat', handlers, {
|
|
256
|
-
body: JSON.stringify({
|
|
257
|
-
message: 'Hello',
|
|
258
|
-
conversationId: '123'
|
|
259
|
-
})
|
|
260
|
-
});
|
|
261
|
-
} catch (error) {
|
|
262
|
-
console.error('连接失败:', error);
|
|
263
|
-
setIsLoading(false);
|
|
264
|
-
}
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
const stopStream = () => {
|
|
268
|
-
if (clientRef.current) {
|
|
269
|
-
clientRef.current.disconnect();
|
|
270
|
-
setIsLoading(false);
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
useEffect(() => {
|
|
275
|
-
return () => {
|
|
276
|
-
// 组件卸载时断开连接
|
|
277
|
-
if (clientRef.current) {
|
|
278
|
-
clientRef.current.disconnect();
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
}, []);
|
|
282
|
-
|
|
283
|
-
return (
|
|
284
|
-
<div>
|
|
285
|
-
<button onClick={startStream} disabled={isLoading}>
|
|
286
|
-
{isLoading ? '连接中...' : '开始流式传输'}
|
|
287
|
-
</button>
|
|
288
|
-
<button onClick={stopStream} disabled={!isLoading}>
|
|
289
|
-
停止
|
|
290
|
-
</button>
|
|
291
|
-
<div className="content-area">
|
|
292
|
-
{content}
|
|
293
|
-
</div>
|
|
294
|
-
</div>
|
|
295
|
-
);
|
|
296
|
-
};
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Vue 3 组件中使用
|
|
300
|
-
|
|
301
|
-
```vue
|
|
302
|
-
<template>
|
|
303
|
-
<div>
|
|
304
|
-
<button @click="startStream" :disabled="isLoading">
|
|
305
|
-
{{ isLoading ? '连接中...' : '开始流式传输' }}
|
|
306
|
-
</button>
|
|
307
|
-
<button @click="stopStream" :disabled="!isLoading">停止</button>
|
|
308
|
-
<div class="content-area">
|
|
309
|
-
{{ content }}
|
|
310
|
-
</div>
|
|
311
|
-
</div>
|
|
312
|
-
</template>
|
|
313
|
-
|
|
314
|
-
<script setup lang="ts">
|
|
315
|
-
import { ref, onUnmounted } from 'vue';
|
|
316
|
-
import { SSEClient } from './sse-client';
|
|
317
|
-
|
|
318
|
-
const content = ref('');
|
|
319
|
-
const isLoading = ref(false);
|
|
320
|
-
let client: SSEClient | null = null;
|
|
321
|
-
|
|
322
|
-
const startStream = async () => {
|
|
323
|
-
isLoading.value = true;
|
|
324
|
-
content.value = '';
|
|
325
|
-
|
|
326
|
-
client = new SSEClient('your-token', 'https://api.example.com');
|
|
327
|
-
|
|
328
|
-
const handlers = {
|
|
329
|
-
onContent: (newContent: string) => {
|
|
330
|
-
content.value += newContent;
|
|
331
|
-
},
|
|
332
|
-
|
|
333
|
-
onToolCallDelta: (data: any) => {
|
|
334
|
-
console.log('工具调用:', data);
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
onComplete: (finalData: { content: string; toolCalls: any[] }) => {
|
|
338
|
-
console.log('完成:', finalData);
|
|
339
|
-
isLoading.value = false;
|
|
340
|
-
},
|
|
341
|
-
|
|
342
|
-
onError: (error: Error) => {
|
|
343
|
-
console.error('错误:', error);
|
|
344
|
-
isLoading.value = false;
|
|
345
|
-
},
|
|
346
|
-
|
|
347
|
-
onDone: () => {
|
|
348
|
-
isLoading.value = false;
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
try {
|
|
353
|
-
await client.connect('/api/chat', handlers);
|
|
354
|
-
} catch (error) {
|
|
355
|
-
console.error('连接失败:', error);
|
|
356
|
-
isLoading.value = false;
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
const stopStream = () => {
|
|
361
|
-
if (client) {
|
|
362
|
-
client.disconnect();
|
|
363
|
-
isLoading.value = false;
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
onUnmounted(() => {
|
|
368
|
-
if (client) {
|
|
369
|
-
client.disconnect();
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
</script>
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
## 错误处理
|
|
376
|
-
|
|
377
|
-
### 常见错误类型
|
|
378
|
-
|
|
379
|
-
1. **HTTP 错误** - 服务器返回非 200 状态码
|
|
380
|
-
2. **网络错误** - 连接失败或中断
|
|
381
|
-
3. **数据解析错误** - 接收到的数据格式不正确
|
|
382
|
-
4. **中止错误** - 主动调用 `disconnect()` 方法
|
|
383
|
-
|
|
384
|
-
### 错误处理示例
|
|
385
|
-
|
|
386
|
-
```typescript
|
|
387
|
-
const handlers = {
|
|
388
|
-
onError: (error: Error) => {
|
|
389
|
-
if (error.message.includes('HTTP')) {
|
|
390
|
-
// 处理 HTTP 错误
|
|
391
|
-
console.error('服务器错误:', error.message);
|
|
392
|
-
} else if (error.message.includes('No response body')) {
|
|
393
|
-
// 处理无响应体错误
|
|
394
|
-
console.error('无响应数据');
|
|
395
|
-
} else {
|
|
396
|
-
// 其他错误
|
|
397
|
-
console.error('未知错误:', error);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// 更新 UI 状态
|
|
401
|
-
setIsConnected(false);
|
|
402
|
-
setErrorMessage(error.message);
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
## 注意事项
|
|
408
|
-
|
|
409
|
-
### 1. 连接管理
|
|
410
|
-
|
|
411
|
-
- 确保在组件卸载或不再需要时调用 `disconnect()` 方法
|
|
412
|
-
- 避免同时建立多个相同连接
|
|
413
|
-
|
|
414
|
-
### 2. 内存管理
|
|
415
|
-
|
|
416
|
-
- 长时间运行的连接可能积累大量数据,定期清理缓冲区
|
|
417
|
-
- 监控内存使用情况
|
|
418
|
-
|
|
419
|
-
### 3. 错误恢复
|
|
420
|
-
|
|
421
|
-
- 实现重连机制处理网络中断
|
|
422
|
-
- 设置合理的超时时间
|
|
423
|
-
|
|
424
|
-
### 4. 性能优化
|
|
425
|
-
|
|
426
|
-
- 对于高频更新,考虑使用防抖或节流
|
|
427
|
-
- 避免在 `onContent` 回调中执行重操作
|
|
428
|
-
|
|
429
|
-
### 5. 安全考虑
|
|
430
|
-
|
|
431
|
-
- 妥善保管认证令牌
|
|
432
|
-
- 验证服务器响应数据
|
|
433
|
-
- 使用 HTTPS 连接
|
|
434
|
-
|
|
435
|
-
### 完整重连机制示例
|
|
436
|
-
|
|
437
|
-
```typescript
|
|
438
|
-
class ResilientSSEClient {
|
|
439
|
-
private client: SSEClient | null = null;
|
|
440
|
-
private reconnectAttempts = 0;
|
|
441
|
-
private maxReconnectAttempts = 3;
|
|
442
|
-
private reconnectDelay = 1000;
|
|
443
|
-
|
|
444
|
-
constructor(
|
|
445
|
-
private token: string,
|
|
446
|
-
private baseUrl: string
|
|
447
|
-
) {}
|
|
448
|
-
|
|
449
|
-
async connectWithRetry(url: string, handlers: any, options?: RequestInit): Promise<void> {
|
|
450
|
-
try {
|
|
451
|
-
this.client = new SSEClient(this.token, this.baseUrl);
|
|
452
|
-
|
|
453
|
-
const enhancedHandlers = {
|
|
454
|
-
...handlers,
|
|
455
|
-
onError: (error: Error) => {
|
|
456
|
-
console.error('SSE错误:', error);
|
|
457
|
-
|
|
458
|
-
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
459
|
-
this.reconnectAttempts++;
|
|
460
|
-
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
461
|
-
|
|
462
|
-
setTimeout(() => {
|
|
463
|
-
this.connectWithRetry(url, handlers, options);
|
|
464
|
-
}, this.reconnectDelay * this.reconnectAttempts);
|
|
465
|
-
} else {
|
|
466
|
-
handlers.onError?.(error);
|
|
467
|
-
}
|
|
468
|
-
},
|
|
469
|
-
|
|
470
|
-
onDone: () => {
|
|
471
|
-
this.reconnectAttempts = 0; // 重置重连计数
|
|
472
|
-
handlers.onDone?.();
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
await this.client.connect(url, enhancedHandlers, options);
|
|
477
|
-
} catch (error) {
|
|
478
|
-
console.error('连接失败:', error);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
disconnect() {
|
|
483
|
-
this.client?.disconnect();
|
|
484
|
-
this.reconnectAttempts = 0;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
---
|
|
490
|
-
|
|
491
|
-
**版本**: 1.0.0
|
|
492
|
-
**最后更新**: 2024年12月
|
|
1
|
+
# SSEClient 使用参考手册
|
|
2
|
+
|
|
3
|
+
## 目录
|
|
4
|
+
|
|
5
|
+
- [概述](#概述)
|
|
6
|
+
- [安装与导入](#安装与导入)
|
|
7
|
+
- [API 参考](#api-参考)
|
|
8
|
+
- [事件处理](#事件处理)
|
|
9
|
+
- [使用示例](#使用示例)
|
|
10
|
+
- [错误处理](#错误处理)
|
|
11
|
+
- [注意事项](#注意事项)
|
|
12
|
+
|
|
13
|
+
## 概述
|
|
14
|
+
|
|
15
|
+
`SSEClient` 是一个用于处理服务器发送事件(Server-Sent Events)的客户端类,支持流式数据传输,特别适用于实时内容更新、工具调用状态监控等场景。
|
|
16
|
+
|
|
17
|
+
### 主要特性
|
|
18
|
+
|
|
19
|
+
- 🔄 **流式数据处理** - 实时处理分块传输的内容
|
|
20
|
+
- 🛠️ **工具调用监控** - 跟踪工具调用的增量更新
|
|
21
|
+
- ⚡ **事件驱动** - 基于事件的处理机制
|
|
22
|
+
- 🚫 **错误处理** - 完善的错误处理机制
|
|
23
|
+
- 🔗 **连接管理** - 支持主动断开连接
|
|
24
|
+
|
|
25
|
+
## 安装与导入
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { SSEClient } from './sse-client';
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## API 参考
|
|
32
|
+
|
|
33
|
+
### 构造函数
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
new SSEClient(token: string, baseUrl: string, method?: 'POST' | 'GET')
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**参数:**
|
|
40
|
+
|
|
41
|
+
- `token` (string) - 认证令牌,用于 Authorization 头
|
|
42
|
+
- `baseUrl` (string) - 基础 URL
|
|
43
|
+
- `method` ('POST' | 'GET', 可选) - 请求方法,默认为 `'GET'`
|
|
44
|
+
|
|
45
|
+
**示例:**
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// GET 请求(默认)
|
|
49
|
+
const client = new SSEClient('your-auth-token', 'https://api.example.com');
|
|
50
|
+
|
|
51
|
+
// POST 请求
|
|
52
|
+
const client = new SSEClient('your-auth-token', 'https://api.example.com', 'POST');
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 方法
|
|
56
|
+
|
|
57
|
+
#### connect()
|
|
58
|
+
|
|
59
|
+
建立 SSE 连接并开始处理事件流。
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
async connect(
|
|
63
|
+
url: string,
|
|
64
|
+
handlers: {
|
|
65
|
+
onMessage: (message: string) => void;
|
|
66
|
+
onContent: (content: string) => void;
|
|
67
|
+
onToolCallDelta: (data: any) => void;
|
|
68
|
+
onComplete: (finalData: { content: string; toolCalls: any[] }) => void;
|
|
69
|
+
onError: (error: Error) => void;
|
|
70
|
+
onDone?: () => void;
|
|
71
|
+
},
|
|
72
|
+
options?: RequestInit
|
|
73
|
+
): Promise<void>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**参数:**
|
|
77
|
+
|
|
78
|
+
- `url` (string) - 要连接的相对 URL
|
|
79
|
+
- `handlers` (object) - 事件处理器对象
|
|
80
|
+
- `onMessage` - 消息事件回调
|
|
81
|
+
- `onContent` - 内容更新回调
|
|
82
|
+
- `onToolCallDelta` - 工具调用增量更新回调
|
|
83
|
+
- `onComplete` - 完成事件回调
|
|
84
|
+
- `onError` - 错误处理回调
|
|
85
|
+
- `onDone` (可选) - 连接完成回调
|
|
86
|
+
- `options` (RequestInit, 可选) - 额外的 fetch 选项,包括 `body` 用于 POST 请求的请求体数据
|
|
87
|
+
|
|
88
|
+
#### disconnect()
|
|
89
|
+
|
|
90
|
+
立即断开 SSE 连接。
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
disconnect(): void
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 事件处理
|
|
97
|
+
|
|
98
|
+
### 事件类型
|
|
99
|
+
|
|
100
|
+
| 事件类型 | 数据类型 | 描述 |
|
|
101
|
+
| --------------- | --------------------------------------- | -------------------- |
|
|
102
|
+
| `message` | `string` | 通用消息事件 |
|
|
103
|
+
| `content` | `string` | 内容增量更新 |
|
|
104
|
+
| `toolCallDelta` | `any` | 工具调用状态增量更新 |
|
|
105
|
+
| `complete` | `{ content: string; toolCalls: any[] }` | 流式传输完成 |
|
|
106
|
+
| `error` | `Error` | 错误事件 |
|
|
107
|
+
|
|
108
|
+
### 处理器配置
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const handlers = {
|
|
112
|
+
// 处理消息事件
|
|
113
|
+
onMessage: (message: string) => {
|
|
114
|
+
console.log('收到消息:', message);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// 处理内容流
|
|
118
|
+
onContent: (content: string) => {
|
|
119
|
+
console.log('收到内容:', content);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
// 处理工具调用增量
|
|
123
|
+
onToolCallDelta: (data: any) => {
|
|
124
|
+
console.log('工具调用更新:', data);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
// 处理完成事件
|
|
128
|
+
onComplete: (finalData: { content: string; toolCalls: any[] }) => {
|
|
129
|
+
console.log('传输完成:', finalData);
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// 处理错误
|
|
133
|
+
onError: (error: Error) => {
|
|
134
|
+
console.error('发生错误:', error);
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
// 连接完全结束
|
|
138
|
+
onDone: () => {
|
|
139
|
+
console.log('连接结束');
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 使用示例
|
|
145
|
+
|
|
146
|
+
### 基础使用
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// 创建客户端实例(GET 请求)
|
|
150
|
+
const client = new SSEClient('your-token', 'https://api.example.com');
|
|
151
|
+
|
|
152
|
+
// 或者创建 POST 请求客户端
|
|
153
|
+
const postClient = new SSEClient('your-token', 'https://api.example.com', 'POST');
|
|
154
|
+
|
|
155
|
+
// 定义事件处理器
|
|
156
|
+
const handlers = {
|
|
157
|
+
onMessage: (message) => {
|
|
158
|
+
console.log('消息:', message);
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
onContent: (content) => {
|
|
162
|
+
document.getElementById('output').innerHTML += content;
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
onToolCallDelta: (data) => {
|
|
166
|
+
console.log('工具调用状态:', data);
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
onComplete: (finalData) => {
|
|
170
|
+
console.log('完整响应:', finalData);
|
|
171
|
+
// 可以在这里进行后续处理
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
onError: (error) => {
|
|
175
|
+
console.error('连接错误:', error);
|
|
176
|
+
alert('连接发生错误: ' + error.message);
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
onDone: () => {
|
|
180
|
+
console.log('连接正常结束');
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// GET 请求建立连接
|
|
185
|
+
client.connect('/api/stream', handlers, {
|
|
186
|
+
headers: {
|
|
187
|
+
'X-Custom-Header': 'value'
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// POST 请求建立连接(带请求体)
|
|
192
|
+
postClient.connect('/api/stream', handlers, {
|
|
193
|
+
headers: {
|
|
194
|
+
'X-Custom-Header': 'value'
|
|
195
|
+
},
|
|
196
|
+
body: JSON.stringify({
|
|
197
|
+
prompt: 'Hello, world!',
|
|
198
|
+
options: { temperature: 0.7 }
|
|
199
|
+
})
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// 在需要时断开连接
|
|
203
|
+
// client.disconnect();
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### React 组件中使用
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
210
|
+
import { SSEClient } from './sse-client';
|
|
211
|
+
|
|
212
|
+
const StreamComponent: React.FC = () => {
|
|
213
|
+
const [content, setContent] = useState('');
|
|
214
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
215
|
+
const clientRef = useRef<SSEClient | null>(null);
|
|
216
|
+
|
|
217
|
+
const startStream = async () => {
|
|
218
|
+
setIsLoading(true);
|
|
219
|
+
setContent('');
|
|
220
|
+
|
|
221
|
+
// 使用 POST 方法创建客户端
|
|
222
|
+
const client = new SSEClient('your-token', 'https://api.example.com', 'POST');
|
|
223
|
+
clientRef.current = client;
|
|
224
|
+
|
|
225
|
+
const handlers = {
|
|
226
|
+
onMessage: (message: string) => {
|
|
227
|
+
console.log('消息:', message);
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
onContent: (newContent: string) => {
|
|
231
|
+
setContent(prev => prev + newContent);
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
onToolCallDelta: (data: any) => {
|
|
235
|
+
// 处理工具调用
|
|
236
|
+
console.log('工具调用:', data);
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
onComplete: (finalData: { content: string; toolCalls: any[] }) => {
|
|
240
|
+
console.log('完成:', finalData);
|
|
241
|
+
setIsLoading(false);
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
onError: (error: Error) => {
|
|
245
|
+
console.error('错误:', error);
|
|
246
|
+
setIsLoading(false);
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
onDone: () => {
|
|
250
|
+
setIsLoading(false);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
await client.connect('/api/chat', handlers, {
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
message: 'Hello',
|
|
258
|
+
conversationId: '123'
|
|
259
|
+
})
|
|
260
|
+
});
|
|
261
|
+
} catch (error) {
|
|
262
|
+
console.error('连接失败:', error);
|
|
263
|
+
setIsLoading(false);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const stopStream = () => {
|
|
268
|
+
if (clientRef.current) {
|
|
269
|
+
clientRef.current.disconnect();
|
|
270
|
+
setIsLoading(false);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
return () => {
|
|
276
|
+
// 组件卸载时断开连接
|
|
277
|
+
if (clientRef.current) {
|
|
278
|
+
clientRef.current.disconnect();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
}, []);
|
|
282
|
+
|
|
283
|
+
return (
|
|
284
|
+
<div>
|
|
285
|
+
<button onClick={startStream} disabled={isLoading}>
|
|
286
|
+
{isLoading ? '连接中...' : '开始流式传输'}
|
|
287
|
+
</button>
|
|
288
|
+
<button onClick={stopStream} disabled={!isLoading}>
|
|
289
|
+
停止
|
|
290
|
+
</button>
|
|
291
|
+
<div className="content-area">
|
|
292
|
+
{content}
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
);
|
|
296
|
+
};
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Vue 3 组件中使用
|
|
300
|
+
|
|
301
|
+
```vue
|
|
302
|
+
<template>
|
|
303
|
+
<div>
|
|
304
|
+
<button @click="startStream" :disabled="isLoading">
|
|
305
|
+
{{ isLoading ? '连接中...' : '开始流式传输' }}
|
|
306
|
+
</button>
|
|
307
|
+
<button @click="stopStream" :disabled="!isLoading">停止</button>
|
|
308
|
+
<div class="content-area">
|
|
309
|
+
{{ content }}
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
312
|
+
</template>
|
|
313
|
+
|
|
314
|
+
<script setup lang="ts">
|
|
315
|
+
import { ref, onUnmounted } from 'vue';
|
|
316
|
+
import { SSEClient } from './sse-client';
|
|
317
|
+
|
|
318
|
+
const content = ref('');
|
|
319
|
+
const isLoading = ref(false);
|
|
320
|
+
let client: SSEClient | null = null;
|
|
321
|
+
|
|
322
|
+
const startStream = async () => {
|
|
323
|
+
isLoading.value = true;
|
|
324
|
+
content.value = '';
|
|
325
|
+
|
|
326
|
+
client = new SSEClient('your-token', 'https://api.example.com');
|
|
327
|
+
|
|
328
|
+
const handlers = {
|
|
329
|
+
onContent: (newContent: string) => {
|
|
330
|
+
content.value += newContent;
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
onToolCallDelta: (data: any) => {
|
|
334
|
+
console.log('工具调用:', data);
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
onComplete: (finalData: { content: string; toolCalls: any[] }) => {
|
|
338
|
+
console.log('完成:', finalData);
|
|
339
|
+
isLoading.value = false;
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
onError: (error: Error) => {
|
|
343
|
+
console.error('错误:', error);
|
|
344
|
+
isLoading.value = false;
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
onDone: () => {
|
|
348
|
+
isLoading.value = false;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
await client.connect('/api/chat', handlers);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.error('连接失败:', error);
|
|
356
|
+
isLoading.value = false;
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
const stopStream = () => {
|
|
361
|
+
if (client) {
|
|
362
|
+
client.disconnect();
|
|
363
|
+
isLoading.value = false;
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
onUnmounted(() => {
|
|
368
|
+
if (client) {
|
|
369
|
+
client.disconnect();
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
</script>
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## 错误处理
|
|
376
|
+
|
|
377
|
+
### 常见错误类型
|
|
378
|
+
|
|
379
|
+
1. **HTTP 错误** - 服务器返回非 200 状态码
|
|
380
|
+
2. **网络错误** - 连接失败或中断
|
|
381
|
+
3. **数据解析错误** - 接收到的数据格式不正确
|
|
382
|
+
4. **中止错误** - 主动调用 `disconnect()` 方法
|
|
383
|
+
|
|
384
|
+
### 错误处理示例
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
const handlers = {
|
|
388
|
+
onError: (error: Error) => {
|
|
389
|
+
if (error.message.includes('HTTP')) {
|
|
390
|
+
// 处理 HTTP 错误
|
|
391
|
+
console.error('服务器错误:', error.message);
|
|
392
|
+
} else if (error.message.includes('No response body')) {
|
|
393
|
+
// 处理无响应体错误
|
|
394
|
+
console.error('无响应数据');
|
|
395
|
+
} else {
|
|
396
|
+
// 其他错误
|
|
397
|
+
console.error('未知错误:', error);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// 更新 UI 状态
|
|
401
|
+
setIsConnected(false);
|
|
402
|
+
setErrorMessage(error.message);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## 注意事项
|
|
408
|
+
|
|
409
|
+
### 1. 连接管理
|
|
410
|
+
|
|
411
|
+
- 确保在组件卸载或不再需要时调用 `disconnect()` 方法
|
|
412
|
+
- 避免同时建立多个相同连接
|
|
413
|
+
|
|
414
|
+
### 2. 内存管理
|
|
415
|
+
|
|
416
|
+
- 长时间运行的连接可能积累大量数据,定期清理缓冲区
|
|
417
|
+
- 监控内存使用情况
|
|
418
|
+
|
|
419
|
+
### 3. 错误恢复
|
|
420
|
+
|
|
421
|
+
- 实现重连机制处理网络中断
|
|
422
|
+
- 设置合理的超时时间
|
|
423
|
+
|
|
424
|
+
### 4. 性能优化
|
|
425
|
+
|
|
426
|
+
- 对于高频更新,考虑使用防抖或节流
|
|
427
|
+
- 避免在 `onContent` 回调中执行重操作
|
|
428
|
+
|
|
429
|
+
### 5. 安全考虑
|
|
430
|
+
|
|
431
|
+
- 妥善保管认证令牌
|
|
432
|
+
- 验证服务器响应数据
|
|
433
|
+
- 使用 HTTPS 连接
|
|
434
|
+
|
|
435
|
+
### 完整重连机制示例
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
class ResilientSSEClient {
|
|
439
|
+
private client: SSEClient | null = null;
|
|
440
|
+
private reconnectAttempts = 0;
|
|
441
|
+
private maxReconnectAttempts = 3;
|
|
442
|
+
private reconnectDelay = 1000;
|
|
443
|
+
|
|
444
|
+
constructor(
|
|
445
|
+
private token: string,
|
|
446
|
+
private baseUrl: string
|
|
447
|
+
) {}
|
|
448
|
+
|
|
449
|
+
async connectWithRetry(url: string, handlers: any, options?: RequestInit): Promise<void> {
|
|
450
|
+
try {
|
|
451
|
+
this.client = new SSEClient(this.token, this.baseUrl);
|
|
452
|
+
|
|
453
|
+
const enhancedHandlers = {
|
|
454
|
+
...handlers,
|
|
455
|
+
onError: (error: Error) => {
|
|
456
|
+
console.error('SSE错误:', error);
|
|
457
|
+
|
|
458
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
459
|
+
this.reconnectAttempts++;
|
|
460
|
+
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
|
|
461
|
+
|
|
462
|
+
setTimeout(() => {
|
|
463
|
+
this.connectWithRetry(url, handlers, options);
|
|
464
|
+
}, this.reconnectDelay * this.reconnectAttempts);
|
|
465
|
+
} else {
|
|
466
|
+
handlers.onError?.(error);
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
onDone: () => {
|
|
471
|
+
this.reconnectAttempts = 0; // 重置重连计数
|
|
472
|
+
handlers.onDone?.();
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
await this.client.connect(url, enhancedHandlers, options);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
console.error('连接失败:', error);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
disconnect() {
|
|
483
|
+
this.client?.disconnect();
|
|
484
|
+
this.reconnectAttempts = 0;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
**版本**: 1.0.0
|
|
492
|
+
**最后更新**: 2024年12月
|