@blueking/chat-helper 0.0.1-beta.8 → 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 +701 -167
- package/dist/agent/type.d.ts +27 -2
- package/dist/agent/type.ts.js +1 -1
- package/dist/agent/use-agent.d.ts +460 -413
- package/dist/agent/use-agent.ts.js +191 -44
- package/dist/event/ag-ui.d.ts +37 -7
- package/dist/event/ag-ui.ts.js +225 -165
- 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 +2989 -2845
- 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 -142
- package/dist/message/type.ts.js +16 -15
- package/dist/message/use-message.d.ts +839 -819
- package/dist/message/use-message.ts.js +230 -37
- package/dist/session/type.d.ts +10 -0
- package/dist/session/use-session.d.ts +1921 -1857
- package/dist/session/use-session.ts.js +198 -33
- package/dist/type.d.ts +1 -1
- 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` 返回一个包含四个模块的对象:
|
|
@@ -41,16 +80,16 @@ const { agent } = chatHelper;
|
|
|
41
80
|
// 响应式数据
|
|
42
81
|
agent.info; // Ref<IAgentInfo | null> - agent 信息
|
|
43
82
|
agent.isInfoLoading; // Ref<boolean> - agent 信息加载状态
|
|
44
|
-
agent.userInput; // Ref<string> - 用户输入内容
|
|
45
83
|
|
|
46
84
|
// 方法
|
|
47
85
|
agent.getAgentInfo(); // () => Promise<void> - 获取 agent 信息
|
|
48
|
-
agent.chat(sessionCode, url?, config?); // (sessionCode: string, url?: string, config?: IRequestConfig) => Promise<void> - 开始聊天(流式响应)
|
|
86
|
+
agent.chat(userInput, sessionCode, url?, config?); // (userInput: string, sessionCode: string, url?: string, config?: IRequestConfig) => Promise<void> - 开始聊天(流式响应)
|
|
49
87
|
agent.stopChat(); // () => Promise<void> - 停止聊天
|
|
50
88
|
```
|
|
51
89
|
|
|
52
90
|
**参数说明**:
|
|
53
91
|
|
|
92
|
+
- `userInput`: 用户输入的消息内容,必需参数
|
|
54
93
|
- `sessionCode`: 会话代码,必需参数,标识当前聊天会话
|
|
55
94
|
- `url`: 可选参数,自定义聊天接口地址,默认为 `'chat_completion/'`
|
|
56
95
|
- `config`: 可选参数,额外的请求配置(如自定义 headers、data 等)
|
|
@@ -62,11 +101,11 @@ agent.stopChat(); // () => Promise<void> - 停止聊天
|
|
|
62
101
|
<div>
|
|
63
102
|
<div v-if="agent.isInfoLoading">加载中...</div>
|
|
64
103
|
<div v-else-if="agent.info">
|
|
65
|
-
<h2>{{ agent.info.
|
|
66
|
-
<p>{{ agent.info.
|
|
104
|
+
<h2>{{ agent.info.agentName }}</h2>
|
|
105
|
+
<p>{{ agent.info.conversationSettings?.openingRemark }}</p>
|
|
67
106
|
</div>
|
|
68
107
|
<input
|
|
69
|
-
v-model="
|
|
108
|
+
v-model="userInput"
|
|
70
109
|
placeholder="请输入消息..."
|
|
71
110
|
/>
|
|
72
111
|
<button @click="startChat">开始对话</button>
|
|
@@ -75,20 +114,23 @@ agent.stopChat(); // () => Promise<void> - 停止聊天
|
|
|
75
114
|
</template>
|
|
76
115
|
|
|
77
116
|
<script setup lang="ts">
|
|
78
|
-
import { onMounted } from 'vue';
|
|
117
|
+
import { onMounted, ref } from 'vue';
|
|
79
118
|
import { useChatHelper } from '@blueking/chat-helper';
|
|
80
119
|
|
|
81
120
|
const { agent, session } = useChatHelper({
|
|
82
121
|
/* ... */
|
|
83
122
|
});
|
|
84
123
|
|
|
124
|
+
const userInput = ref('');
|
|
125
|
+
|
|
85
126
|
onMounted(() => {
|
|
86
127
|
agent.getAgentInfo();
|
|
87
128
|
});
|
|
88
129
|
|
|
89
130
|
const startChat = () => {
|
|
90
|
-
if (session.current?.sessionCode) {
|
|
91
|
-
agent.chat(session.current.sessionCode);
|
|
131
|
+
if (session.current?.sessionCode && userInput.value.trim()) {
|
|
132
|
+
agent.chat(userInput.value, session.current.sessionCode);
|
|
133
|
+
userInput.value = ''; // 清空输入框
|
|
92
134
|
}
|
|
93
135
|
};
|
|
94
136
|
</script>
|
|
@@ -98,10 +140,10 @@ agent.stopChat(); // () => Promise<void> - 停止聊天
|
|
|
98
140
|
|
|
99
141
|
```typescript
|
|
100
142
|
// 使用自定义 URL
|
|
101
|
-
agent.chat(sessionCode, 'custom_chat_endpoint/');
|
|
143
|
+
agent.chat('你好', sessionCode, 'custom_chat_endpoint/');
|
|
102
144
|
|
|
103
145
|
// 使用自定义请求配置
|
|
104
|
-
agent.chat(sessionCode, undefined, {
|
|
146
|
+
agent.chat('你好', sessionCode, undefined, {
|
|
105
147
|
headers: {
|
|
106
148
|
'X-Custom-Header': 'value',
|
|
107
149
|
},
|
|
@@ -112,7 +154,7 @@ agent.chat(sessionCode, undefined, {
|
|
|
112
154
|
});
|
|
113
155
|
|
|
114
156
|
// 同时使用自定义 URL 和配置
|
|
115
|
-
agent.chat(sessionCode, 'custom_chat_endpoint/', {
|
|
157
|
+
agent.chat('你好', sessionCode, 'custom_chat_endpoint/', {
|
|
116
158
|
headers: {
|
|
117
159
|
'X-Custom-Header': 'value',
|
|
118
160
|
},
|
|
@@ -156,7 +198,7 @@ session.deleteSession(sessionCode); // (sessionCode: string) => Promise<void> -
|
|
|
156
198
|
@click="session.chooseSession(item.sessionCode)"
|
|
157
199
|
:class="{ active: session.current?.sessionCode === item.sessionCode }"
|
|
158
200
|
>
|
|
159
|
-
{{ item.
|
|
201
|
+
{{ item.sessionName }}
|
|
160
202
|
</li>
|
|
161
203
|
</ul>
|
|
162
204
|
<button @click="createNewSession">新建会话</button>
|
|
@@ -177,7 +219,8 @@ session.deleteSession(sessionCode); // (sessionCode: string) => Promise<void> -
|
|
|
177
219
|
|
|
178
220
|
const createNewSession = () => {
|
|
179
221
|
session.createSession({
|
|
180
|
-
|
|
222
|
+
sessionName: '新会话',
|
|
223
|
+
sessionCode: `session_${Date.now()}`,
|
|
181
224
|
// ... 其他会话属性
|
|
182
225
|
});
|
|
183
226
|
};
|
|
@@ -199,25 +242,23 @@ message.isBatchDeleteLoading; // Ref<boolean> - 批量删除加载状态
|
|
|
199
242
|
|
|
200
243
|
// 方法
|
|
201
244
|
message.getMessages(sessionCode); // (sessionCode: string) => Promise<void> - 获取消息列表
|
|
202
|
-
message.plusLatestMessage(sessionCode); // (sessionCode: string) => Promise<void> - 从接口获取最新的消息并添加到列表前面
|
|
203
245
|
message.plusMessage(message); // (message: IMessage) => Promise<void> - 添加消息
|
|
204
246
|
message.modifyMessage(message); // (message: IMessage) => void - 修改消息
|
|
205
|
-
message.deleteMessage(id); // (id:
|
|
206
|
-
message.batchDeleteMessages(ids); // (ids:
|
|
247
|
+
message.deleteMessage(id); // (id: string) => Promise<void> - 删除消息
|
|
248
|
+
message.batchDeleteMessages(ids); // (ids: string[]) => Promise<void> - 批量删除消息
|
|
207
249
|
message.getCurrentLoadingMessage(); // () => IMessage | undefined - 获取当前加载中的消息
|
|
208
|
-
message.
|
|
250
|
+
message.getMessageByMessageId(id); // (id: string) => IMessage | undefined - 根据消息 ID 获取消息
|
|
209
251
|
```
|
|
210
252
|
|
|
211
253
|
**方法说明**:
|
|
212
254
|
|
|
213
255
|
- `getMessages`: 获取指定会话的所有消息列表
|
|
214
|
-
- `
|
|
215
|
-
- `
|
|
216
|
-
- `
|
|
217
|
-
- `
|
|
218
|
-
- `
|
|
219
|
-
- `
|
|
220
|
-
- `getMessageById`: 根据 ID 从列表中查找消息
|
|
256
|
+
- `plusMessage`: 主动添加一条新消息到列表(会先调用后端接口创建消息,然后添加到列表最前面)
|
|
257
|
+
- `modifyMessage`: 修改已存在的消息(本地更新,不调用接口)
|
|
258
|
+
- `deleteMessage`: 删除单条消息(调用接口删除并从列表中移除)
|
|
259
|
+
- `batchDeleteMessages`: 批量删除多条消息(调用接口批量删除并从列表中移除)
|
|
260
|
+
- `getCurrentLoadingMessage`: 获取当前正在加载/流式传输中的 AI 响应消息(状态为 Pending 或 Streaming)
|
|
261
|
+
- `getMessageByMessageId`: 根据消息 ID 从列表中查找消息
|
|
221
262
|
|
|
222
263
|
**使用示例**:
|
|
223
264
|
|
|
@@ -226,21 +267,20 @@ message.getMessageById(id); // (id: number) => IMessage | undefined - 根据 ID
|
|
|
226
267
|
<div class="message-list">
|
|
227
268
|
<div
|
|
228
269
|
v-for="msg in message.list"
|
|
229
|
-
:key="msg.
|
|
270
|
+
:key="msg.messageId"
|
|
230
271
|
class="message-item"
|
|
231
272
|
>
|
|
232
273
|
<div class="role">{{ msg.role }}</div>
|
|
233
274
|
<div class="content">
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
:
|
|
237
|
-
>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
</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 || '' }}
|
|
241
281
|
</div>
|
|
242
282
|
</div>
|
|
243
|
-
<button @click="message.deleteMessage(msg.
|
|
283
|
+
<button @click="message.deleteMessage(msg.messageId)">删除</button>
|
|
244
284
|
</div>
|
|
245
285
|
<button
|
|
246
286
|
@click="batchDelete"
|
|
@@ -259,20 +299,13 @@ message.getMessageById(id); // (id: number) => IMessage | undefined - 根据 ID
|
|
|
259
299
|
/* ... */
|
|
260
300
|
});
|
|
261
301
|
|
|
262
|
-
const selectedIds = ref<
|
|
302
|
+
const selectedIds = ref<string[]>([]);
|
|
263
303
|
|
|
264
304
|
const batchDelete = () => {
|
|
265
305
|
if (selectedIds.value.length > 0) {
|
|
266
306
|
message.batchDeleteMessages(selectedIds.value);
|
|
267
307
|
}
|
|
268
308
|
};
|
|
269
|
-
|
|
270
|
-
// 刷新最新消息(例如在多端同步场景)
|
|
271
|
-
const refreshLatestMessage = () => {
|
|
272
|
-
if (session.current?.sessionCode) {
|
|
273
|
-
message.plusLatestMessage(session.current.sessionCode);
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
309
|
</script>
|
|
277
310
|
```
|
|
278
311
|
|
|
@@ -758,7 +791,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
758
791
|
|
|
759
792
|
### 场景 1:带输入框的聊天界面
|
|
760
793
|
|
|
761
|
-
|
|
794
|
+
使用独立的 `ref` 管理用户输入:
|
|
762
795
|
|
|
763
796
|
```vue
|
|
764
797
|
<template>
|
|
@@ -773,7 +806,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
773
806
|
</div>
|
|
774
807
|
<div class="input-box">
|
|
775
808
|
<textarea
|
|
776
|
-
v-model="
|
|
809
|
+
v-model="userInput"
|
|
777
810
|
@keydown.enter.prevent="handleSend"
|
|
778
811
|
placeholder="输入消息,按 Enter 发送..."
|
|
779
812
|
:disabled="isStreaming"
|
|
@@ -796,15 +829,18 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
796
829
|
/* ... */
|
|
797
830
|
});
|
|
798
831
|
const isStreaming = ref(false);
|
|
832
|
+
const userInput = ref('');
|
|
799
833
|
|
|
800
834
|
const canSend = computed(
|
|
801
|
-
() => !isStreaming.value &&
|
|
835
|
+
() => !isStreaming.value && userInput.value.trim().length > 0 && session.current?.sessionCode,
|
|
802
836
|
);
|
|
803
837
|
|
|
804
838
|
const handleSend = () => {
|
|
805
839
|
if (canSend.value) {
|
|
806
840
|
isStreaming.value = true;
|
|
807
|
-
|
|
841
|
+
const input = userInput.value;
|
|
842
|
+
userInput.value = ''; // 立即清空输入框
|
|
843
|
+
agent.chat(input, session.current!.sessionCode).finally(() => {
|
|
808
844
|
isStreaming.value = false;
|
|
809
845
|
});
|
|
810
846
|
}
|
|
@@ -833,13 +869,13 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
833
869
|
<div class="messages">
|
|
834
870
|
<div
|
|
835
871
|
v-for="msg in message.list"
|
|
836
|
-
:key="msg.
|
|
837
|
-
@click="toggleSelection(msg.
|
|
838
|
-
:class="{ selected: selectedIds.includes(msg.
|
|
872
|
+
:key="msg.messageId"
|
|
873
|
+
@click="toggleSelection(msg.messageId)"
|
|
874
|
+
:class="{ selected: selectedIds.includes(msg.messageId) }"
|
|
839
875
|
>
|
|
840
876
|
<input
|
|
841
877
|
type="checkbox"
|
|
842
|
-
:checked="selectedIds.includes(msg.
|
|
878
|
+
:checked="selectedIds.includes(msg.messageId)"
|
|
843
879
|
@click.stop
|
|
844
880
|
/>
|
|
845
881
|
<!-- 消息内容 -->
|
|
@@ -855,9 +891,9 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
855
891
|
const { message } = useChatHelper({
|
|
856
892
|
/* ... */
|
|
857
893
|
});
|
|
858
|
-
const selectedIds = ref<
|
|
894
|
+
const selectedIds = ref<string[]>([]);
|
|
859
895
|
|
|
860
|
-
const toggleSelection = (id:
|
|
896
|
+
const toggleSelection = (id: string) => {
|
|
861
897
|
const index = selectedIds.value.indexOf(id);
|
|
862
898
|
if (index > -1) {
|
|
863
899
|
selectedIds.value.splice(index, 1);
|
|
@@ -867,7 +903,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
867
903
|
};
|
|
868
904
|
|
|
869
905
|
const selectAll = () => {
|
|
870
|
-
selectedIds.value = message.list.value.filter(msg => msg.
|
|
906
|
+
selectedIds.value = message.list.value.filter(msg => msg.messageId).map(msg => msg.messageId!);
|
|
871
907
|
};
|
|
872
908
|
|
|
873
909
|
const clearSelection = () => {
|
|
@@ -883,76 +919,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
883
919
|
</script>
|
|
884
920
|
```
|
|
885
921
|
|
|
886
|
-
### 场景 3
|
|
887
|
-
|
|
888
|
-
在多端同步场景下,使用 `plusLatestMessage` 获取最新消息:
|
|
889
|
-
|
|
890
|
-
```vue
|
|
891
|
-
<template>
|
|
892
|
-
<div class="sync-chat">
|
|
893
|
-
<div class="header">
|
|
894
|
-
<button
|
|
895
|
-
@click="syncLatest"
|
|
896
|
-
:disabled="isSyncing"
|
|
897
|
-
>
|
|
898
|
-
{{ isSyncing ? '同步中...' : '同步最新消息' }}
|
|
899
|
-
</button>
|
|
900
|
-
<span v-if="lastSyncTime"> 上次同步: {{ lastSyncTime.toLocaleTimeString() }} </span>
|
|
901
|
-
</div>
|
|
902
|
-
|
|
903
|
-
<div class="messages">
|
|
904
|
-
<div
|
|
905
|
-
v-for="msg in message.list"
|
|
906
|
-
:key="msg.id"
|
|
907
|
-
>
|
|
908
|
-
<!-- 消息渲染 -->
|
|
909
|
-
</div>
|
|
910
|
-
</div>
|
|
911
|
-
</div>
|
|
912
|
-
</template>
|
|
913
|
-
|
|
914
|
-
<script setup lang="ts">
|
|
915
|
-
import { ref, onMounted, onUnmounted } from 'vue';
|
|
916
|
-
import { useChatHelper } from '@blueking/chat-helper';
|
|
917
|
-
|
|
918
|
-
const { message, session } = useChatHelper({
|
|
919
|
-
/* ... */
|
|
920
|
-
});
|
|
921
|
-
const isSyncing = ref(false);
|
|
922
|
-
const lastSyncTime = ref<Date | null>(null);
|
|
923
|
-
let syncTimer: number | null = null;
|
|
924
|
-
|
|
925
|
-
// 手动同步
|
|
926
|
-
const syncLatest = async () => {
|
|
927
|
-
if (!session.current?.sessionCode || isSyncing.value) return;
|
|
928
|
-
|
|
929
|
-
isSyncing.value = true;
|
|
930
|
-
try {
|
|
931
|
-
await message.plusLatestMessage(session.current.sessionCode);
|
|
932
|
-
lastSyncTime.value = new Date();
|
|
933
|
-
} catch (error) {
|
|
934
|
-
console.error('同步失败:', error);
|
|
935
|
-
} finally {
|
|
936
|
-
isSyncing.value = false;
|
|
937
|
-
}
|
|
938
|
-
};
|
|
939
|
-
|
|
940
|
-
// 自动同步(每30秒)
|
|
941
|
-
onMounted(() => {
|
|
942
|
-
syncTimer = window.setInterval(() => {
|
|
943
|
-
syncLatest();
|
|
944
|
-
}, 30000);
|
|
945
|
-
});
|
|
946
|
-
|
|
947
|
-
onUnmounted(() => {
|
|
948
|
-
if (syncTimer) {
|
|
949
|
-
clearInterval(syncTimer);
|
|
950
|
-
}
|
|
951
|
-
});
|
|
952
|
-
</script>
|
|
953
|
-
```
|
|
954
|
-
|
|
955
|
-
### 场景 4:自定义聊天参数
|
|
922
|
+
### 场景 3:自定义聊天参数
|
|
956
923
|
|
|
957
924
|
根据不同场景使用不同的聊天配置:
|
|
958
925
|
|
|
@@ -989,7 +956,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
989
956
|
</div>
|
|
990
957
|
|
|
991
958
|
<textarea
|
|
992
|
-
v-model="
|
|
959
|
+
v-model="userInput"
|
|
993
960
|
placeholder="输入消息..."
|
|
994
961
|
/>
|
|
995
962
|
<button @click="sendWithConfig">发送</button>
|
|
@@ -1004,12 +971,13 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1004
971
|
/* ... */
|
|
1005
972
|
});
|
|
1006
973
|
|
|
974
|
+
const userInput = ref('');
|
|
1007
975
|
const temperature = ref(0.7);
|
|
1008
976
|
const maxTokens = ref(1000);
|
|
1009
977
|
const mode = ref('default');
|
|
1010
978
|
|
|
1011
979
|
const sendWithConfig = () => {
|
|
1012
|
-
if (!session.current?.sessionCode) return;
|
|
980
|
+
if (!session.current?.sessionCode || !userInput.value.trim()) return;
|
|
1013
981
|
|
|
1014
982
|
// 根据模式设置不同的端点
|
|
1015
983
|
const endpoint =
|
|
@@ -1020,7 +988,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1020
988
|
: undefined;
|
|
1021
989
|
|
|
1022
990
|
// 自定义请求参数
|
|
1023
|
-
agent.chat(session.current.sessionCode, endpoint, {
|
|
991
|
+
agent.chat(userInput.value, session.current.sessionCode, endpoint, {
|
|
1024
992
|
data: {
|
|
1025
993
|
temperature: temperature.value,
|
|
1026
994
|
max_tokens: maxTokens.value,
|
|
@@ -1030,6 +998,8 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1030
998
|
'X-Chat-Mode': mode.value,
|
|
1031
999
|
},
|
|
1032
1000
|
});
|
|
1001
|
+
|
|
1002
|
+
userInput.value = ''; // 清空输入框
|
|
1033
1003
|
};
|
|
1034
1004
|
</script>
|
|
1035
1005
|
```
|
|
@@ -1051,7 +1021,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1051
1021
|
@click="session.chooseSession(item.sessionCode)"
|
|
1052
1022
|
:class="{ active: session.current?.sessionCode === item.sessionCode }"
|
|
1053
1023
|
>
|
|
1054
|
-
{{ item.
|
|
1024
|
+
{{ item.sessionName }}
|
|
1055
1025
|
<button @click.stop="session.deleteSession(item.sessionCode)">删除</button>
|
|
1056
1026
|
</li>
|
|
1057
1027
|
</ul>
|
|
@@ -1066,49 +1036,25 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1066
1036
|
>
|
|
1067
1037
|
<div
|
|
1068
1038
|
v-for="msg in message.list"
|
|
1069
|
-
:key="msg.
|
|
1039
|
+
:key="msg.messageId"
|
|
1070
1040
|
:class="['message-item', msg.role]"
|
|
1071
1041
|
>
|
|
1072
1042
|
<div class="message-content">
|
|
1073
|
-
<template
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
>
|
|
1077
|
-
|
|
1078
|
-
<div
|
|
1079
|
-
v-if="content.type === 'text'"
|
|
1080
|
-
class="text-content"
|
|
1081
|
-
>
|
|
1082
|
-
{{ content.data }}
|
|
1083
|
-
</div>
|
|
1084
|
-
|
|
1085
|
-
<!-- 思考过程 -->
|
|
1086
|
-
<div
|
|
1087
|
-
v-else-if="content.type === 'thinking'"
|
|
1088
|
-
class="thinking-content"
|
|
1089
|
-
>
|
|
1090
|
-
<strong>{{ content.data.title }}</strong>
|
|
1091
|
-
<p>{{ content.data.text }}</p>
|
|
1092
|
-
</div>
|
|
1093
|
-
|
|
1094
|
-
<!-- 工具调用 -->
|
|
1095
|
-
<div
|
|
1096
|
-
v-else-if="content.type === 'tool_call'"
|
|
1097
|
-
class="tool-call-content"
|
|
1098
|
-
>
|
|
1099
|
-
<strong>调用工具: {{ content.data.toolCallName }}</strong>
|
|
1100
|
-
<pre>{{ content.data.args }}</pre>
|
|
1101
|
-
</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 || '' }}
|
|
1102
1048
|
</template>
|
|
1103
1049
|
</div>
|
|
1104
|
-
<button @click="message.deleteMessage(msg.
|
|
1050
|
+
<button @click="message.deleteMessage(msg.messageId)">删除</button>
|
|
1105
1051
|
</div>
|
|
1106
1052
|
</div>
|
|
1107
1053
|
|
|
1108
1054
|
<!-- 输入区域 -->
|
|
1109
1055
|
<div class="input-area">
|
|
1110
1056
|
<input
|
|
1111
|
-
v-model="
|
|
1057
|
+
v-model="userInput"
|
|
1112
1058
|
@keyup.enter="sendMessage"
|
|
1113
1059
|
placeholder="输入消息..."
|
|
1114
1060
|
:disabled="isStreaming"
|
|
@@ -1135,6 +1081,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1135
1081
|
import { useChatHelper, AGUIProtocol } from '@blueking/chat-helper';
|
|
1136
1082
|
|
|
1137
1083
|
const isStreaming = ref(false);
|
|
1084
|
+
const userInput = ref('');
|
|
1138
1085
|
|
|
1139
1086
|
const { agent, session, message } = useChatHelper({
|
|
1140
1087
|
requestData: {
|
|
@@ -1186,14 +1133,15 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1186
1133
|
|
|
1187
1134
|
const createNewSession = () => {
|
|
1188
1135
|
session.createSession({
|
|
1189
|
-
|
|
1136
|
+
sessionName: `会话 ${new Date().toLocaleString()}`,
|
|
1190
1137
|
sessionCode: `session_${Date.now()}`,
|
|
1191
1138
|
});
|
|
1192
1139
|
};
|
|
1193
1140
|
|
|
1194
1141
|
const sendMessage = () => {
|
|
1195
|
-
if (session.current?.sessionCode &&
|
|
1196
|
-
agent.chat(session.current.sessionCode);
|
|
1142
|
+
if (session.current?.sessionCode && userInput.value.trim()) {
|
|
1143
|
+
agent.chat(userInput.value, session.current.sessionCode);
|
|
1144
|
+
userInput.value = ''; // 清空输入框
|
|
1197
1145
|
}
|
|
1198
1146
|
};
|
|
1199
1147
|
</script>
|
|
@@ -1267,7 +1215,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1267
1215
|
<div class="messages">
|
|
1268
1216
|
<div
|
|
1269
1217
|
v-for="msg in message.list"
|
|
1270
|
-
:key="msg.
|
|
1218
|
+
:key="msg.messageId"
|
|
1271
1219
|
class="message"
|
|
1272
1220
|
>
|
|
1273
1221
|
<template
|
|
@@ -1306,6 +1254,12 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1306
1254
|
|
|
1307
1255
|
<!-- 输入区域 -->
|
|
1308
1256
|
<div class="input-area">
|
|
1257
|
+
<input
|
|
1258
|
+
v-model="userInput"
|
|
1259
|
+
@keyup.enter="sendMessage"
|
|
1260
|
+
placeholder="输入消息..."
|
|
1261
|
+
:disabled="isStreaming"
|
|
1262
|
+
/>
|
|
1309
1263
|
<button
|
|
1310
1264
|
@click="sendMessage"
|
|
1311
1265
|
:disabled="isStreaming"
|
|
@@ -1334,6 +1288,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1334
1288
|
|
|
1335
1289
|
const isStreaming = ref(false);
|
|
1336
1290
|
const status = ref<{ type: string; message: string } | null>(null);
|
|
1291
|
+
const userInput = ref('');
|
|
1337
1292
|
|
|
1338
1293
|
// 自定义 Protocol,添加各种钩子
|
|
1339
1294
|
class CustomChatProtocol extends AGUIProtocol {
|
|
@@ -1370,7 +1325,7 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1370
1325
|
}
|
|
1371
1326
|
}
|
|
1372
1327
|
|
|
1373
|
-
const { agent } = useChatHelper({
|
|
1328
|
+
const { agent, session, message } = useChatHelper({
|
|
1374
1329
|
requestData: {
|
|
1375
1330
|
urlPrefix: 'https://your-api-domain.com/api/',
|
|
1376
1331
|
},
|
|
@@ -1410,7 +1365,10 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1410
1365
|
});
|
|
1411
1366
|
|
|
1412
1367
|
const sendMessage = () => {
|
|
1413
|
-
|
|
1368
|
+
if (session.current?.sessionCode && userInput.value.trim()) {
|
|
1369
|
+
agent.chat(userInput.value, session.current.sessionCode);
|
|
1370
|
+
userInput.value = ''; // 清空输入框
|
|
1371
|
+
}
|
|
1414
1372
|
};
|
|
1415
1373
|
|
|
1416
1374
|
const stopStreaming = () => {
|
|
@@ -1464,19 +1422,595 @@ class SafeProtocol extends AGUIProtocol {
|
|
|
1464
1422
|
|
|
1465
1423
|
```typescript
|
|
1466
1424
|
import type {
|
|
1425
|
+
// 主要配置和模块类型
|
|
1467
1426
|
IUseChatHelperOptions,
|
|
1468
1427
|
IAgentModule,
|
|
1469
1428
|
ISessionModule,
|
|
1470
1429
|
IMessageModule,
|
|
1471
1430
|
IHttpModule,
|
|
1431
|
+
|
|
1432
|
+
// 核心数据类型
|
|
1472
1433
|
IMessage,
|
|
1473
1434
|
ISession,
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1435
|
+
IAgentInfo,
|
|
1436
|
+
|
|
1437
|
+
// 消息相关枚举
|
|
1438
|
+
MessageRole, // 消息角色:user, assistant, system, tool, activity, reasoning 等
|
|
1439
|
+
MessageStatus, // 消息状态:pending, streaming, complete, error, stop
|
|
1440
|
+
MessageType, // 消息类型:text, binary, function
|
|
1441
|
+
|
|
1442
|
+
// 协议相关
|
|
1443
|
+
ISSEProtocol,
|
|
1444
|
+
AGUIProtocol,
|
|
1445
|
+
|
|
1446
|
+
// 事件类型(用于自定义 Protocol)
|
|
1447
|
+
IEvent,
|
|
1448
|
+
IRunStartedEvent,
|
|
1449
|
+
IRunFinishedEvent,
|
|
1450
|
+
IRunErrorEvent,
|
|
1451
|
+
ITextMessageChunkEvent,
|
|
1452
|
+
IThinkingStartEvent,
|
|
1453
|
+
IThinkingEndEvent,
|
|
1454
|
+
IToolCallStartEvent,
|
|
1455
|
+
// ... 更多事件类型
|
|
1477
1456
|
} from '@blueking/chat-helper';
|
|
1478
1457
|
```
|
|
1479
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
|
+
|
|
1480
2014
|
## License
|
|
1481
2015
|
|
|
1482
2016
|
MIT License
|