agent-configs 1.0.0
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 +223 -0
- package/agents/architect.md +211 -0
- package/agents/code-reviewer.md +104 -0
- package/agents/planner.md +119 -0
- package/agents/refactor-cleaner.md +306 -0
- package/agents/security-reviewer.md +545 -0
- package/agents/tdd-guide.md +280 -0
- package/bundles/bk-chat-bundle/README.md +48 -0
- package/bundles/bk-chat-bundle/manifest.json +10 -0
- package/bundles/continuous-learning/.claude/commands/evolve.md +190 -0
- package/bundles/continuous-learning/.claude/commands/instinct-status.md +64 -0
- package/bundles/continuous-learning/.claude/commands/learn.md +83 -0
- package/bundles/continuous-learning/.claude/hooks/learning-end.js +85 -0
- package/bundles/continuous-learning/.claude/hooks/observe.js +131 -0
- package/bundles/continuous-learning/.claude/lib/learning.js +559 -0
- package/bundles/continuous-learning/.claude/lib/utils.js +312 -0
- package/bundles/continuous-learning/.claude/skills/continuous-learning/SKILL.md +200 -0
- package/bundles/continuous-learning/.cursor/hooks/learning-end.js +102 -0
- package/bundles/continuous-learning/.cursor/rules/continuous-learning.mdc +34 -0
- package/bundles/continuous-learning/.cursor/skills/continuous-learning/SKILL.md +77 -0
- package/bundles/continuous-learning/README.md +159 -0
- package/bundles/continuous-learning/manifest.json +51 -0
- package/bundles/planning-bundle/README.md +34 -0
- package/bundles/planning-bundle/manifest.json +10 -0
- package/bundles/review-bundle/README.md +43 -0
- package/bundles/review-bundle/manifest.json +11 -0
- package/bundles/shared-memory/.claude/commands/list-sessions.md +124 -0
- package/bundles/shared-memory/.claude/commands/load-session.md +169 -0
- package/bundles/shared-memory/.claude/commands/save-session.md +137 -0
- package/bundles/shared-memory/.claude/hooks/memory-compact.js +43 -0
- package/bundles/shared-memory/.claude/hooks/memory-end.js +42 -0
- package/bundles/shared-memory/.claude/hooks/memory-start.js +59 -0
- package/bundles/shared-memory/.claude/lib/memory.js +416 -0
- package/bundles/shared-memory/.claude/lib/utils.js +209 -0
- package/bundles/shared-memory/.claude/skills/shared-memory/SKILL.md +183 -0
- package/bundles/shared-memory/.cursor/hooks/memory-start.js +42 -0
- package/bundles/shared-memory/.cursor/rules/shared-memory.mdc +37 -0
- package/bundles/shared-memory/.cursor/skills/shared-memory/SKILL.md +183 -0
- package/bundles/tdd-bundle/README.md +33 -0
- package/bundles/tdd-bundle/manifest.json +10 -0
- package/cli.js +978 -0
- package/commands/build-fix.md +29 -0
- package/commands/code-review.md +40 -0
- package/commands/e2e.md +363 -0
- package/commands/learn.md +114 -0
- package/commands/plan.md +113 -0
- package/commands/refactor-clean.md +28 -0
- package/commands/tdd.md +326 -0
- package/commands/test-coverage.md +27 -0
- package/commands/update-codemaps.md +17 -0
- package/commands/update-docs.md +31 -0
- package/configs.json +158 -0
- package/hooks/hooks.json +101 -0
- package/package.json +58 -0
- package/rules/agents.md +49 -0
- package/rules/coding-style.md +70 -0
- package/rules/git-workflow.md +45 -0
- package/rules/hooks.md +46 -0
- package/rules/patterns.md +55 -0
- package/rules/performance.md +47 -0
- package/rules/security.md +36 -0
- package/rules/testing.md +30 -0
- package/skills/ai-config-architect/SKILL.md +59 -0
- package/skills/ai-config-architect/references/agents.md +77 -0
- package/skills/ai-config-architect/references/commands.md +66 -0
- package/skills/ai-config-architect/references/hooks.md +70 -0
- package/skills/ai-config-architect/references/patterns.md +66 -0
- package/skills/ai-config-architect/references/platforms.md +82 -0
- package/skills/ai-config-architect/references/rules.md +66 -0
- package/skills/ai-config-architect/references/skills.md +67 -0
- package/skills/bk-chat-helper/SKILL.md +398 -0
- package/skills/bk-chat-helper/references/api-reference.md +606 -0
- package/skills/bk-chat-helper/references/examples.md +789 -0
- package/skills/bk-chat-helper/references/integration-guide.md +583 -0
- package/skills/bk-chat-x/SKILL.md +400 -0
- package/skills/bk-chat-x/references/components-api.md +340 -0
- package/skills/bk-chat-x/references/examples.md +386 -0
- package/skills/bk-chat-x/references/shortcuts-guide.md +375 -0
- package/skills/coding-standards/SKILL.md +523 -0
- package/skills/security-review/SKILL.md +497 -0
- package/skills/security-review/references/cloud-infrastructure-security.md +361 -0
- package/skills/strategic-compact/SKILL.md +66 -0
- package/skills/strategic-compact/scripts/suggest-compact.sh +52 -0
- package/skills/tdd-workflow/SKILL.md +412 -0
- package/skills/verification-loop/SKILL.md +128 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
# chat-helper 与 chat-x 集成指南
|
|
2
|
+
|
|
3
|
+
本指南详细说明如何将 `@blueking/chat-helper` SDK 与 `@blueking/chat-x` UI 组件库集成,构建完整的 AI 智能体应用。
|
|
4
|
+
|
|
5
|
+
## 架构概述
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
9
|
+
│ 应用层 (Vue 组件) │
|
|
10
|
+
├──────────────────────────────────────────────────────────────┤
|
|
11
|
+
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
|
|
12
|
+
│ │ @blueking/chat-x │ │ @blueking/chat-helper │ │
|
|
13
|
+
│ │ (UI 组件库) │ │ (业务逻辑 SDK) │ │
|
|
14
|
+
│ │ │ │ │ │
|
|
15
|
+
│ │ - ChatInput │◄──►│ - useChatHelper() │ │
|
|
16
|
+
│ │ - MessageContainer │ │ - agent │ │
|
|
17
|
+
│ │ - ShortcutBtns │ │ - session │ │
|
|
18
|
+
│ │ - ShortcutRender │ │ - message │ │
|
|
19
|
+
│ │ - AiSelection │ │ │ │
|
|
20
|
+
│ └─────────────────────┘ └─────────────────────────────┘ │
|
|
21
|
+
├──────────────────────────────────────────────────────────────┤
|
|
22
|
+
│ 后端 API │
|
|
23
|
+
└──────────────────────────────────────────────────────────────┘
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**职责划分**:
|
|
27
|
+
- **chat-x**: 纯 UI 渲染,不包含业务逻辑
|
|
28
|
+
- **chat-helper**: 状态管理、API 调用、流式处理
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 状态映射
|
|
33
|
+
|
|
34
|
+
### MessageStatus 映射
|
|
35
|
+
|
|
36
|
+
| chat-helper 状态 | chat-x MessageStatus | 触发场景 |
|
|
37
|
+
|-----------------|---------------------|----------|
|
|
38
|
+
| `message.status = 'pending'` | `MessageStatus.Pending` | 等待响应 |
|
|
39
|
+
| `message.status = 'streaming'` | `MessageStatus.Streaming` | 流式输出中 |
|
|
40
|
+
| `message.status = 'complete'` | `MessageStatus.Complete` | 响应完成 |
|
|
41
|
+
| `message.status = 'error'` | `MessageStatus.Error` | 发生错误 |
|
|
42
|
+
| `message.status = 'stop'` | `MessageStatus.Stop` | 用户停止 |
|
|
43
|
+
| `agent.isChatting = true` | `MessageStatus.Streaming` | 正在聊天 |
|
|
44
|
+
|
|
45
|
+
### 状态计算示例
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { computed } from 'vue';
|
|
49
|
+
import { MessageStatus } from '@blueking/chat-x';
|
|
50
|
+
import { useChatHelper } from '@blueking/chat-helper';
|
|
51
|
+
|
|
52
|
+
const { agent, message } = useChatHelper({ /* ... */ });
|
|
53
|
+
|
|
54
|
+
// 方式 1:基于 agent.isChatting
|
|
55
|
+
const messageStatus = computed(() =>
|
|
56
|
+
agent.isChatting.value ? MessageStatus.Streaming : MessageStatus.Complete
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// 方式 2:基于最后一条消息状态
|
|
60
|
+
const messageStatus = computed(() => {
|
|
61
|
+
const lastMsg = message.list.value.at(-1);
|
|
62
|
+
if (!lastMsg) return MessageStatus.Complete;
|
|
63
|
+
|
|
64
|
+
switch (lastMsg.status) {
|
|
65
|
+
case 'streaming': return MessageStatus.Streaming;
|
|
66
|
+
case 'pending': return MessageStatus.Pending;
|
|
67
|
+
case 'error': return MessageStatus.Error;
|
|
68
|
+
case 'stop': return MessageStatus.Stop;
|
|
69
|
+
default: return MessageStatus.Complete;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 组件对接
|
|
77
|
+
|
|
78
|
+
### 1. ChatInput 对接
|
|
79
|
+
|
|
80
|
+
```vue
|
|
81
|
+
<template>
|
|
82
|
+
<ChatInput
|
|
83
|
+
v-model="userInput"
|
|
84
|
+
v-model:cite="citeContent"
|
|
85
|
+
:message-status="messageStatus"
|
|
86
|
+
:placeholder="placeholder"
|
|
87
|
+
:prompts="prompts"
|
|
88
|
+
:resources="resources"
|
|
89
|
+
:shortcuts="shortcuts"
|
|
90
|
+
:shortcut-id="selectedShortcutId"
|
|
91
|
+
:on-send-message="handleSendMessage"
|
|
92
|
+
:on-stop-sending="handleStopSending"
|
|
93
|
+
@select-shortcut="handleSelectShortcut"
|
|
94
|
+
@delete-shortcut="handleDeleteShortcut"
|
|
95
|
+
/>
|
|
96
|
+
</template>
|
|
97
|
+
|
|
98
|
+
<script setup lang="ts">
|
|
99
|
+
import { ref, computed } from 'vue';
|
|
100
|
+
import { ChatInput, MessageStatus, type Shortcut, type TagSchema } from '@blueking/chat-x';
|
|
101
|
+
import { useChatHelper, AGUIProtocol } from '@blueking/chat-helper';
|
|
102
|
+
|
|
103
|
+
const userInput = ref<string | TagSchema>('');
|
|
104
|
+
const citeContent = ref('');
|
|
105
|
+
const selectedShortcutId = ref<string | null>(null);
|
|
106
|
+
const isStreaming = ref(false);
|
|
107
|
+
|
|
108
|
+
const chatHelper = useChatHelper({
|
|
109
|
+
requestData: { urlPrefix: '/api/' },
|
|
110
|
+
protocol: new AGUIProtocol({
|
|
111
|
+
onStart: () => { isStreaming.value = true; },
|
|
112
|
+
onDone: () => { isStreaming.value = false; },
|
|
113
|
+
onError: () => { isStreaming.value = false; },
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const { agent, session, message } = chatHelper;
|
|
118
|
+
|
|
119
|
+
// 状态映射
|
|
120
|
+
const messageStatus = computed(() =>
|
|
121
|
+
isStreaming.value ? MessageStatus.Streaming : MessageStatus.Complete
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// 发送消息
|
|
125
|
+
const handleSendMessage = async (value: string, docSchema: TagSchema) => {
|
|
126
|
+
if (!session.current.value?.sessionCode) return;
|
|
127
|
+
|
|
128
|
+
// 构建消息属性
|
|
129
|
+
const property = citeContent.value
|
|
130
|
+
? { extra: { cite: citeContent.value } }
|
|
131
|
+
: undefined;
|
|
132
|
+
|
|
133
|
+
// 清空输入
|
|
134
|
+
userInput.value = '';
|
|
135
|
+
citeContent.value = '';
|
|
136
|
+
|
|
137
|
+
// 发送
|
|
138
|
+
await agent.chat(
|
|
139
|
+
value,
|
|
140
|
+
session.current.value.sessionCode,
|
|
141
|
+
undefined,
|
|
142
|
+
undefined,
|
|
143
|
+
property
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// 停止发送
|
|
148
|
+
const handleStopSending = async () => {
|
|
149
|
+
agent.stopChat();
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// 快捷指令选择
|
|
153
|
+
const handleSelectShortcut = (shortcut: Shortcut, text?: string) => {
|
|
154
|
+
selectedShortcutId.value = shortcut.id;
|
|
155
|
+
if (text) userInput.value = text;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// 删除快捷指令
|
|
159
|
+
const handleDeleteShortcut = () => {
|
|
160
|
+
selectedShortcutId.value = null;
|
|
161
|
+
};
|
|
162
|
+
</script>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 2. MessageContainer 对接
|
|
166
|
+
|
|
167
|
+
```vue
|
|
168
|
+
<template>
|
|
169
|
+
<MessageContainer
|
|
170
|
+
v-model:selected-messages="selectedMessages"
|
|
171
|
+
:messages="messages"
|
|
172
|
+
:message-status="messageStatus"
|
|
173
|
+
:enable-selection="enableSelection"
|
|
174
|
+
:on-agent-action="handleAgentAction"
|
|
175
|
+
:on-user-action="handleUserAction"
|
|
176
|
+
@stop-streaming="handleStopStreaming"
|
|
177
|
+
>
|
|
178
|
+
<!-- 自定义消息渲染(可选) -->
|
|
179
|
+
<template #default="{ message: msg }">
|
|
180
|
+
<ActivityMessage v-if="msg.role === 'activity'" :message="msg" />
|
|
181
|
+
<ReasoningMessage v-else-if="msg.role === 'reasoning'" :message="msg" />
|
|
182
|
+
<MessageRender v-else :message="msg" :on-action="handleAction" />
|
|
183
|
+
</template>
|
|
184
|
+
</MessageContainer>
|
|
185
|
+
</template>
|
|
186
|
+
|
|
187
|
+
<script setup lang="ts">
|
|
188
|
+
import { computed, ref } from 'vue';
|
|
189
|
+
import {
|
|
190
|
+
MessageContainer,
|
|
191
|
+
MessageRender,
|
|
192
|
+
MessageStatus,
|
|
193
|
+
type IToolBtn,
|
|
194
|
+
} from '@blueking/chat-x';
|
|
195
|
+
import { useChatHelper, MessageRole } from '@blueking/chat-helper';
|
|
196
|
+
|
|
197
|
+
const selectedMessages = ref([]);
|
|
198
|
+
const enableSelection = ref(false);
|
|
199
|
+
|
|
200
|
+
const { agent, session, message } = useChatHelper({ /* ... */ });
|
|
201
|
+
|
|
202
|
+
// 消息列表直接使用 chat-helper 的数据
|
|
203
|
+
const messages = computed(() => message.list.value);
|
|
204
|
+
|
|
205
|
+
// AI 消息操作
|
|
206
|
+
const handleAgentAction = async (tool: IToolBtn, msgData: any) => {
|
|
207
|
+
switch (tool.id) {
|
|
208
|
+
case 'copy':
|
|
209
|
+
// 复制由组件内部处理
|
|
210
|
+
break;
|
|
211
|
+
case 'like':
|
|
212
|
+
// 返回点赞原因列表
|
|
213
|
+
const likeReasons = await session.getSessionFeedbackReasons(1);
|
|
214
|
+
return likeReasons || ['回答准确', '信息全面', '解决了问题'];
|
|
215
|
+
case 'unlike':
|
|
216
|
+
// 返回点踩原因列表
|
|
217
|
+
const unlikeReasons = await session.getSessionFeedbackReasons(-1);
|
|
218
|
+
return unlikeReasons || ['信息错误', '回答不相关', '内容重复'];
|
|
219
|
+
case 'rebuild':
|
|
220
|
+
// 重新生成
|
|
221
|
+
await rebuildMessage(msgData);
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// 用户消息操作
|
|
227
|
+
const handleUserAction = async (tool: IToolBtn, msgData: any) => {
|
|
228
|
+
switch (tool.id) {
|
|
229
|
+
case 'edit':
|
|
230
|
+
// 编辑消息
|
|
231
|
+
editMessage(msgData);
|
|
232
|
+
break;
|
|
233
|
+
case 'delete':
|
|
234
|
+
// 删除消息
|
|
235
|
+
await message.deleteMessages([msgData]);
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// 停止流式
|
|
241
|
+
const handleStopStreaming = () => {
|
|
242
|
+
agent.stopChat();
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// 重新生成
|
|
246
|
+
const rebuildMessage = async (msgData: any) => {
|
|
247
|
+
// 找到对应的用户消息
|
|
248
|
+
const index = message.list.value.findIndex(m => m.messageId === msgData.messageId);
|
|
249
|
+
if (index > 0) {
|
|
250
|
+
const userMsg = message.list.value[index - 1];
|
|
251
|
+
if (userMsg.role === MessageRole.User) {
|
|
252
|
+
const content = typeof userMsg.content === 'string'
|
|
253
|
+
? userMsg.content
|
|
254
|
+
: JSON.stringify(userMsg.content);
|
|
255
|
+
await agent.chat(content, session.current.value!.sessionCode);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const editMessage = (msgData: any) => {
|
|
261
|
+
// 实现编辑逻辑
|
|
262
|
+
};
|
|
263
|
+
</script>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### 3. ShortcutBtns + ShortcutRender 对接
|
|
267
|
+
|
|
268
|
+
```vue
|
|
269
|
+
<template>
|
|
270
|
+
<div class="shortcut-area">
|
|
271
|
+
<!-- 快捷指令按钮(空消息时显示) -->
|
|
272
|
+
<ShortcutBtns
|
|
273
|
+
v-if="showShortcutBtns"
|
|
274
|
+
:shortcuts="shortcuts"
|
|
275
|
+
@select-shortcut="handleSelectShortcut"
|
|
276
|
+
/>
|
|
277
|
+
|
|
278
|
+
<!-- 快捷指令表单 -->
|
|
279
|
+
<ShortcutRender
|
|
280
|
+
v-if="selectedShortcut?.components?.length"
|
|
281
|
+
:name="selectedShortcut.name"
|
|
282
|
+
:components="selectedShortcut.components"
|
|
283
|
+
:form-model="selectedShortcut.formModel"
|
|
284
|
+
@close="handleCloseShortcut"
|
|
285
|
+
@submit="handleSubmitShortcut"
|
|
286
|
+
/>
|
|
287
|
+
</div>
|
|
288
|
+
</template>
|
|
289
|
+
|
|
290
|
+
<script setup lang="ts">
|
|
291
|
+
import { ref, computed } from 'vue';
|
|
292
|
+
import { ShortcutBtns, ShortcutRender, type Shortcut } from '@blueking/chat-x';
|
|
293
|
+
import { useChatHelper } from '@blueking/chat-helper';
|
|
294
|
+
|
|
295
|
+
const selectedShortcut = ref<Shortcut | null>(null);
|
|
296
|
+
|
|
297
|
+
const { agent, session, message } = useChatHelper({ /* ... */ });
|
|
298
|
+
|
|
299
|
+
// 从 agent 信息获取快捷指令
|
|
300
|
+
const shortcuts = computed<Shortcut[]>(() => {
|
|
301
|
+
const commands = agent.info.value?.conversationSettings?.commands;
|
|
302
|
+
if (!commands) return [];
|
|
303
|
+
|
|
304
|
+
return commands.map(cmd => ({
|
|
305
|
+
id: cmd.id,
|
|
306
|
+
name: cmd.name,
|
|
307
|
+
icon: cmd.icon,
|
|
308
|
+
components: cmd.components?.map(comp => ({
|
|
309
|
+
type: comp.type,
|
|
310
|
+
key: comp.key,
|
|
311
|
+
name: comp.name,
|
|
312
|
+
props: {
|
|
313
|
+
placeholder: comp.placeholder,
|
|
314
|
+
options: comp.options,
|
|
315
|
+
rows: comp.rows,
|
|
316
|
+
},
|
|
317
|
+
fillBack: comp.fillBack,
|
|
318
|
+
required: comp.required,
|
|
319
|
+
})),
|
|
320
|
+
}));
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// 是否显示快捷指令按钮
|
|
324
|
+
const showShortcutBtns = computed(() =>
|
|
325
|
+
message.list.value.length === 0 && shortcuts.value.length > 0
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
// 选择快捷指令
|
|
329
|
+
const handleSelectShortcut = (shortcut: Shortcut, text?: string) => {
|
|
330
|
+
selectedShortcut.value = {
|
|
331
|
+
...shortcut,
|
|
332
|
+
formModel: text ? { content: text } : {},
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// 关闭快捷指令
|
|
337
|
+
const handleCloseShortcut = () => {
|
|
338
|
+
selectedShortcut.value = null;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// 提交快捷指令表单
|
|
342
|
+
const handleSubmitShortcut = async (formModel: Record<string, unknown>) => {
|
|
343
|
+
if (!session.current.value?.sessionCode) return;
|
|
344
|
+
|
|
345
|
+
// 构建消息内容
|
|
346
|
+
const content = (formModel.content as string) || selectedShortcut.value?.name || '';
|
|
347
|
+
|
|
348
|
+
// 构建消息属性
|
|
349
|
+
const property = {
|
|
350
|
+
extra: {
|
|
351
|
+
command: selectedShortcut.value?.id,
|
|
352
|
+
...formModel,
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
selectedShortcut.value = null;
|
|
357
|
+
|
|
358
|
+
// 发送
|
|
359
|
+
await agent.chat(
|
|
360
|
+
content,
|
|
361
|
+
session.current.value.sessionCode,
|
|
362
|
+
undefined,
|
|
363
|
+
undefined,
|
|
364
|
+
property
|
|
365
|
+
);
|
|
366
|
+
};
|
|
367
|
+
</script>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### 4. AiSelection 对接
|
|
371
|
+
|
|
372
|
+
```vue
|
|
373
|
+
<template>
|
|
374
|
+
<AiSelection
|
|
375
|
+
v-model:visible="aiSelectionVisible"
|
|
376
|
+
:shortcuts="selectionShortcuts"
|
|
377
|
+
:max-shortcut-count="3"
|
|
378
|
+
:offset="10"
|
|
379
|
+
@select-shortcut="handleSelectionShortcut"
|
|
380
|
+
@selection-change="handleSelectionChange"
|
|
381
|
+
/>
|
|
382
|
+
</template>
|
|
383
|
+
|
|
384
|
+
<script setup lang="ts">
|
|
385
|
+
import { ref, computed } from 'vue';
|
|
386
|
+
import { AiSelection, type Shortcut } from '@blueking/chat-x';
|
|
387
|
+
import { useChatHelper } from '@blueking/chat-helper';
|
|
388
|
+
|
|
389
|
+
const aiSelectionVisible = ref(false);
|
|
390
|
+
const selectedText = ref('');
|
|
391
|
+
|
|
392
|
+
const { agent, session } = useChatHelper({ /* ... */ });
|
|
393
|
+
|
|
394
|
+
// 划词快捷指令(通常是子集)
|
|
395
|
+
const selectionShortcuts = computed<Shortcut[]>(() => [
|
|
396
|
+
{ id: 'ask', name: '问问小鲸' },
|
|
397
|
+
{ id: 'translate', name: '翻译' },
|
|
398
|
+
{ id: 'explain', name: '解释' },
|
|
399
|
+
]);
|
|
400
|
+
|
|
401
|
+
// 选区变化
|
|
402
|
+
const handleSelectionChange = (text: string) => {
|
|
403
|
+
selectedText.value = text;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// 选择快捷指令
|
|
407
|
+
const handleSelectionShortcut = async (shortcut: Shortcut, text: string) => {
|
|
408
|
+
if (!session.current.value?.sessionCode) return;
|
|
409
|
+
|
|
410
|
+
// 根据快捷指令构建内容
|
|
411
|
+
let content = text;
|
|
412
|
+
if (shortcut.id === 'translate') {
|
|
413
|
+
content = `翻译以下内容:\n${text}`;
|
|
414
|
+
} else if (shortcut.id === 'explain') {
|
|
415
|
+
content = `解释以下内容:\n${text}`;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 发送
|
|
419
|
+
await agent.chat(
|
|
420
|
+
content,
|
|
421
|
+
session.current.value.sessionCode,
|
|
422
|
+
undefined,
|
|
423
|
+
undefined,
|
|
424
|
+
{ extra: { command: shortcut.id, cite: text } }
|
|
425
|
+
);
|
|
426
|
+
};
|
|
427
|
+
</script>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## 数据流转示例
|
|
433
|
+
|
|
434
|
+
### 完整对话流程
|
|
435
|
+
|
|
436
|
+
```
|
|
437
|
+
1. 用户输入 → ChatInput.onSendMessage
|
|
438
|
+
2. 调用 agent.chat(userInput, sessionCode)
|
|
439
|
+
├─ 创建用户消息 → message.list 更新
|
|
440
|
+
└─ 发起 SSE 请求
|
|
441
|
+
3. Protocol.onStart → isStreaming = true → MessageStatus.Streaming
|
|
442
|
+
4. 流式事件处理:
|
|
443
|
+
├─ TextMessageStart → 创建 AI 消息占位
|
|
444
|
+
├─ TextMessageChunk → 更新 AI 消息内容
|
|
445
|
+
├─ ThinkingStart → 创建推理消息
|
|
446
|
+
├─ ToolCallStart → 创建工具调用消息
|
|
447
|
+
└─ TextMessageEnd → 标记消息完成
|
|
448
|
+
5. Protocol.onDone → isStreaming = false → MessageStatus.Complete
|
|
449
|
+
6. MessageContainer 自动更新渲染
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 会话切换流程
|
|
453
|
+
|
|
454
|
+
```
|
|
455
|
+
1. 用户点击会话 → session.chooseSession(sessionCode)
|
|
456
|
+
├─ agent.stopChat() → 停止当前聊天
|
|
457
|
+
├─ session.current = 目标会话
|
|
458
|
+
├─ message.getMessages(sessionCode) → 加载消息
|
|
459
|
+
└─ agent.resumeStreamingChat() → 恢复流式(如果需要)
|
|
460
|
+
2. MessageContainer 更新显示新会话消息
|
|
461
|
+
3. ChatInput 清空输入状态
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## 错误处理
|
|
467
|
+
|
|
468
|
+
### 统一错误处理
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { useChatHelper, AGUIProtocol } from '@blueking/chat-helper';
|
|
472
|
+
import { Message } from 'bkui-vue';
|
|
473
|
+
|
|
474
|
+
const chatHelper = useChatHelper({
|
|
475
|
+
requestData: { urlPrefix: '/api/' },
|
|
476
|
+
interceptors: {
|
|
477
|
+
response: (response) => {
|
|
478
|
+
// API 业务错误
|
|
479
|
+
if (response.data.code !== 0) {
|
|
480
|
+
Message({ theme: 'error', message: response.data.message });
|
|
481
|
+
}
|
|
482
|
+
return response;
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
protocol: new AGUIProtocol({
|
|
486
|
+
onError: (error) => {
|
|
487
|
+
// 流式错误
|
|
488
|
+
Message({ theme: 'error', message: `AI 响应错误: ${error.message}` });
|
|
489
|
+
},
|
|
490
|
+
}),
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### 重试机制
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
const retryChat = async (retries = 3) => {
|
|
498
|
+
for (let i = 0; i < retries; i++) {
|
|
499
|
+
try {
|
|
500
|
+
await agent.chat(userInput, sessionCode);
|
|
501
|
+
return; // 成功则退出
|
|
502
|
+
} catch (error) {
|
|
503
|
+
if (i === retries - 1) {
|
|
504
|
+
throw error; // 最后一次仍失败则抛出
|
|
505
|
+
}
|
|
506
|
+
await new Promise(r => setTimeout(r, 1000 * (i + 1))); // 递增延迟
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## 性能优化
|
|
515
|
+
|
|
516
|
+
### 1. 消息虚拟滚动
|
|
517
|
+
|
|
518
|
+
对于大量消息,使用虚拟滚动:
|
|
519
|
+
|
|
520
|
+
```vue
|
|
521
|
+
<template>
|
|
522
|
+
<VirtualScroll
|
|
523
|
+
:items="message.list.value"
|
|
524
|
+
:item-height="80"
|
|
525
|
+
v-slot="{ item }"
|
|
526
|
+
>
|
|
527
|
+
<MessageRender :message="item" />
|
|
528
|
+
</VirtualScroll>
|
|
529
|
+
</template>
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### 2. 防抖发送
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
import { useDebounceFn } from '@vueuse/core';
|
|
536
|
+
|
|
537
|
+
const debouncedSend = useDebounceFn((value: string) => {
|
|
538
|
+
agent.chat(value, session.current.value!.sessionCode);
|
|
539
|
+
}, 300);
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
### 3. 消息分页加载
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
const loadMoreMessages = async () => {
|
|
546
|
+
if (message.isListLoading.value) return;
|
|
547
|
+
|
|
548
|
+
// 获取更早的消息
|
|
549
|
+
const oldestMsg = message.list.value[0];
|
|
550
|
+
if (oldestMsg?.messageId) {
|
|
551
|
+
await message.getMessages(sessionCode, { before: oldestMsg.messageId });
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## 最佳实践检查清单
|
|
559
|
+
|
|
560
|
+
### 初始化
|
|
561
|
+
- [ ] `onMounted` 中初始化 agent 和 session
|
|
562
|
+
- [ ] 有会话则选择第一个,无则创建
|
|
563
|
+
- [ ] 处理初始化错误
|
|
564
|
+
|
|
565
|
+
### 清理
|
|
566
|
+
- [ ] `onUnmounted` 中调用 `agent.stopChat()`
|
|
567
|
+
- [ ] 清理定时器和事件监听
|
|
568
|
+
|
|
569
|
+
### 状态管理
|
|
570
|
+
- [ ] 使用 computed 映射 messageStatus
|
|
571
|
+
- [ ] 正确处理 loading 状态
|
|
572
|
+
- [ ] 使用枚举而非字符串
|
|
573
|
+
|
|
574
|
+
### 错误处理
|
|
575
|
+
- [ ] 配置拦截器处理 API 错误
|
|
576
|
+
- [ ] 配置 Protocol.onError 处理流式错误
|
|
577
|
+
- [ ] 提供友好的错误提示
|
|
578
|
+
|
|
579
|
+
### 用户体验
|
|
580
|
+
- [ ] 发送后清空输入框
|
|
581
|
+
- [ ] 流式响应时禁用发送按钮
|
|
582
|
+
- [ ] 提供停止按钮
|
|
583
|
+
- [ ] 自动滚动到最新消息
|