@blueking/chat-helper 0.0.1-beta.9 → 0.0.2
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/README.md +631 -141
- package/dist/agent/type.d.ts +27 -2
- package/dist/agent/type.ts.js +1 -1
- package/dist/agent/use-agent.d.ts +464 -484
- package/dist/agent/use-agent.ts.js +191 -42
- package/dist/event/ag-ui.d.ts +37 -7
- package/dist/event/ag-ui.ts.js +225 -173
- package/dist/event/type.d.ts +99 -107
- package/dist/event/type.ts.js +34 -11
- package/dist/http/fetch/fetch.d.ts +2 -1
- package/dist/http/fetch/fetch.ts.js +38 -8
- package/dist/http/fetch/index.d.ts +1 -0
- package/dist/http/fetch/index.ts.js +17 -1
- package/dist/http/index.d.ts +14 -5
- package/dist/http/index.ts.js +59 -3
- package/dist/http/module/index.d.ts +13 -5
- package/dist/http/module/index.ts.js +2 -1
- package/dist/http/module/message.d.ts +22 -5
- package/dist/http/module/message.ts.js +51 -4
- package/dist/http/module/session.d.ts +4 -1
- package/dist/http/module/session.ts.js +66 -4
- package/dist/http/transform/agent.ts.js +11 -8
- package/dist/http/transform/message.d.ts +6 -6
- package/dist/http/transform/message.ts.js +566 -118
- package/dist/http/transform/session.ts.js +9 -1
- package/dist/index.d.ts +2983 -3314
- package/dist/index.ts.js +27 -5
- package/dist/mediator/index.d.ts +2 -0
- package/dist/mediator/index.ts.js +26 -0
- package/dist/mediator/type.d.ts +50 -0
- package/dist/mediator/type.ts.js +28 -0
- package/dist/mediator/use-mediator.d.ts +7 -0
- package/dist/mediator/use-mediator.ts.js +47 -0
- package/dist/message/type.d.ts +280 -146
- package/dist/message/type.ts.js +16 -15
- package/dist/message/use-message.d.ts +847 -963
- package/dist/message/use-message.ts.js +230 -37
- package/dist/session/type.d.ts +10 -0
- package/dist/session/use-session.d.ts +2006 -2214
- package/dist/session/use-session.ts.js +198 -33
- package/package.json +11 -6
package/README.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
一个用于 Vue3 的聊天助手工具包,提供完整的 AI 对话功能,支持流式响应、会话管理、消息管理等功能。
|
|
4
4
|
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- 🚀 **Vue3 Composition API**:基于 Vue3 的响应式系统,提供流畅的开发体验
|
|
8
|
+
- 💬 **完整的会话管理**:支持多会话创建、切换、更新和删除
|
|
9
|
+
- 📨 **消息管理**:支持消息列表、添加、修改、删除和批量操作
|
|
10
|
+
- 🤖 **AI Agent 集成**:轻松接入 AI 代理,获取代理信息和进行对话
|
|
11
|
+
- 🌊 **流式响应**:支持 SSE(Server-Sent Events)流式响应,实时展示 AI 回复
|
|
12
|
+
- 🎯 **中介者模式**:模块间通过中介者协调通信,降低耦合度
|
|
13
|
+
- 🔧 **高度可定制**:支持自定义拦截器、自定义 Protocol、自定义请求配置
|
|
14
|
+
- 📝 **TypeScript 支持**:完整的类型定义,提供良好的类型提示
|
|
15
|
+
|
|
5
16
|
## 安装
|
|
6
17
|
|
|
7
18
|
```bash
|
|
@@ -27,6 +38,34 @@ const chatHelper = useChatHelper({
|
|
|
27
38
|
const { agent, session, message, http } = chatHelper;
|
|
28
39
|
```
|
|
29
40
|
|
|
41
|
+
## 架构设计
|
|
42
|
+
|
|
43
|
+
`@blueking/chat-helper` 采用**中介者模式(Mediator Pattern)**进行架构设计,将复杂的模块间通信集中到中介者对象中,降低了各模块之间的耦合度。
|
|
44
|
+
|
|
45
|
+
### 核心模块
|
|
46
|
+
|
|
47
|
+
- **Agent 模块**:管理 AI 代理相关功能,包括获取代理信息、开始聊天、停止聊天等
|
|
48
|
+
- **Session 模块**:管理聊天会话,包括会话列表、创建、切换、更新和删除
|
|
49
|
+
- **Message 模块**:管理聊天消息,包括消息列表、添加、修改和删除
|
|
50
|
+
- **HTTP 模块**:底层 HTTP 请求封装,提供各种 API 调用方法和 SSE 流式响应支持
|
|
51
|
+
- **Mediator 模块**:中介者,协调各模块之间的通信,避免模块间直接依赖
|
|
52
|
+
|
|
53
|
+
### 数据流向
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
用户操作 → Agent/Session/Message 模块 → Mediator → HTTP 模块 → 后端 API
|
|
57
|
+
↑ ↓
|
|
58
|
+
←────────── 响应数据/流式事件 ←─────────────
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Protocol 系统
|
|
62
|
+
|
|
63
|
+
`@blueking/chat-helper` 提供了可扩展的 Protocol 系统来处理 SSE 流式响应:
|
|
64
|
+
|
|
65
|
+
- **ISSEProtocol 接口**:定义了流式响应的处理接口
|
|
66
|
+
- **AGUIProtocol 实现**:默认的 AGUI 协议实现,支持丰富的事件类型
|
|
67
|
+
- **自定义 Protocol**:用户可以实现自己的 Protocol 来处理特定的流式响应格式
|
|
68
|
+
|
|
30
69
|
## useChatHelper 返回数据说明
|
|
31
70
|
|
|
32
71
|
`useChatHelper` 返回一个包含四个模块的对象:
|
|
@@ -203,25 +242,23 @@ message.isBatchDeleteLoading; // Ref<boolean> - 批量删除加载状态
|
|
|
203
242
|
|
|
204
243
|
// 方法
|
|
205
244
|
message.getMessages(sessionCode); // (sessionCode: string) => Promise<void> - 获取消息列表
|
|
206
|
-
message.plusLatestMessage(sessionCode); // (sessionCode: string) => Promise<void> - 从接口获取最新的消息并添加到列表前面
|
|
207
245
|
message.plusMessage(message); // (message: IMessage) => Promise<void> - 添加消息
|
|
208
246
|
message.modifyMessage(message); // (message: IMessage) => void - 修改消息
|
|
209
|
-
message.deleteMessage(id); // (id:
|
|
210
|
-
message.batchDeleteMessages(ids); // (ids:
|
|
247
|
+
message.deleteMessage(id); // (id: string) => Promise<void> - 删除消息
|
|
248
|
+
message.batchDeleteMessages(ids); // (ids: string[]) => Promise<void> - 批量删除消息
|
|
211
249
|
message.getCurrentLoadingMessage(); // () => IMessage | undefined - 获取当前加载中的消息
|
|
212
|
-
message.
|
|
250
|
+
message.getMessageByMessageId(id); // (id: string) => IMessage | undefined - 根据消息 ID 获取消息
|
|
213
251
|
```
|
|
214
252
|
|
|
215
253
|
**方法说明**:
|
|
216
254
|
|
|
217
255
|
- `getMessages`: 获取指定会话的所有消息列表
|
|
218
|
-
- `
|
|
219
|
-
- `
|
|
220
|
-
- `
|
|
221
|
-
- `
|
|
222
|
-
- `
|
|
223
|
-
- `
|
|
224
|
-
- `getMessageById`: 根据 ID 从列表中查找消息
|
|
256
|
+
- `plusMessage`: 主动添加一条新消息到列表(会先调用后端接口创建消息,然后添加到列表最前面)
|
|
257
|
+
- `modifyMessage`: 修改已存在的消息(本地更新,不调用接口)
|
|
258
|
+
- `deleteMessage`: 删除单条消息(调用接口删除并从列表中移除)
|
|
259
|
+
- `batchDeleteMessages`: 批量删除多条消息(调用接口批量删除并从列表中移除)
|
|
260
|
+
- `getCurrentLoadingMessage`: 获取当前正在加载/流式传输中的 AI 响应消息(状态为 Pending 或 Streaming)
|
|
261
|
+
- `getMessageByMessageId`: 根据消息 ID 从列表中查找消息
|
|
225
262
|
|
|
226
263
|
**使用示例**:
|
|
227
264
|
|
|
@@ -230,21 +267,20 @@ message.getMessageById(id); // (id: number) => IMessage | undefined - 根据 ID
|
|
|
230
267
|
<div class="message-list">
|
|
231
268
|
<div
|
|
232
269
|
v-for="msg in message.list"
|
|
233
|
-
:key="msg.
|
|
270
|
+
:key="msg.messageId"
|
|
234
271
|
class="message-item"
|
|
235
272
|
>
|
|
236
273
|
<div class="role">{{ msg.role }}</div>
|
|
237
274
|
<div class="content">
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
:
|
|
241
|
-
>
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
</template>
|
|
275
|
+
<!-- 根据消息类型显示不同内容 -->
|
|
276
|
+
<div v-if="msg.role === 'user'">
|
|
277
|
+
{{ typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content) }}
|
|
278
|
+
</div>
|
|
279
|
+
<div v-else-if="msg.role === 'assistant'">
|
|
280
|
+
{{ msg.content || '' }}
|
|
245
281
|
</div>
|
|
246
282
|
</div>
|
|
247
|
-
<button @click="message.deleteMessage(msg.
|
|
283
|
+
<button @click="message.deleteMessage(msg.messageId)">删除</button>
|
|
248
284
|
</div>
|
|
249
285
|
<button
|
|
250
286
|
@click="batchDelete"
|
|
@@ -263,20 +299,13 @@ message.getMessageById(id); // (id: number) => IMessage | undefined - 根据 ID
|
|
|
263
299
|
/* ... */
|
|
264
300
|
});
|
|
265
301
|
|
|
266
|
-
const selectedIds = ref<
|
|
302
|
+
const selectedIds = ref<string[]>([]);
|
|
267
303
|
|
|
268
304
|
const batchDelete = () => {
|
|
269
305
|
if (selectedIds.value.length > 0) {
|
|
270
306
|
message.batchDeleteMessages(selectedIds.value);
|
|
271
307
|
}
|
|
272
308
|
};
|
|
273
|
-
|
|
274
|
-
// 刷新最新消息(例如在多端同步场景)
|
|
275
|
-
const refreshLatestMessage = () => {
|
|
276
|
-
if (session.current?.sessionCode) {
|
|
277
|
-
message.plusLatestMessage(session.current.sessionCode);
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
309
|
</script>
|
|
281
310
|
```
|
|
282
311
|
|
|
@@ -840,13 +869,13 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
840
869
|
<div class="messages">
|
|
841
870
|
<div
|
|
842
871
|
v-for="msg in message.list"
|
|
843
|
-
:key="msg.
|
|
844
|
-
@click="toggleSelection(msg.
|
|
845
|
-
:class="{ selected: selectedIds.includes(msg.
|
|
872
|
+
:key="msg.messageId"
|
|
873
|
+
@click="toggleSelection(msg.messageId)"
|
|
874
|
+
:class="{ selected: selectedIds.includes(msg.messageId) }"
|
|
846
875
|
>
|
|
847
876
|
<input
|
|
848
877
|
type="checkbox"
|
|
849
|
-
:checked="selectedIds.includes(msg.
|
|
878
|
+
:checked="selectedIds.includes(msg.messageId)"
|
|
850
879
|
@click.stop
|
|
851
880
|
/>
|
|
852
881
|
<!-- 消息内容 -->
|
|
@@ -862,9 +891,9 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
862
891
|
const { message } = useChatHelper({
|
|
863
892
|
/* ... */
|
|
864
893
|
});
|
|
865
|
-
const selectedIds = ref<
|
|
894
|
+
const selectedIds = ref<string[]>([]);
|
|
866
895
|
|
|
867
|
-
const toggleSelection = (id:
|
|
896
|
+
const toggleSelection = (id: string) => {
|
|
868
897
|
const index = selectedIds.value.indexOf(id);
|
|
869
898
|
if (index > -1) {
|
|
870
899
|
selectedIds.value.splice(index, 1);
|
|
@@ -874,7 +903,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
874
903
|
};
|
|
875
904
|
|
|
876
905
|
const selectAll = () => {
|
|
877
|
-
selectedIds.value = message.list.value.filter(msg => msg.
|
|
906
|
+
selectedIds.value = message.list.value.filter(msg => msg.messageId).map(msg => msg.messageId!);
|
|
878
907
|
};
|
|
879
908
|
|
|
880
909
|
const clearSelection = () => {
|
|
@@ -890,76 +919,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
890
919
|
</script>
|
|
891
920
|
```
|
|
892
921
|
|
|
893
|
-
### 场景 3
|
|
894
|
-
|
|
895
|
-
在多端同步场景下,使用 `plusLatestMessage` 获取最新消息:
|
|
896
|
-
|
|
897
|
-
```vue
|
|
898
|
-
<template>
|
|
899
|
-
<div class="sync-chat">
|
|
900
|
-
<div class="header">
|
|
901
|
-
<button
|
|
902
|
-
@click="syncLatest"
|
|
903
|
-
:disabled="isSyncing"
|
|
904
|
-
>
|
|
905
|
-
{{ isSyncing ? '同步中...' : '同步最新消息' }}
|
|
906
|
-
</button>
|
|
907
|
-
<span v-if="lastSyncTime"> 上次同步: {{ lastSyncTime.toLocaleTimeString() }} </span>
|
|
908
|
-
</div>
|
|
909
|
-
|
|
910
|
-
<div class="messages">
|
|
911
|
-
<div
|
|
912
|
-
v-for="msg in message.list"
|
|
913
|
-
:key="msg.id"
|
|
914
|
-
>
|
|
915
|
-
<!-- 消息渲染 -->
|
|
916
|
-
</div>
|
|
917
|
-
</div>
|
|
918
|
-
</div>
|
|
919
|
-
</template>
|
|
920
|
-
|
|
921
|
-
<script setup lang="ts">
|
|
922
|
-
import { ref, onMounted, onUnmounted } from 'vue';
|
|
923
|
-
import { useChatHelper } from '@blueking/chat-helper';
|
|
924
|
-
|
|
925
|
-
const { message, session } = useChatHelper({
|
|
926
|
-
/* ... */
|
|
927
|
-
});
|
|
928
|
-
const isSyncing = ref(false);
|
|
929
|
-
const lastSyncTime = ref<Date | null>(null);
|
|
930
|
-
let syncTimer: number | null = null;
|
|
931
|
-
|
|
932
|
-
// 手动同步
|
|
933
|
-
const syncLatest = async () => {
|
|
934
|
-
if (!session.current?.sessionCode || isSyncing.value) return;
|
|
935
|
-
|
|
936
|
-
isSyncing.value = true;
|
|
937
|
-
try {
|
|
938
|
-
await message.plusLatestMessage(session.current.sessionCode);
|
|
939
|
-
lastSyncTime.value = new Date();
|
|
940
|
-
} catch (error) {
|
|
941
|
-
console.error('同步失败:', error);
|
|
942
|
-
} finally {
|
|
943
|
-
isSyncing.value = false;
|
|
944
|
-
}
|
|
945
|
-
};
|
|
946
|
-
|
|
947
|
-
// 自动同步(每30秒)
|
|
948
|
-
onMounted(() => {
|
|
949
|
-
syncTimer = window.setInterval(() => {
|
|
950
|
-
syncLatest();
|
|
951
|
-
}, 30000);
|
|
952
|
-
});
|
|
953
|
-
|
|
954
|
-
onUnmounted(() => {
|
|
955
|
-
if (syncTimer) {
|
|
956
|
-
clearInterval(syncTimer);
|
|
957
|
-
}
|
|
958
|
-
});
|
|
959
|
-
</script>
|
|
960
|
-
```
|
|
961
|
-
|
|
962
|
-
### 场景 4:自定义聊天参数
|
|
922
|
+
### 场景 3:自定义聊天参数
|
|
963
923
|
|
|
964
924
|
根据不同场景使用不同的聊天配置:
|
|
965
925
|
|
|
@@ -1076,42 +1036,18 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1076
1036
|
>
|
|
1077
1037
|
<div
|
|
1078
1038
|
v-for="msg in message.list"
|
|
1079
|
-
:key="msg.
|
|
1039
|
+
:key="msg.messageId"
|
|
1080
1040
|
:class="['message-item', msg.role]"
|
|
1081
1041
|
>
|
|
1082
1042
|
<div class="message-content">
|
|
1083
|
-
<template
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
>
|
|
1087
|
-
|
|
1088
|
-
<div
|
|
1089
|
-
v-if="content.type === 'text'"
|
|
1090
|
-
class="text-content"
|
|
1091
|
-
>
|
|
1092
|
-
{{ content.data }}
|
|
1093
|
-
</div>
|
|
1094
|
-
|
|
1095
|
-
<!-- 思考过程 -->
|
|
1096
|
-
<div
|
|
1097
|
-
v-else-if="content.type === 'thinking'"
|
|
1098
|
-
class="thinking-content"
|
|
1099
|
-
>
|
|
1100
|
-
<strong>{{ content.data.title }}</strong>
|
|
1101
|
-
<p>{{ content.data.text }}</p>
|
|
1102
|
-
</div>
|
|
1103
|
-
|
|
1104
|
-
<!-- 工具调用 -->
|
|
1105
|
-
<div
|
|
1106
|
-
v-else-if="content.type === 'tool_call'"
|
|
1107
|
-
class="tool-call-content"
|
|
1108
|
-
>
|
|
1109
|
-
<strong>调用工具: {{ content.data.toolCallName }}</strong>
|
|
1110
|
-
<pre>{{ content.data.args }}</pre>
|
|
1111
|
-
</div>
|
|
1043
|
+
<template v-if="msg.role === 'user'">
|
|
1044
|
+
{{ typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content) }}
|
|
1045
|
+
</template>
|
|
1046
|
+
<template v-else-if="msg.role === 'assistant'">
|
|
1047
|
+
{{ msg.content || '' }}
|
|
1112
1048
|
</template>
|
|
1113
1049
|
</div>
|
|
1114
|
-
<button @click="message.deleteMessage(msg.
|
|
1050
|
+
<button @click="message.deleteMessage(msg.messageId)">删除</button>
|
|
1115
1051
|
</div>
|
|
1116
1052
|
</div>
|
|
1117
1053
|
|
|
@@ -1279,7 +1215,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1279
1215
|
<div class="messages">
|
|
1280
1216
|
<div
|
|
1281
1217
|
v-for="msg in message.list"
|
|
1282
|
-
:key="msg.
|
|
1218
|
+
:key="msg.messageId"
|
|
1283
1219
|
class="message"
|
|
1284
1220
|
>
|
|
1285
1221
|
<template
|
|
@@ -1499,10 +1435,9 @@ import type {
|
|
|
1499
1435
|
IAgentInfo,
|
|
1500
1436
|
|
|
1501
1437
|
// 消息相关枚举
|
|
1502
|
-
MessageRole,
|
|
1503
|
-
MessageStatus,
|
|
1504
|
-
|
|
1505
|
-
MessageContentStatus,
|
|
1438
|
+
MessageRole, // 消息角色:user, assistant, system, tool, activity, reasoning 等
|
|
1439
|
+
MessageStatus, // 消息状态:pending, streaming, complete, error, stop
|
|
1440
|
+
MessageType, // 消息类型:text, binary, function
|
|
1506
1441
|
|
|
1507
1442
|
// 协议相关
|
|
1508
1443
|
ISSEProtocol,
|
|
@@ -1521,6 +1456,561 @@ import type {
|
|
|
1521
1456
|
} from '@blueking/chat-helper';
|
|
1522
1457
|
```
|
|
1523
1458
|
|
|
1459
|
+
### MessageRole 枚举
|
|
1460
|
+
|
|
1461
|
+
消息支持多种角色类型:
|
|
1462
|
+
|
|
1463
|
+
```typescript
|
|
1464
|
+
enum MessageRole {
|
|
1465
|
+
User = 'user', // 用户消息
|
|
1466
|
+
Assistant = 'assistant', // AI 助手消息
|
|
1467
|
+
System = 'system', // 系统消息
|
|
1468
|
+
Tool = 'tool', // 工具调用结果
|
|
1469
|
+
Activity = 'activity', // 活动消息
|
|
1470
|
+
Reasoning = 'reasoning', // 推理过程
|
|
1471
|
+
Developer = 'developer', // 开发者消息
|
|
1472
|
+
Guide = 'guide', // 引导消息
|
|
1473
|
+
Info = 'info', // 信息消息
|
|
1474
|
+
Pause = 'pause', // 暂停消息
|
|
1475
|
+
Placeholder = 'placeholder', // 占位消息
|
|
1476
|
+
Hidden = 'hidden', // 隐藏消息
|
|
1477
|
+
HiddenUser = 'hidden-user', // 隐藏的用户消息
|
|
1478
|
+
HiddenAssistant = 'hidden-assistant', // 隐藏的助手消息
|
|
1479
|
+
HiddenSystem = 'hidden-system', // 隐藏的系统消息
|
|
1480
|
+
HiddenGuide = 'hidden-guide', // 隐藏的引导消息
|
|
1481
|
+
TemplateUser = 'template-user', // 用户消息模板
|
|
1482
|
+
TemplateAssistant = 'template-assistant', // 助手消息模板
|
|
1483
|
+
TemplateSystem = 'template-system', // 系统消息模板
|
|
1484
|
+
TemplateGuide = 'template-guide', // 引导消息模板
|
|
1485
|
+
TemplateHidden = 'template-hidden', // 隐藏消息模板
|
|
1486
|
+
}
|
|
1487
|
+
```
|
|
1488
|
+
|
|
1489
|
+
### MessageStatus 枚举
|
|
1490
|
+
|
|
1491
|
+
消息状态定义:
|
|
1492
|
+
|
|
1493
|
+
```typescript
|
|
1494
|
+
enum MessageStatus {
|
|
1495
|
+
Pending = 'pending', // 等待中
|
|
1496
|
+
Streaming = 'streaming', // 流式传输中
|
|
1497
|
+
Complete = 'complete', // 已完成
|
|
1498
|
+
Error = 'error', // 错误
|
|
1499
|
+
Stop = 'stop', // 已停止
|
|
1500
|
+
}
|
|
1501
|
+
```
|
|
1502
|
+
|
|
1503
|
+
### IMessage 类型
|
|
1504
|
+
|
|
1505
|
+
消息是联合类型,根据 `role` 字段不同有不同的结构:
|
|
1506
|
+
|
|
1507
|
+
```typescript
|
|
1508
|
+
// 用户消息
|
|
1509
|
+
interface IUserMessage {
|
|
1510
|
+
messageId?: string;
|
|
1511
|
+
role: MessageRole.User;
|
|
1512
|
+
status: MessageStatus;
|
|
1513
|
+
content: string | IInputContent[]; // 支持文本或多媒体内容
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// AI 助手消息
|
|
1517
|
+
interface IAssistantMessage {
|
|
1518
|
+
messageId?: string;
|
|
1519
|
+
role: MessageRole.Assistant;
|
|
1520
|
+
status: MessageStatus;
|
|
1521
|
+
content?: string;
|
|
1522
|
+
toolCalls?: IToolCall[]; // 工具调用信息
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// 工具消息
|
|
1526
|
+
interface IToolMessage {
|
|
1527
|
+
messageId?: string;
|
|
1528
|
+
role: MessageRole.Tool;
|
|
1529
|
+
status: MessageStatus;
|
|
1530
|
+
content: string;
|
|
1531
|
+
toolCallId: string;
|
|
1532
|
+
duration?: number;
|
|
1533
|
+
error?: string;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// ... 更多消息类型
|
|
1537
|
+
```
|
|
1538
|
+
|
|
1539
|
+
## API 参考
|
|
1540
|
+
|
|
1541
|
+
### useChatHelper(options)
|
|
1542
|
+
|
|
1543
|
+
创建 Chat Helper 实例。
|
|
1544
|
+
|
|
1545
|
+
**参数:**
|
|
1546
|
+
|
|
1547
|
+
- `options.requestData` (必需)
|
|
1548
|
+
|
|
1549
|
+
- `urlPrefix: string` - API 基础路径
|
|
1550
|
+
- `data?: Record<string, unknown> | (() => Record<string, unknown>)` - 额外的请求数据
|
|
1551
|
+
- `headers?: Record<string, string> | (() => Record<string, string>)` - 额外的请求头
|
|
1552
|
+
|
|
1553
|
+
- `options.interceptors` (可选)
|
|
1554
|
+
|
|
1555
|
+
- `request?: (config: IRequestConfig) => IRequestConfig` - 请求拦截器
|
|
1556
|
+
- `response?: (response: IResponse) => IResponse` - 响应拦截器
|
|
1557
|
+
|
|
1558
|
+
- `options.protocol` (可选) - 自定义 SSE Protocol 实例,默认使用 `AGUIProtocol`
|
|
1559
|
+
|
|
1560
|
+
**返回值:**
|
|
1561
|
+
|
|
1562
|
+
```typescript
|
|
1563
|
+
{
|
|
1564
|
+
agent: IAgentModule, // Agent 模块
|
|
1565
|
+
session: ISessionModule, // Session 模块
|
|
1566
|
+
message: IMessageModule, // Message 模块
|
|
1567
|
+
http: IHttpModule // HTTP 模块
|
|
1568
|
+
}
|
|
1569
|
+
```
|
|
1570
|
+
|
|
1571
|
+
### Agent 模块 API
|
|
1572
|
+
|
|
1573
|
+
#### agent.info
|
|
1574
|
+
|
|
1575
|
+
- **类型**:`Ref<IAgentInfo | null>`
|
|
1576
|
+
- **说明**:当前 Agent 的信息,包括名称、会话设置、预定义问题等
|
|
1577
|
+
|
|
1578
|
+
#### agent.isInfoLoading
|
|
1579
|
+
|
|
1580
|
+
- **类型**:`Ref<boolean>`
|
|
1581
|
+
- **说明**:Agent 信息是否正在加载
|
|
1582
|
+
|
|
1583
|
+
#### agent.getAgentInfo()
|
|
1584
|
+
|
|
1585
|
+
- **返回值**:`Promise<void>`
|
|
1586
|
+
- **说明**:获取 Agent 信息,结果保存在 `agent.info` 中
|
|
1587
|
+
|
|
1588
|
+
#### agent.chat(userInput, sessionCode, url?, config?)
|
|
1589
|
+
|
|
1590
|
+
- **参数**:
|
|
1591
|
+
- `userInput: string` - 用户输入的消息内容
|
|
1592
|
+
- `sessionCode: string` - 会话代码
|
|
1593
|
+
- `url?: string` - 自定义聊天接口地址,默认为 `'chat_completion/'`
|
|
1594
|
+
- `config?: IRequestConfig` - 额外的请求配置
|
|
1595
|
+
- **返回值**:`Promise<void>`
|
|
1596
|
+
- **说明**:开始与 AI 进行聊天,支持流式响应。会先创建一条用户消息,然后发起流式请求
|
|
1597
|
+
|
|
1598
|
+
#### agent.stopChat()
|
|
1599
|
+
|
|
1600
|
+
- **返回值**:`Promise<void>`
|
|
1601
|
+
- **说明**:停止当前的聊天流式响应
|
|
1602
|
+
|
|
1603
|
+
### Session 模块 API
|
|
1604
|
+
|
|
1605
|
+
#### session.list
|
|
1606
|
+
|
|
1607
|
+
- **类型**:`Ref<ISession[]>`
|
|
1608
|
+
- **说明**:会话列表
|
|
1609
|
+
|
|
1610
|
+
#### session.current
|
|
1611
|
+
|
|
1612
|
+
- **类型**:`Ref<ISession | null>`
|
|
1613
|
+
- **说明**:当前选中的会话
|
|
1614
|
+
|
|
1615
|
+
#### session.isListLoading / isCurrentLoading / isCreateLoading / isUpdateLoading / isDeleteLoading
|
|
1616
|
+
|
|
1617
|
+
- **类型**:`Ref<boolean>`
|
|
1618
|
+
- **说明**:各操作的加载状态
|
|
1619
|
+
|
|
1620
|
+
#### session.getSessions()
|
|
1621
|
+
|
|
1622
|
+
- **返回值**:`Promise<void>`
|
|
1623
|
+
- **说明**:获取会话列表,结果保存在 `session.list` 中
|
|
1624
|
+
|
|
1625
|
+
#### session.chooseSession(sessionCode)
|
|
1626
|
+
|
|
1627
|
+
- **参数**:`sessionCode: string` - 会话代码
|
|
1628
|
+
- **返回值**:`Promise<void>`
|
|
1629
|
+
- **说明**:选择会话,会停止当前聊天、设置 `session.current`,并自动加载该会话的消息列表
|
|
1630
|
+
|
|
1631
|
+
#### session.getSession(sessionCode)
|
|
1632
|
+
|
|
1633
|
+
- **参数**:`sessionCode: string` - 会话代码
|
|
1634
|
+
- **返回值**:`Promise<void>`
|
|
1635
|
+
- **说明**:获取单个会话信息,结果保存在 `session.current` 中
|
|
1636
|
+
|
|
1637
|
+
#### session.createSession(session)
|
|
1638
|
+
|
|
1639
|
+
- **参数**:`session: ISession` - 会话数据
|
|
1640
|
+
- **返回值**:`Promise<void>`
|
|
1641
|
+
- **说明**:创建新会话,创建成功后会添加到列表并自动选中
|
|
1642
|
+
|
|
1643
|
+
#### session.updateSession(session)
|
|
1644
|
+
|
|
1645
|
+
- **参数**:`session: ISession` - 会话数据
|
|
1646
|
+
- **返回值**:`Promise<void>`
|
|
1647
|
+
- **说明**:更新会话信息
|
|
1648
|
+
|
|
1649
|
+
#### session.deleteSession(sessionCode)
|
|
1650
|
+
|
|
1651
|
+
- **参数**:`sessionCode: string` - 会话代码
|
|
1652
|
+
- **返回值**:`Promise<void>`
|
|
1653
|
+
- **说明**:删除会话,如果删除的是当前会话,会自动切换到第一个会话
|
|
1654
|
+
|
|
1655
|
+
### Message 模块 API
|
|
1656
|
+
|
|
1657
|
+
#### message.list
|
|
1658
|
+
|
|
1659
|
+
- **类型**:`Ref<IMessage[]>`
|
|
1660
|
+
- **说明**:消息列表
|
|
1661
|
+
|
|
1662
|
+
#### message.isListLoading / isDeleteLoading / isBatchDeleteLoading
|
|
1663
|
+
|
|
1664
|
+
- **类型**:`Ref<boolean>`
|
|
1665
|
+
- **说明**:各操作的加载状态
|
|
1666
|
+
|
|
1667
|
+
#### message.getMessages(sessionCode)
|
|
1668
|
+
|
|
1669
|
+
- **参数**:`sessionCode: string` - 会话代码
|
|
1670
|
+
- **返回值**:`Promise<void>`
|
|
1671
|
+
- **说明**:获取指定会话的消息列表
|
|
1672
|
+
|
|
1673
|
+
#### message.plusMessage(message)
|
|
1674
|
+
|
|
1675
|
+
- **参数**:`message: IMessage` - 消息数据
|
|
1676
|
+
- **返回值**:`Promise<void>`
|
|
1677
|
+
- **说明**:添加新消息,会先调用接口创建,然后添加到列表最前面
|
|
1678
|
+
|
|
1679
|
+
#### message.modifyMessage(message)
|
|
1680
|
+
|
|
1681
|
+
- **参数**:`message: IMessage` - 消息数据
|
|
1682
|
+
- **返回值**:`void`
|
|
1683
|
+
- **说明**:修改消息(仅本地更新,不调用接口)
|
|
1684
|
+
|
|
1685
|
+
#### message.deleteMessage(id)
|
|
1686
|
+
|
|
1687
|
+
- **参数**:`id: string` - 消息 ID
|
|
1688
|
+
- **返回值**:`Promise<void>`
|
|
1689
|
+
- **说明**:删除单条消息
|
|
1690
|
+
|
|
1691
|
+
#### message.batchDeleteMessages(ids)
|
|
1692
|
+
|
|
1693
|
+
- **参数**:`ids: string[]` - 消息 ID 数组
|
|
1694
|
+
- **返回值**:`Promise<void>`
|
|
1695
|
+
- **说明**:批量删除多条消息
|
|
1696
|
+
|
|
1697
|
+
#### message.getCurrentLoadingMessage()
|
|
1698
|
+
|
|
1699
|
+
- **返回值**:`IMessage | undefined`
|
|
1700
|
+
- **说明**:获取当前正在加载或流式传输中的 AI 消息(状态为 Pending 或 Streaming)
|
|
1701
|
+
|
|
1702
|
+
#### message.getMessageByMessageId(id)
|
|
1703
|
+
|
|
1704
|
+
- **参数**:`id: string` - 消息 ID
|
|
1705
|
+
- **返回值**:`IMessage | undefined`
|
|
1706
|
+
- **说明**:根据消息 ID 从列表中查找消息
|
|
1707
|
+
|
|
1708
|
+
## 最佳实践
|
|
1709
|
+
|
|
1710
|
+
### 1. 错误处理
|
|
1711
|
+
|
|
1712
|
+
建议在使用 API 时添加适当的错误处理:
|
|
1713
|
+
|
|
1714
|
+
```typescript
|
|
1715
|
+
const { session } = useChatHelper({
|
|
1716
|
+
/* ... */
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
const loadSessions = async () => {
|
|
1720
|
+
try {
|
|
1721
|
+
await session.getSessions();
|
|
1722
|
+
} catch (error) {
|
|
1723
|
+
console.error('加载会话列表失败:', error);
|
|
1724
|
+
// 显示错误提示给用户
|
|
1725
|
+
}
|
|
1726
|
+
};
|
|
1727
|
+
```
|
|
1728
|
+
|
|
1729
|
+
### 2. 加载状态管理
|
|
1730
|
+
|
|
1731
|
+
利用响应式的加载状态提供更好的用户体验:
|
|
1732
|
+
|
|
1733
|
+
```vue
|
|
1734
|
+
<template>
|
|
1735
|
+
<div>
|
|
1736
|
+
<div v-if="session.isListLoading">
|
|
1737
|
+
<Spinner />
|
|
1738
|
+
加载中...
|
|
1739
|
+
</div>
|
|
1740
|
+
<ul v-else>
|
|
1741
|
+
<li
|
|
1742
|
+
v-for="item in session.list"
|
|
1743
|
+
:key="item.sessionCode"
|
|
1744
|
+
>
|
|
1745
|
+
{{ item.sessionName }}
|
|
1746
|
+
</li>
|
|
1747
|
+
</ul>
|
|
1748
|
+
</div>
|
|
1749
|
+
</template>
|
|
1750
|
+
```
|
|
1751
|
+
|
|
1752
|
+
### 3. 会话切换
|
|
1753
|
+
|
|
1754
|
+
切换会话时,`chooseSession` 会自动停止当前聊天并加载新会话的消息:
|
|
1755
|
+
|
|
1756
|
+
```typescript
|
|
1757
|
+
// ✅ 推荐:使用 chooseSession
|
|
1758
|
+
await session.chooseSession(sessionCode);
|
|
1759
|
+
|
|
1760
|
+
// ❌ 不推荐:手动操作
|
|
1761
|
+
agent.stopChat();
|
|
1762
|
+
session.current.value = session.list.value.find(s => s.sessionCode === sessionCode);
|
|
1763
|
+
message.getMessages(sessionCode);
|
|
1764
|
+
```
|
|
1765
|
+
|
|
1766
|
+
### 4. 消息 ID 使用
|
|
1767
|
+
|
|
1768
|
+
代码中使用 `messageId` 字段作为消息的唯一标识:
|
|
1769
|
+
|
|
1770
|
+
```typescript
|
|
1771
|
+
// ✅ 正确
|
|
1772
|
+
message.deleteMessage(msg.messageId);
|
|
1773
|
+
|
|
1774
|
+
// ❌ 错误
|
|
1775
|
+
message.deleteMessage(msg.id);
|
|
1776
|
+
```
|
|
1777
|
+
|
|
1778
|
+
### 5. 动态请求配置
|
|
1779
|
+
|
|
1780
|
+
对于需要动态获取的配置(如 token),使用函数形式:
|
|
1781
|
+
|
|
1782
|
+
```typescript
|
|
1783
|
+
useChatHelper({
|
|
1784
|
+
requestData: {
|
|
1785
|
+
urlPrefix: 'https://api.example.com/',
|
|
1786
|
+
// ✅ 使用函数动态获取
|
|
1787
|
+
headers: () => ({
|
|
1788
|
+
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
|
1789
|
+
'X-Request-ID': generateRequestId(),
|
|
1790
|
+
}),
|
|
1791
|
+
},
|
|
1792
|
+
});
|
|
1793
|
+
```
|
|
1794
|
+
|
|
1795
|
+
### 6. Protocol 钩子函数
|
|
1796
|
+
|
|
1797
|
+
使用 Protocol 钩子函数时,注意不要阻塞事件处理:
|
|
1798
|
+
|
|
1799
|
+
```typescript
|
|
1800
|
+
new AGUIProtocol({
|
|
1801
|
+
onMessage: async event => {
|
|
1802
|
+
// ❌ 不推荐:阻塞事件处理
|
|
1803
|
+
await someAsyncOperation();
|
|
1804
|
+
console.log(event);
|
|
1805
|
+
},
|
|
1806
|
+
|
|
1807
|
+
onMessage: event => {
|
|
1808
|
+
// ✅ 推荐:快速处理,异步操作不阻塞
|
|
1809
|
+
console.log(event);
|
|
1810
|
+
someAsyncOperation(); // 不 await
|
|
1811
|
+
},
|
|
1812
|
+
});
|
|
1813
|
+
```
|
|
1814
|
+
|
|
1815
|
+
### 7. 内存管理
|
|
1816
|
+
|
|
1817
|
+
在组件卸载时,记得停止正在进行的聊天:
|
|
1818
|
+
|
|
1819
|
+
```typescript
|
|
1820
|
+
import { onUnmounted } from 'vue';
|
|
1821
|
+
|
|
1822
|
+
const { agent } = useChatHelper({
|
|
1823
|
+
/* ... */
|
|
1824
|
+
});
|
|
1825
|
+
|
|
1826
|
+
onUnmounted(() => {
|
|
1827
|
+
agent.stopChat();
|
|
1828
|
+
});
|
|
1829
|
+
```
|
|
1830
|
+
|
|
1831
|
+
### 8. 消息状态判断
|
|
1832
|
+
|
|
1833
|
+
使用枚举类型进行状态判断,避免硬编码字符串:
|
|
1834
|
+
|
|
1835
|
+
```typescript
|
|
1836
|
+
import { MessageStatus, MessageRole } from '@blueking/chat-helper';
|
|
1837
|
+
|
|
1838
|
+
// ✅ 推荐:使用枚举
|
|
1839
|
+
if (msg.status === MessageStatus.Streaming && msg.role === MessageRole.Assistant) {
|
|
1840
|
+
// 显示流式动画
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// ❌ 不推荐:硬编码字符串
|
|
1844
|
+
if (msg.status === 'streaming' && msg.role === 'assistant') {
|
|
1845
|
+
// 显示流式动画
|
|
1846
|
+
}
|
|
1847
|
+
```
|
|
1848
|
+
|
|
1849
|
+
## 常见问题
|
|
1850
|
+
|
|
1851
|
+
### Q: 如何处理流式响应中的错误?
|
|
1852
|
+
|
|
1853
|
+
A: 使用 Protocol 的 `onError` 钩子函数:
|
|
1854
|
+
|
|
1855
|
+
```typescript
|
|
1856
|
+
useChatHelper({
|
|
1857
|
+
requestData: {
|
|
1858
|
+
/* ... */
|
|
1859
|
+
},
|
|
1860
|
+
protocol: new AGUIProtocol({
|
|
1861
|
+
onError: error => {
|
|
1862
|
+
console.error('流式响应错误:', error);
|
|
1863
|
+
// 显示错误提示
|
|
1864
|
+
showErrorMessage('AI 响应出错,请重试');
|
|
1865
|
+
},
|
|
1866
|
+
}),
|
|
1867
|
+
});
|
|
1868
|
+
```
|
|
1869
|
+
|
|
1870
|
+
### Q: 如何自定义不同端点的聊天行为?
|
|
1871
|
+
|
|
1872
|
+
A: 使用 `agent.chat` 的 `url` 和 `config` 参数:
|
|
1873
|
+
|
|
1874
|
+
```typescript
|
|
1875
|
+
// 使用不同的端点
|
|
1876
|
+
agent.chat(userInput, sessionCode, 'custom_chat/');
|
|
1877
|
+
|
|
1878
|
+
// 添加自定义参数
|
|
1879
|
+
agent.chat(userInput, sessionCode, undefined, {
|
|
1880
|
+
data: {
|
|
1881
|
+
temperature: 0.8,
|
|
1882
|
+
model: 'gpt-4',
|
|
1883
|
+
},
|
|
1884
|
+
});
|
|
1885
|
+
```
|
|
1886
|
+
|
|
1887
|
+
### Q: 消息列表如何实现自动滚动到底部?
|
|
1888
|
+
|
|
1889
|
+
A: 使用 Vue 的 `watch` 监听消息列表变化:
|
|
1890
|
+
|
|
1891
|
+
```typescript
|
|
1892
|
+
import { watch, nextTick, ref } from 'vue';
|
|
1893
|
+
|
|
1894
|
+
const messageContainer = ref<HTMLElement>();
|
|
1895
|
+
const { message } = useChatHelper({
|
|
1896
|
+
/* ... */
|
|
1897
|
+
});
|
|
1898
|
+
|
|
1899
|
+
watch(
|
|
1900
|
+
() => message.list.value.length,
|
|
1901
|
+
async () => {
|
|
1902
|
+
await nextTick();
|
|
1903
|
+
messageContainer.value?.scrollTo({
|
|
1904
|
+
top: messageContainer.value.scrollHeight,
|
|
1905
|
+
behavior: 'smooth',
|
|
1906
|
+
});
|
|
1907
|
+
},
|
|
1908
|
+
);
|
|
1909
|
+
```
|
|
1910
|
+
|
|
1911
|
+
### Q: 如何判断 AI 是否正在回复?
|
|
1912
|
+
|
|
1913
|
+
A: 使用 `message.getCurrentLoadingMessage()` 方法:
|
|
1914
|
+
|
|
1915
|
+
```typescript
|
|
1916
|
+
const { message } = useChatHelper({
|
|
1917
|
+
/* ... */
|
|
1918
|
+
});
|
|
1919
|
+
|
|
1920
|
+
const isAIResponding = computed(() => {
|
|
1921
|
+
return message.getCurrentLoadingMessage() !== undefined;
|
|
1922
|
+
});
|
|
1923
|
+
```
|
|
1924
|
+
|
|
1925
|
+
### Q: 如何实现多租户或多应用场景?
|
|
1926
|
+
|
|
1927
|
+
A: 使用动态的 `data` 配置:
|
|
1928
|
+
|
|
1929
|
+
```typescript
|
|
1930
|
+
const currentAppId = ref('app-1');
|
|
1931
|
+
|
|
1932
|
+
useChatHelper({
|
|
1933
|
+
requestData: {
|
|
1934
|
+
urlPrefix: 'https://api.example.com/',
|
|
1935
|
+
data: () => ({
|
|
1936
|
+
app_id: currentAppId.value,
|
|
1937
|
+
tenant_id: getTenantId(),
|
|
1938
|
+
}),
|
|
1939
|
+
},
|
|
1940
|
+
});
|
|
1941
|
+
```
|
|
1942
|
+
|
|
1943
|
+
### Q: 如何自定义流式事件的处理逻辑?
|
|
1944
|
+
|
|
1945
|
+
A: 继承 `AGUIProtocol` 并重写对应的事件处理方法:
|
|
1946
|
+
|
|
1947
|
+
```typescript
|
|
1948
|
+
class MyProtocol extends AGUIProtocol {
|
|
1949
|
+
handleTextMessageChunkEvent(event: ITextMessageChunkEvent) {
|
|
1950
|
+
// 自定义逻辑:例如敏感词过滤
|
|
1951
|
+
const filteredText = filterSensitiveWords(event.delta);
|
|
1952
|
+
|
|
1953
|
+
// 调用父类方法继续处理
|
|
1954
|
+
super.handleTextMessageChunkEvent({
|
|
1955
|
+
...event,
|
|
1956
|
+
delta: filteredText,
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
useChatHelper({
|
|
1962
|
+
requestData: {
|
|
1963
|
+
/* ... */
|
|
1964
|
+
},
|
|
1965
|
+
protocol: new MyProtocol(),
|
|
1966
|
+
});
|
|
1967
|
+
```
|
|
1968
|
+
|
|
1969
|
+
### Q: 如何实现消息的本地缓存?
|
|
1970
|
+
|
|
1971
|
+
A: 使用 `watch` 监听消息变化并保存到本地存储:
|
|
1972
|
+
|
|
1973
|
+
```typescript
|
|
1974
|
+
import { watch } from 'vue';
|
|
1975
|
+
|
|
1976
|
+
const { message, session } = useChatHelper({
|
|
1977
|
+
/* ... */
|
|
1978
|
+
});
|
|
1979
|
+
|
|
1980
|
+
// 监听消息变化并缓存
|
|
1981
|
+
watch(
|
|
1982
|
+
() => message.list.value,
|
|
1983
|
+
newList => {
|
|
1984
|
+
if (session.current?.sessionCode) {
|
|
1985
|
+
localStorage.setItem(`messages_${session.current.sessionCode}`, JSON.stringify(newList));
|
|
1986
|
+
}
|
|
1987
|
+
},
|
|
1988
|
+
{ deep: true },
|
|
1989
|
+
);
|
|
1990
|
+
|
|
1991
|
+
// 加载缓存
|
|
1992
|
+
const loadCachedMessages = (sessionCode: string) => {
|
|
1993
|
+
const cached = localStorage.getItem(`messages_${sessionCode}`);
|
|
1994
|
+
if (cached) {
|
|
1995
|
+
message.list.value = JSON.parse(cached);
|
|
1996
|
+
}
|
|
1997
|
+
};
|
|
1998
|
+
```
|
|
1999
|
+
|
|
2000
|
+
## 贡献指南
|
|
2001
|
+
|
|
2002
|
+
欢迎提交 Issue 和 Pull Request!
|
|
2003
|
+
|
|
2004
|
+
在提交 PR 之前,请确保:
|
|
2005
|
+
|
|
2006
|
+
1. 代码通过 ESLint 检查
|
|
2007
|
+
2. 添加了必要的类型定义
|
|
2008
|
+
3. 更新了相关文档
|
|
2009
|
+
|
|
2010
|
+
## 更新日志
|
|
2011
|
+
|
|
2012
|
+
查看 [CHANGELOG.md](./CHANGELOG.md) 了解版本更新历史。
|
|
2013
|
+
|
|
1524
2014
|
## License
|
|
1525
2015
|
|
|
1526
2016
|
MIT License
|