@fv-ui/ai-chat 0.1.0 → 0.2.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 +746 -741
- package/dist/index.d.ts +27 -12
- package/dist/style.css +1 -1
- package/es/index.mjs +441 -151
- package/lib/index.js +441 -151
- package/package.json +80 -80
package/README.md
CHANGED
|
@@ -1,741 +1,746 @@
|
|
|
1
|
-
# @fv-ui/ai-chat
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-

|
|
5
|
-

|
|
6
|
-
|
|
7
|
-
基于 Vue 3 + Element Plus 的 AI 对话组件库,复刻 element-plus-x 与 open-webui 对话框:从底层气泡、发送框、Markdown 渲染,到上层的对话整框(`<FvAiChat>`)、分支树消息、Artifacts 预览,一次给全。
|
|
8
|
-
|
|
9
|
-
- 14 个基础组件(Bubble/Sender/XMarkdown/Thinking/...)自由拼装
|
|
10
|
-
- 对话应用层整框 `<FvAiChat>`:顶栏模型多选、消息列表、分支切换、Artifacts 面板、发送停止一体化
|
|
11
|
-
- 树形消息模型(同构 open-webui):一条提问可挂多个模型回答,分支可切换、可重生成
|
|
12
|
-
- `ChatAdapter` 适配器协议:内置 OpenAI 兼容实现,可替换自研后端/Ollama
|
|
13
|
-
- 流式打字机、思考链折叠、KaTeX/代码高亮(Prism)开箱即用
|
|
14
|
-
|
|
15
|
-
## 安装
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
pnpm add @fv-ui/ai-chat
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
peerDependencies:`vue ^3.4.0`、`element-plus ^2.7.0`、`@element-plus/icons-vue ^2.3.0`。
|
|
22
|
-
|
|
23
|
-
## 快速开始
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
// main.ts —— 需要先引入 element-plus(组件库依赖其组件与暗色变量)
|
|
27
|
-
import ElementPlus from 'element-plus'
|
|
28
|
-
import 'element-plus/dist/index.css'
|
|
29
|
-
import { createApp } from 'vue'
|
|
30
|
-
import App from './App.vue'
|
|
31
|
-
|
|
32
|
-
createApp(App).use(ElementPlus).mount('#app')
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
```vue
|
|
36
|
-
<!-- App.vue -->
|
|
37
|
-
<script setup lang="ts">
|
|
38
|
-
import { FvAiChat } from '@fv-ui/ai-chat'
|
|
39
|
-
import '@fv-ui/ai-chat/dist/style.css'
|
|
40
|
-
</script>
|
|
41
|
-
|
|
42
|
-
<template>
|
|
43
|
-
<FvAiChat
|
|
44
|
-
title="AI 助手"
|
|
45
|
-
base-url="https://api.deepseek.com/v1"
|
|
46
|
-
api-key="sk-xxx"
|
|
47
|
-
model="deepseek-chat"
|
|
48
|
-
style="height: 600px"
|
|
49
|
-
/>
|
|
50
|
-
</template>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
`<FvAiChat>` 未传 `adapter` 时,用 `baseURL`/`apiKey`/`model` 构造内置 `OpenAIAdapter`(走 `/chat/completions`,SSE 流式)。两个都缺省时发送会报"未配置 adapter 或 baseURL"。
|
|
54
|
-
|
|
55
|
-
## 基础组件层 API
|
|
56
|
-
|
|
57
|
-
### XMarkdown
|
|
58
|
-
|
|
59
|
-
Markdown 渲染(Sanitized by DOMPurify),内置 Prism 代码高亮与 KaTeX。
|
|
60
|
-
|
|
61
|
-
| Prop | 类型 | 默认 | 说明 |
|
|
62
|
-
| --- | --- | --- | --- |
|
|
63
|
-
| content | string | —(必填) | Markdown 源文本 |
|
|
64
|
-
| mdPlugins | PluginWithParams[] | — | 额外 markdown-it 插件 |
|
|
65
|
-
| highlight | (str, lang) => string | 内置 Prism | 自定义代码高亮 |
|
|
66
|
-
| isTyping | boolean | — | 打字机模式样式钩子(透传占位) |
|
|
67
|
-
|
|
68
|
-
| Ref | 说明 |
|
|
69
|
-
| --- | --- |
|
|
70
|
-
| getHtml() | 取当前渲染后的 HTML 字符串 |
|
|
71
|
-
|
|
72
|
-
### Typewriter
|
|
73
|
-
|
|
74
|
-
打字机输出,支持续打/重打/雾化遮罩。
|
|
75
|
-
|
|
76
|
-
| Prop | 类型 | 默认 | 说明 |
|
|
77
|
-
| --- | --- | --- | --- |
|
|
78
|
-
| content | string | —(必填) | 全文(流式时增量传入,子集扩展自动续打) |
|
|
79
|
-
| isMarkdown | boolean | false | 走 XMarkdown 渲染(此时 suffix 不追加) |
|
|
80
|
-
| isFog | boolean \| { bgColor?; width? } | false | 打字中尾部雾化遮罩 |
|
|
81
|
-
| typing | boolean \| { step?; interval?; suffix? } | false | 开启打字机;对象形态可配步长/间隔/光标后缀 |
|
|
82
|
-
|
|
83
|
-
| Event | 说明 |
|
|
84
|
-
| --- | --- |
|
|
85
|
-
| start / writing / finish | 开始打字 / 每次推进 / 全部打完 |
|
|
86
|
-
|
|
87
|
-
| Ref | 说明 |
|
|
88
|
-
| --- | --- |
|
|
89
|
-
| interrupt() / continue() / restart() / destroy() | 暂停 / 继续 / 重打 / 重置清空 |
|
|
90
|
-
| renderedContent / isTyping / progress | 已输出文本 / 是否打字中 / 进度 0-1 |
|
|
91
|
-
|
|
92
|
-
### Bubble
|
|
93
|
-
|
|
94
|
-
单条气泡(内部按需挂载 Typewriter/XMarkdown)。
|
|
95
|
-
|
|
96
|
-
| Prop | 类型 | 默认 | 说明 |
|
|
97
|
-
| --- | --- | --- | --- |
|
|
98
|
-
| content | string | '' | 气泡内容 |
|
|
99
|
-
| placement | 'start' \| 'end' | 'start' | start 靠左 / end 靠右 |
|
|
100
|
-
| loading | boolean | false | 加载态(三点动画,可被 #loading 覆盖) |
|
|
101
|
-
| typing | boolean \| TypingConfig | false | 打字机模式(透传 Typewriter) |
|
|
102
|
-
| isMarkdown | boolean | false | 内容按 Markdown 渲染 |
|
|
103
|
-
| isFog | boolean \| { bgColor?; width? } | false | 雾化(自定义气泡背景时传 bgColor) |
|
|
104
|
-
| variant | 'filled' \| 'borderless' \| 'outlined' \| 'shadow' | 'filled' | 气泡样式 |
|
|
105
|
-
| shape | 'round' \| 'corner' | 'round' | 圆角 / 方角 |
|
|
106
|
-
| avatar | string | '' | 头像图片地址 |
|
|
107
|
-
| avatarSize | ElAvatar size \| number | '' | 头像尺寸 |
|
|
108
|
-
| avatarGap | string | '12px' | 头像与内容间距 |
|
|
109
|
-
| avatarShape | 'circle' \| 'square' | 'circle' | 头像形状 |
|
|
110
|
-
| avatarIcon | Component | — | 头像图标(与 avatar 冲突时优先) |
|
|
111
|
-
| avatarSrcSet / avatarAlt | string | '' / '' | 头像 srcSet/alt |
|
|
112
|
-
| avatarFit | 'fill' \| 'contain' \| 'cover' \| 'none' \| 'scale-down' | 'cover' | 头像填充方式(el-avatar fit) |
|
|
113
|
-
| noPadding | boolean | false | 内容区去内边距 |
|
|
114
|
-
| noStyle | boolean | false | 去气泡样式(透明/无框/无阴影) |
|
|
115
|
-
|
|
116
|
-
| Event | 说明 |
|
|
117
|
-
| --- | --- |
|
|
118
|
-
| avatar-error | 头像加载失败 |
|
|
119
|
-
| start / writing / finish | typing 模式下的打字机事件透传 |
|
|
120
|
-
|
|
121
|
-
| Slot | 说明 |
|
|
122
|
-
| --- | --- |
|
|
123
|
-
| avatar / header / content / loading / footer | 自定义头像 / 头部 / 内容(优先于 content prop)/ 加载态 / 底部 |
|
|
124
|
-
|
|
125
|
-
| Ref | 说明 |
|
|
126
|
-
| --- | --- |
|
|
127
|
-
| interrupt / continue / restart / destroy | Typewriter 方法转发(非 typing 模式为 no-op) |
|
|
128
|
-
| renderedContent / isTyping / progress | Typewriter 状态转发 |
|
|
129
|
-
| twInstance | 内部 Typewriter 原始实例(BubbleList @complete 透传用) |
|
|
130
|
-
|
|
131
|
-
### BubbleList
|
|
132
|
-
|
|
133
|
-
气泡列表 + 吸底/回底按钮。
|
|
134
|
-
|
|
135
|
-
| Prop | 类型 | 默认 | 说明 |
|
|
136
|
-
| --- | --- | --- | --- |
|
|
137
|
-
| list | BubbleListItemProps[] | [] | 列表项(Bubble props + 必填 key) |
|
|
138
|
-
| maxHeight | string | '' | 滚动容器最大高度(如 '350px'/'60vh') |
|
|
139
|
-
| alwaysShowScrollbar | boolean | false | 始终显示滚动条 |
|
|
140
|
-
| showBackButton | boolean | true | 是否显示回底按钮 |
|
|
141
|
-
| backButtonThreshold | number | 80 | 距底超过该像素显示回底按钮 |
|
|
142
|
-
| btnLoading | boolean | false | 回底按钮加载态 |
|
|
143
|
-
| btnColor | string | '' | 回底按钮颜色(el-button color) |
|
|
144
|
-
| backButtonPosition | { bottom?; left? } | {} | 回底按钮位置偏移 |
|
|
145
|
-
| btnIconSize | number | 16 | 回底按钮图标尺寸 |
|
|
146
|
-
| triggerIndices | 'only-last' \| 'all' \| number[] | 'only-last' | @complete 触发索引 |
|
|
147
|
-
|
|
148
|
-
| Event | 说明 |
|
|
149
|
-
| --- | --- |
|
|
150
|
-
| complete | (typewriterInstance, index) 打字完成(按 triggerIndices 过滤后上抛) |
|
|
151
|
-
|
|
152
|
-
| Slot | 说明 |
|
|
153
|
-
| --- | --- |
|
|
154
|
-
| avatar / header / content / loading / footer | 透传给每个 Bubble,作用域 { item, index } |
|
|
155
|
-
|
|
156
|
-
| Ref | 说明 |
|
|
157
|
-
| --- | --- |
|
|
158
|
-
| scrollToTop() / scrollToBottom() / scrollToBubble(index) | 滚动控制 |
|
|
159
|
-
|
|
160
|
-
### Sender
|
|
161
|
-
|
|
162
|
-
输入发送框。
|
|
163
|
-
|
|
164
|
-
| Prop | 类型 | 默认 | 说明 |
|
|
165
|
-
| --- | --- | --- | --- |
|
|
166
|
-
| modelValue | string(v-model) | '' | 输入值(缺省内部自持) |
|
|
167
|
-
| placeholder | string | '' | 占位文案 |
|
|
168
|
-
| autosize | { minRows?; maxRows? } | {} | 自增高(行高 22px) |
|
|
169
|
-
| submitType | 'enter' \| 'shiftEnter' \| 'cmdOrCtrlEnter' \| 'altEnter' | 'enter' | 提交按键模式 |
|
|
170
|
-
| loading | boolean | false | 加载态(发送键变停止键,输入区禁用) |
|
|
171
|
-
| disabled | boolean | false | 禁用(阻断提交) |
|
|
172
|
-
| readOnly | boolean | false | 只读 |
|
|
173
|
-
| clearable | boolean | false | 显示清空按钮 |
|
|
174
|
-
|
|
|
175
|
-
|
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
184
|
-
|
|
185
|
-
|
|
|
186
|
-
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
|
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
195
|
-
|
|
|
196
|
-
|
|
197
|
-
|
|
|
198
|
-
|
|
|
199
|
-
|
|
|
200
|
-
|
|
|
201
|
-
|
|
|
202
|
-
|
|
203
|
-
|
|
|
204
|
-
|
|
205
|
-
|
|
|
206
|
-
|
|
|
207
|
-
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
|
216
|
-
|
|
|
217
|
-
|
|
|
218
|
-
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
|
225
|
-
|
|
|
226
|
-
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
|
235
|
-
|
|
|
236
|
-
|
|
|
237
|
-
|
|
|
238
|
-
|
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
|
243
|
-
|
|
|
244
|
-
|
|
|
245
|
-
|
|
|
246
|
-
|
|
|
247
|
-
|
|
|
248
|
-
|
|
|
249
|
-
|
|
|
250
|
-
|
|
251
|
-
|
|
|
252
|
-
|
|
253
|
-
|
|
|
254
|
-
|
|
|
255
|
-
|
|
|
256
|
-
|
|
257
|
-
|
|
|
258
|
-
|
|
259
|
-
|
|
|
260
|
-
|
|
|
261
|
-
|
|
262
|
-
|
|
|
263
|
-
|
|
264
|
-
|
|
|
265
|
-
|
|
|
266
|
-
|
|
|
267
|
-
|
|
|
268
|
-
|
|
|
269
|
-
|
|
|
270
|
-
|
|
|
271
|
-
|
|
|
272
|
-
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
|
281
|
-
|
|
|
282
|
-
|
|
|
283
|
-
|
|
|
284
|
-
|
|
|
285
|
-
|
|
|
286
|
-
|
|
|
287
|
-
|
|
|
288
|
-
|
|
289
|
-
|
|
|
290
|
-
|
|
291
|
-
|
|
|
292
|
-
|
|
293
|
-
|
|
|
294
|
-
|
|
295
|
-
|
|
|
296
|
-
|
|
|
297
|
-
|
|
298
|
-
|
|
|
299
|
-
|
|
300
|
-
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
|
309
|
-
|
|
|
310
|
-
|
|
311
|
-
|
|
|
312
|
-
|
|
313
|
-
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
|
322
|
-
|
|
|
323
|
-
|
|
|
324
|
-
|
|
325
|
-
|
|
|
326
|
-
|
|
327
|
-
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
|
336
|
-
|
|
|
337
|
-
|
|
338
|
-
|
|
|
339
|
-
|
|
340
|
-
|
|
|
341
|
-
|
|
342
|
-
|
|
|
343
|
-
|
|
344
|
-
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
|
353
|
-
|
|
|
354
|
-
|
|
|
355
|
-
|
|
|
356
|
-
|
|
|
357
|
-
|
|
|
358
|
-
|
|
|
359
|
-
|
|
360
|
-
|
|
|
361
|
-
|
|
362
|
-
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
|
371
|
-
|
|
|
372
|
-
|
|
|
373
|
-
|
|
|
374
|
-
|
|
|
375
|
-
|
|
|
376
|
-
|
|
377
|
-
|
|
|
378
|
-
|
|
379
|
-
|
|
|
380
|
-
|
|
|
381
|
-
|
|
|
382
|
-
|
|
383
|
-
|
|
|
384
|
-
|
|
385
|
-
|
|
|
386
|
-
|
|
|
387
|
-
|
|
388
|
-
|
|
|
389
|
-
|
|
390
|
-
|
|
|
391
|
-
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
|
400
|
-
|
|
|
401
|
-
|
|
|
402
|
-
|
|
|
403
|
-
|
|
404
|
-
|
|
|
405
|
-
|
|
406
|
-
|
|
|
407
|
-
|
|
|
408
|
-
|
|
409
|
-
|
|
|
410
|
-
|
|
411
|
-
|
|
|
412
|
-
|
|
|
413
|
-
|
|
|
414
|
-
|
|
415
|
-
|
|
|
416
|
-
|
|
417
|
-
|
|
|
418
|
-
|
|
|
419
|
-
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
|
430
|
-
|
|
|
431
|
-
|
|
|
432
|
-
|
|
|
433
|
-
|
|
|
434
|
-
|
|
|
435
|
-
|
|
|
436
|
-
|
|
|
437
|
-
|
|
|
438
|
-
|
|
|
439
|
-
|
|
|
440
|
-
|
|
|
441
|
-
|
|
|
442
|
-
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
|
447
|
-
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
|
452
|
-
|
|
|
453
|
-
|
|
|
454
|
-
|
|
455
|
-
|
|
|
456
|
-
|
|
|
457
|
-
|
|
|
458
|
-
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
|
462
|
-
|
|
|
463
|
-
|
|
|
464
|
-
|
|
|
465
|
-
|
|
466
|
-
|
|
|
467
|
-
|
|
|
468
|
-
|
|
|
469
|
-
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
|
481
|
-
|
|
|
482
|
-
|
|
|
483
|
-
|
|
|
484
|
-
|
|
|
485
|
-
|
|
|
486
|
-
|
|
|
487
|
-
|
|
488
|
-
|
|
|
489
|
-
|
|
|
490
|
-
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
|
494
|
-
|
|
|
495
|
-
|
|
|
496
|
-
|
|
497
|
-
|
|
|
498
|
-
| --- | --- |
|
|
499
|
-
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
|
511
|
-
|
|
|
512
|
-
|
|
513
|
-
|
|
|
514
|
-
|
|
|
515
|
-
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
|
519
|
-
|
|
|
520
|
-
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
|
524
|
-
|
|
|
525
|
-
| message |
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
|
537
|
-
| --- | --- |
|
|
538
|
-
|
|
|
539
|
-
|
|
|
540
|
-
|
|
541
|
-
|
|
|
542
|
-
| --- | --- |
|
|
543
|
-
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
- `
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
handlers.
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
handlers.
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
1
|
+
# @fv-ui/ai-chat
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
基于 Vue 3 + Element Plus 的 AI 对话组件库,复刻 element-plus-x 与 open-webui 对话框:从底层气泡、发送框、Markdown 渲染,到上层的对话整框(`<FvAiChat>`)、分支树消息、Artifacts 预览,一次给全。
|
|
8
|
+
|
|
9
|
+
- 14 个基础组件(Bubble/Sender/XMarkdown/Thinking/...)自由拼装
|
|
10
|
+
- 对话应用层整框 `<FvAiChat>`:顶栏模型多选、消息列表、分支切换、Artifacts 面板、发送停止一体化
|
|
11
|
+
- 树形消息模型(同构 open-webui):一条提问可挂多个模型回答,分支可切换、可重生成
|
|
12
|
+
- `ChatAdapter` 适配器协议:内置 OpenAI 兼容实现,可替换自研后端/Ollama
|
|
13
|
+
- 流式打字机、思考链折叠、KaTeX/代码高亮(Prism)开箱即用
|
|
14
|
+
|
|
15
|
+
## 安装
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @fv-ui/ai-chat
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
peerDependencies:`vue ^3.4.0`、`element-plus ^2.7.0`、`@element-plus/icons-vue ^2.3.0`。
|
|
22
|
+
|
|
23
|
+
## 快速开始
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// main.ts —— 需要先引入 element-plus(组件库依赖其组件与暗色变量)
|
|
27
|
+
import ElementPlus from 'element-plus'
|
|
28
|
+
import 'element-plus/dist/index.css'
|
|
29
|
+
import { createApp } from 'vue'
|
|
30
|
+
import App from './App.vue'
|
|
31
|
+
|
|
32
|
+
createApp(App).use(ElementPlus).mount('#app')
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```vue
|
|
36
|
+
<!-- App.vue -->
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import { FvAiChat } from '@fv-ui/ai-chat'
|
|
39
|
+
import '@fv-ui/ai-chat/dist/style.css'
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<FvAiChat
|
|
44
|
+
title="AI 助手"
|
|
45
|
+
base-url="https://api.deepseek.com/v1"
|
|
46
|
+
api-key="sk-xxx"
|
|
47
|
+
model="deepseek-chat"
|
|
48
|
+
style="height: 600px"
|
|
49
|
+
/>
|
|
50
|
+
</template>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`<FvAiChat>` 未传 `adapter` 时,用 `baseURL`/`apiKey`/`model` 构造内置 `OpenAIAdapter`(走 `/chat/completions`,SSE 流式)。两个都缺省时发送会报"未配置 adapter 或 baseURL"。
|
|
54
|
+
|
|
55
|
+
## 基础组件层 API
|
|
56
|
+
|
|
57
|
+
### XMarkdown
|
|
58
|
+
|
|
59
|
+
Markdown 渲染(Sanitized by DOMPurify),内置 Prism 代码高亮与 KaTeX。
|
|
60
|
+
|
|
61
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
62
|
+
| --- | --- | --- | --- |
|
|
63
|
+
| content | string | —(必填) | Markdown 源文本 |
|
|
64
|
+
| mdPlugins | PluginWithParams[] | — | 额外 markdown-it 插件 |
|
|
65
|
+
| highlight | (str, lang) => string | 内置 Prism | 自定义代码高亮 |
|
|
66
|
+
| isTyping | boolean | — | 打字机模式样式钩子(透传占位) |
|
|
67
|
+
|
|
68
|
+
| Ref | 说明 |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| getHtml() | 取当前渲染后的 HTML 字符串 |
|
|
71
|
+
|
|
72
|
+
### Typewriter
|
|
73
|
+
|
|
74
|
+
打字机输出,支持续打/重打/雾化遮罩。
|
|
75
|
+
|
|
76
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
77
|
+
| --- | --- | --- | --- |
|
|
78
|
+
| content | string | —(必填) | 全文(流式时增量传入,子集扩展自动续打) |
|
|
79
|
+
| isMarkdown | boolean | false | 走 XMarkdown 渲染(此时 suffix 不追加) |
|
|
80
|
+
| isFog | boolean \| { bgColor?; width? } | false | 打字中尾部雾化遮罩 |
|
|
81
|
+
| typing | boolean \| { step?; interval?; suffix? } | false | 开启打字机;对象形态可配步长/间隔/光标后缀 |
|
|
82
|
+
|
|
83
|
+
| Event | 说明 |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| start / writing / finish | 开始打字 / 每次推进 / 全部打完 |
|
|
86
|
+
|
|
87
|
+
| Ref | 说明 |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| interrupt() / continue() / restart() / destroy() | 暂停 / 继续 / 重打 / 重置清空 |
|
|
90
|
+
| renderedContent / isTyping / progress | 已输出文本 / 是否打字中 / 进度 0-1 |
|
|
91
|
+
|
|
92
|
+
### Bubble
|
|
93
|
+
|
|
94
|
+
单条气泡(内部按需挂载 Typewriter/XMarkdown)。
|
|
95
|
+
|
|
96
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| content | string | '' | 气泡内容 |
|
|
99
|
+
| placement | 'start' \| 'end' | 'start' | start 靠左 / end 靠右 |
|
|
100
|
+
| loading | boolean | false | 加载态(三点动画,可被 #loading 覆盖) |
|
|
101
|
+
| typing | boolean \| TypingConfig | false | 打字机模式(透传 Typewriter) |
|
|
102
|
+
| isMarkdown | boolean | false | 内容按 Markdown 渲染 |
|
|
103
|
+
| isFog | boolean \| { bgColor?; width? } | false | 雾化(自定义气泡背景时传 bgColor) |
|
|
104
|
+
| variant | 'filled' \| 'borderless' \| 'outlined' \| 'shadow' | 'filled' | 气泡样式 |
|
|
105
|
+
| shape | 'round' \| 'corner' | 'round' | 圆角 / 方角 |
|
|
106
|
+
| avatar | string | '' | 头像图片地址 |
|
|
107
|
+
| avatarSize | ElAvatar size \| number | '' | 头像尺寸 |
|
|
108
|
+
| avatarGap | string | '12px' | 头像与内容间距 |
|
|
109
|
+
| avatarShape | 'circle' \| 'square' | 'circle' | 头像形状 |
|
|
110
|
+
| avatarIcon | Component | — | 头像图标(与 avatar 冲突时优先) |
|
|
111
|
+
| avatarSrcSet / avatarAlt | string | '' / '' | 头像 srcSet/alt |
|
|
112
|
+
| avatarFit | 'fill' \| 'contain' \| 'cover' \| 'none' \| 'scale-down' | 'cover' | 头像填充方式(el-avatar fit) |
|
|
113
|
+
| noPadding | boolean | false | 内容区去内边距 |
|
|
114
|
+
| noStyle | boolean | false | 去气泡样式(透明/无框/无阴影) |
|
|
115
|
+
|
|
116
|
+
| Event | 说明 |
|
|
117
|
+
| --- | --- |
|
|
118
|
+
| avatar-error | 头像加载失败 |
|
|
119
|
+
| start / writing / finish | typing 模式下的打字机事件透传 |
|
|
120
|
+
|
|
121
|
+
| Slot | 说明 |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| avatar / header / content / loading / footer | 自定义头像 / 头部 / 内容(优先于 content prop)/ 加载态 / 底部 |
|
|
124
|
+
|
|
125
|
+
| Ref | 说明 |
|
|
126
|
+
| --- | --- |
|
|
127
|
+
| interrupt / continue / restart / destroy | Typewriter 方法转发(非 typing 模式为 no-op) |
|
|
128
|
+
| renderedContent / isTyping / progress | Typewriter 状态转发 |
|
|
129
|
+
| twInstance | 内部 Typewriter 原始实例(BubbleList @complete 透传用) |
|
|
130
|
+
|
|
131
|
+
### BubbleList
|
|
132
|
+
|
|
133
|
+
气泡列表 + 吸底/回底按钮。
|
|
134
|
+
|
|
135
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
136
|
+
| --- | --- | --- | --- |
|
|
137
|
+
| list | BubbleListItemProps[] | [] | 列表项(Bubble props + 必填 key) |
|
|
138
|
+
| maxHeight | string | '' | 滚动容器最大高度(如 '350px'/'60vh') |
|
|
139
|
+
| alwaysShowScrollbar | boolean | false | 始终显示滚动条 |
|
|
140
|
+
| showBackButton | boolean | true | 是否显示回底按钮 |
|
|
141
|
+
| backButtonThreshold | number | 80 | 距底超过该像素显示回底按钮 |
|
|
142
|
+
| btnLoading | boolean | false | 回底按钮加载态 |
|
|
143
|
+
| btnColor | string | '' | 回底按钮颜色(el-button color) |
|
|
144
|
+
| backButtonPosition | { bottom?; left? } | {} | 回底按钮位置偏移 |
|
|
145
|
+
| btnIconSize | number | 16 | 回底按钮图标尺寸 |
|
|
146
|
+
| triggerIndices | 'only-last' \| 'all' \| number[] | 'only-last' | @complete 触发索引 |
|
|
147
|
+
|
|
148
|
+
| Event | 说明 |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| complete | (typewriterInstance, index) 打字完成(按 triggerIndices 过滤后上抛) |
|
|
151
|
+
|
|
152
|
+
| Slot | 说明 |
|
|
153
|
+
| --- | --- |
|
|
154
|
+
| avatar / header / content / loading / footer | 透传给每个 Bubble,作用域 { item, index } |
|
|
155
|
+
|
|
156
|
+
| Ref | 说明 |
|
|
157
|
+
| --- | --- |
|
|
158
|
+
| scrollToTop() / scrollToBottom() / scrollToBubble(index) | 滚动控制 |
|
|
159
|
+
|
|
160
|
+
### Sender
|
|
161
|
+
|
|
162
|
+
输入发送框。
|
|
163
|
+
|
|
164
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
165
|
+
| --- | --- | --- | --- |
|
|
166
|
+
| modelValue | string(v-model) | '' | 输入值(缺省内部自持) |
|
|
167
|
+
| placeholder | string | '' | 占位文案 |
|
|
168
|
+
| autosize | { minRows?; maxRows? } | {} | 自增高(行高 22px) |
|
|
169
|
+
| submitType | 'enter' \| 'shiftEnter' \| 'cmdOrCtrlEnter' \| 'altEnter' | 'enter' | 提交按键模式 |
|
|
170
|
+
| loading | boolean | false | 加载态(发送键变停止键,输入区禁用) |
|
|
171
|
+
| disabled | boolean | false | 禁用(阻断提交) |
|
|
172
|
+
| readOnly | boolean | false | 只读 |
|
|
173
|
+
| clearable | boolean | false | 显示清空按钮 |
|
|
174
|
+
| round | boolean | true | 主按钮(发送/停止)为圆形(element-plus circle;false 时方按钮,停止键光带沿方框流转) |
|
|
175
|
+
| shadow | boolean | false | 输入区阴影效果(浮起感,聚焦时加深) |
|
|
176
|
+
| inputWidth | string | '' | 输入区宽度(variant=default 时有效) |
|
|
177
|
+
| variant | 'default' \| 'updown' | 'default' | default 同排 / updown 上输入下操作栏 |
|
|
178
|
+
| submitBtnDisabled | boolean | false | 附加禁用发送按钮 |
|
|
179
|
+
| triggerStrings | string[] | [] | 指令触发串(光标前命中弹指令框) |
|
|
180
|
+
| triggerPopoverVisible | boolean(v-model:triggerPopoverVisible) | false | 指令弹框显隐 |
|
|
181
|
+
| triggerPopoverWidth / triggerPopoverLeft | string | '' | 指令弹框宽度 / left 偏移 |
|
|
182
|
+
| triggerPopoverOffset | number | 0 | 弹框距触发字符偏移量 |
|
|
183
|
+
| triggerPopoverPlacement | ElPopover placement | 'top-start' | 弹框位置 |
|
|
184
|
+
| allowSpeech | boolean | false | 开启语音输入:麦克风按钮 → 录音条(波形+时长,Esc 取消)→ 确认后转写文本插入光标处;浏览器 SpeechRecognition 实时转写 |
|
|
185
|
+
| allowEmptySubmit | boolean | false | 允许空内容提交(附件场景:只发文件不输文字) |
|
|
186
|
+
|
|
187
|
+
| Event | 说明 |
|
|
188
|
+
| --- | --- |
|
|
189
|
+
| update:modelValue / change | 输入值变化 |
|
|
190
|
+
| submit | (value) 提交(提交后内部清空) |
|
|
191
|
+
| cancel | 点击停止按钮 |
|
|
192
|
+
| trigger | (keyword, visible) 命中指令串 |
|
|
193
|
+
| recording-change | 语音录制状态变化(预留) |
|
|
194
|
+
| paste-file | (file) 粘贴文件 |
|
|
195
|
+
| update:triggerPopoverVisible | 指令弹框显隐同步 |
|
|
196
|
+
|
|
197
|
+
| Slot | 说明 |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| header / footer | 头部/底部(配合 ref.openHeader/closeHeader) |
|
|
200
|
+
| prefix | 输入区前缀 |
|
|
201
|
+
| action-list | 自定义操作区(存在时隐藏内置按钮) |
|
|
202
|
+
| action-left | 操作栏左侧附加区(与提交按钮同行靠左,如附件回形针) |
|
|
203
|
+
| trigger | 指令弹框内容(作用域 { keyword }) |
|
|
204
|
+
|
|
205
|
+
| Ref | 说明 |
|
|
206
|
+
| --- | --- |
|
|
207
|
+
| submit() / cancel() / clear() | 提交 / 停止 / 清空 |
|
|
208
|
+
| focus(pos?) / blur() | pos: 'all' 全选 \| 'start' 头 \| 'end' 尾(缺省 end) |
|
|
209
|
+
| openHeader() / closeHeader() | 显隐 #header 插槽 |
|
|
210
|
+
|
|
211
|
+
### MentionSender
|
|
212
|
+
|
|
213
|
+
带 @提及 的 Sender(键盘 ↑↓ 导航 / Enter 确认 / Esc 关闭)。
|
|
214
|
+
|
|
215
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
216
|
+
| --- | --- | --- | --- |
|
|
217
|
+
| modelValue | string(v-model) | '' | 输入值 |
|
|
218
|
+
| userList | MentionUserInfo[] | [] | @ 触发的候选用户(id/name/avatar/pinyin) |
|
|
219
|
+
| customTrigger | MentionCustomTrigger[] | [] | 自定义触发组 { dialogTitle, prefix, tagList } |
|
|
220
|
+
| asyncMatchFun | (search) => Promise<MentionUserInfo[]> | — | 异步候选(存在时防抖 300ms,后端过滤) |
|
|
221
|
+
|
|
222
|
+
事件:`update:modelValue` / `change` / `submit` / `cancel` / `trigger` / `recording-change` / `paste-file` / `update:triggerPopoverVisible` / `show-at-dialog`(弹层显隐);其余 props/事件经 `$attrs` 透传给 Sender,slot 经 forwardedSlots 显式转发(header/footer/prefix/action-list/trigger)。
|
|
223
|
+
|
|
224
|
+
| Ref | 说明 |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| submit / cancel / clear / focus / blur / openHeader / closeHeader | Sender 方法转发 |
|
|
227
|
+
| setUserTag(userId) | 光标处程序化插入 @用户 |
|
|
228
|
+
| setCustomTag(prefix, id) | 光标处插入自定义触发标签 |
|
|
229
|
+
|
|
230
|
+
### EditorSender
|
|
231
|
+
|
|
232
|
+
富文本标签编辑器(contenteditable),open-webui 风格标签芯片。
|
|
233
|
+
|
|
234
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
235
|
+
| --- | --- | --- | --- |
|
|
236
|
+
| placeholder | string | '' | 占位文案 |
|
|
237
|
+
| device | 'pc' \| 'h5' | 'pc' | 设备形态(仅影响样式) |
|
|
238
|
+
| autoFocus | boolean | false | 挂载后聚焦到末尾 |
|
|
239
|
+
| variant | 'default' \| 'updown' | 'default' | 布局形态 |
|
|
240
|
+
| userList | EditorUser[] | [] | @ 候选用户 |
|
|
241
|
+
| customTrigger | EditorCustomTrigger[] | [] | 自定义触发组 |
|
|
242
|
+
| selectList | EditorSelectGroup[] | [] | 选择标签分组(openSelectDialog 弹窗数据) |
|
|
243
|
+
| maxLength | number | 0 | 纯文本最大长度(0 不限;性能开销大,仅显式开启) |
|
|
244
|
+
| submitType | 'enter' \| 'shiftEnter' | 'enter' | 提交按键模式 |
|
|
245
|
+
| customStyle | Record<string, any> | {} | 根节点自定义样式 |
|
|
246
|
+
| loading | boolean | false | 加载态(发送键变停止键) |
|
|
247
|
+
| disabled | boolean | false | 禁用 |
|
|
248
|
+
| clearable | boolean | false | 显示清空按钮 |
|
|
249
|
+
| headerAnimationTimer | number | 300 | 头部过渡时长(ms) |
|
|
250
|
+
| asyncMatchFun | (search) => Promise<EditorUser[]> | — | 异步 @ 候选(防抖 300ms) |
|
|
251
|
+
| customDialog | boolean | false | 选择标签不自弹内置弹窗,仅上抛 show-select-dialog |
|
|
252
|
+
|
|
253
|
+
| Event | 说明 |
|
|
254
|
+
| --- | --- |
|
|
255
|
+
| submit / change | (SubmitResult) SubmitResult = { text, html, tags: { userTags, selectTags, inputTags, customTags } } |
|
|
256
|
+
| cancel | 点击停止按钮 |
|
|
257
|
+
| show-at-dialog / show-select-dialog / show-tag-dialog | 各弹层显隐 |
|
|
258
|
+
|
|
259
|
+
| Slot | 说明 |
|
|
260
|
+
| --- | --- |
|
|
261
|
+
| header / footer / prefix / action-list | 同 Sender |
|
|
262
|
+
| tag-tip | 输入标签 tip 弹层内容(作用域 { nodeId }) |
|
|
263
|
+
|
|
264
|
+
| Ref | 说明 |
|
|
265
|
+
| --- | --- |
|
|
266
|
+
| getCurrentValue() | 取 { text, html, tags } |
|
|
267
|
+
| focusToStart() / focusToEnd() / blur() / selectAll() / clear() | 光标与清空 |
|
|
268
|
+
| setText(text) / setHtml(html) / setMixTags(rows) | 程序化写入 |
|
|
269
|
+
| setUserTag(id) / setCustomTag(prefix, id) / setInputTag(id, name) / setSelectTag(list) | 插入各类标签 |
|
|
270
|
+
| customSetUser(user) / customSetTag(tag) | 外部直传对象插入(不经列表查表) |
|
|
271
|
+
| updateSelectTag(method, options) | 选择标签更新:'insertTag'/'updateTagValue'/'deleteTag'/'updateTagStyle' |
|
|
272
|
+
| openSelectDialog() / openTipTag(nodeId?) / closeTipTag() | 弹窗控制 |
|
|
273
|
+
| openHeader() / closeHeader() | 显隐 #header |
|
|
274
|
+
| chat / opNode(method, options) / chatState() | 底层文档模型:chat.insert/update/delete/append;opNode 按方法名分发 |
|
|
275
|
+
|
|
276
|
+
### Thinking
|
|
277
|
+
|
|
278
|
+
思考链折叠面板。
|
|
279
|
+
|
|
280
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
281
|
+
| --- | --- | --- | --- |
|
|
282
|
+
| modelValue | boolean(v-model) | false | 展开/收起 |
|
|
283
|
+
| content | string | '' | 思考内容(常配合 #content 插槽嵌 XMarkdown) |
|
|
284
|
+
| status | 'start' \| 'thinking' \| 'end' \| 'error' | 'start' | 思考状态 |
|
|
285
|
+
| disabled | boolean | false | 禁用切换 |
|
|
286
|
+
| autoCollapse | boolean | false | status 变为 end 时自动收起 |
|
|
287
|
+
| buttonWidth | string | '' | 切换按钮区宽度 |
|
|
288
|
+
| maxWidth | string | '' | 内容区最大宽度 |
|
|
289
|
+
| color / backgroundColor | string | '' | 前景色 / 背景色 |
|
|
290
|
+
|
|
291
|
+
| Event | 说明 |
|
|
292
|
+
| --- | --- |
|
|
293
|
+
| update:modelValue | 展开/收起同步 |
|
|
294
|
+
|
|
295
|
+
| Slot | 说明 |
|
|
296
|
+
| --- | --- |
|
|
297
|
+
| status-icon / label / arrow | 自定义状态图标 / 文案 / 箭头 |
|
|
298
|
+
| content / error | 思考内容 / error 态内容 |
|
|
299
|
+
|
|
300
|
+
| Ref | 说明 |
|
|
301
|
+
| --- | --- |
|
|
302
|
+
| modelValue / toggle() | 展开状态 / 手动切换 |
|
|
303
|
+
|
|
304
|
+
### ThoughtChain
|
|
305
|
+
|
|
306
|
+
思维链步骤条。
|
|
307
|
+
|
|
308
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
309
|
+
| --- | --- | --- | --- |
|
|
310
|
+
| items | ThoughtChainItem[] | [] | 步骤项 { key, title?, status?: 'pending'/'success'/'loading'/'error', icon?, description? } |
|
|
311
|
+
| direction | 'vertical' \| 'horizontal' | 'vertical' | 排列方向 |
|
|
312
|
+
|
|
313
|
+
| Slot | 说明 |
|
|
314
|
+
| --- | --- |
|
|
315
|
+
| content | 覆盖默认 title/description(作用域 { item, index }) |
|
|
316
|
+
|
|
317
|
+
### Welcome
|
|
318
|
+
|
|
319
|
+
空态欢迎页。
|
|
320
|
+
|
|
321
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
322
|
+
| --- | --- | --- | --- |
|
|
323
|
+
| icon | string \| Component | '' | 图标(字符串为图片地址;未传渲染内置对话图标) |
|
|
324
|
+
| name | string | '' | 名称 |
|
|
325
|
+
| description | string | '' | 描述(纯文本) |
|
|
326
|
+
|
|
327
|
+
| Slot | 说明 |
|
|
328
|
+
| --- | --- |
|
|
329
|
+
| icon / name / description | 覆盖对应区块 |
|
|
330
|
+
|
|
331
|
+
### Prompts
|
|
332
|
+
|
|
333
|
+
建议 prompt 网格。
|
|
334
|
+
|
|
335
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
336
|
+
| --- | --- | --- | --- |
|
|
337
|
+
| prompts | PromptItem[] | [] | 建议项 { key(必填), label?, description?, icon?(EP 图标名字符串或组件), disabled? } |
|
|
338
|
+
| title | string | '' | 网格标题 |
|
|
339
|
+
|
|
340
|
+
| Event | 说明 |
|
|
341
|
+
| --- | --- |
|
|
342
|
+
| select | (item) 点击(disabled 项不触发) |
|
|
343
|
+
|
|
344
|
+
| Slot | 说明 |
|
|
345
|
+
| --- | --- |
|
|
346
|
+
| default | 覆盖整卡(作用域 { item, index }) |
|
|
347
|
+
|
|
348
|
+
### FilesCard
|
|
349
|
+
|
|
350
|
+
单个文件卡片。文档模式按扩展名自动映射类型图标:pdf/doc/txt→文档、xls/csv→图表、ppt→演示、mp4→胶片、mp3→耳机、zip→文件夹、html/url→链接、apk→手机、exe→芯片,未命中回落通用文档图标。
|
|
351
|
+
|
|
352
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
353
|
+
| --- | --- | --- | --- |
|
|
354
|
+
| name | string | '' | 文件名 |
|
|
355
|
+
| fileType | 'image' \| 'file' | 'file' | image 渲染缩略图,file 渲染文档图标卡 |
|
|
356
|
+
| size | number \| string | 0 | 文件大小(字节自动格式化为 B/KB/MB;字符串原样展示;0 不显示) |
|
|
357
|
+
| url | string | '' | 图片地址(image 模式缩略图) |
|
|
358
|
+
| status | 'loading' \| 'success' \| 'error' | 'success' | loading spinner / error 红边 |
|
|
359
|
+
| imageSize | string | '' | 缩略图尺寸(width/height 同值) |
|
|
360
|
+
| onDelete | () => void | — | 删除按钮回调 |
|
|
361
|
+
|
|
362
|
+
| Slot | 说明 |
|
|
363
|
+
| --- | --- |
|
|
364
|
+
| image | 覆盖图片区(默认 url 缩略图,无 url 渲染 Picture 占位) |
|
|
365
|
+
|
|
366
|
+
### Attachments
|
|
367
|
+
|
|
368
|
+
附件选择与列表(v-model)。
|
|
369
|
+
|
|
370
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
371
|
+
| --- | --- | --- | --- |
|
|
372
|
+
| modelValue | object[](v-model) | [] | 附件列表(项含 uid/name/size/url/fileType/status/raw) |
|
|
373
|
+
| accept | string | '' | input accept,如 'image/*' |
|
|
374
|
+
| tip | string | '' | 内联提示文案 |
|
|
375
|
+
| count | number | 0 | 最大附件数(超出走 @exceed;0 不限) |
|
|
376
|
+
| beforeUpload | (file) => boolean \| Promise<boolean> | — | 上传前校验,返回 false 拦截 |
|
|
377
|
+
| layout | 'inline' \| 'triggerOnly' \| 'listOnly' | 'inline' | 布局:inline 回形针+列表+提示一行;triggerOnly 只渲染回形针按钮;listOnly 只渲染文件列表(拆分布局供整框组合:按钮进操作栏、列表进输入区上方) |
|
|
378
|
+
|
|
379
|
+
| Event | 说明 |
|
|
380
|
+
| --- | --- |
|
|
381
|
+
| update:modelValue / change | 列表变化(全量数组) |
|
|
382
|
+
| exceed | (file) 超出 count |
|
|
383
|
+
| delete | (item) 删除某项 |
|
|
384
|
+
|
|
385
|
+
| Slot | 说明 |
|
|
386
|
+
| --- | --- |
|
|
387
|
+
| list-item | 覆盖项渲染(作用域 { item, index }) |
|
|
388
|
+
| list-thumb | 覆盖图片缩略图(作用域 { item, index }) |
|
|
389
|
+
|
|
390
|
+
| Ref | 说明 |
|
|
391
|
+
| --- | --- |
|
|
392
|
+
| openSelect() | 程序化打开文件选择 |
|
|
393
|
+
| retry(item) | error 项重新置为 loading(上传逻辑由外部接线) |
|
|
394
|
+
|
|
395
|
+
### Conversations
|
|
396
|
+
|
|
397
|
+
会话列表(按 group 分组)。
|
|
398
|
+
|
|
399
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
400
|
+
| --- | --- | --- | --- |
|
|
401
|
+
| items | ConversationItem[] | [] | 会话项 { key(必填), label(必填), group?, disabled? } |
|
|
402
|
+
| active | string \| number(v-model:active) | undefined | 当前激活会话 key |
|
|
403
|
+
| showBuiltInMenuType | 'hover' \| 'always' | 'hover' | 内置菜单按钮显隐方式 |
|
|
404
|
+
| groupable | (a, b) => number | — | 分组排序比较器(缺省保持插入顺序) |
|
|
405
|
+
|
|
406
|
+
| Event | 说明 |
|
|
407
|
+
| --- | --- |
|
|
408
|
+
| update:active / change | 选中会话(key / item) |
|
|
409
|
+
| menu-command | ({ item, command: 'rename'/'delete', value? }) 菜单指令;rename 确认后带新名称 |
|
|
410
|
+
|
|
411
|
+
| Slot | 说明 |
|
|
412
|
+
| --- | --- |
|
|
413
|
+
| group-title | 分组标题(作用域 { group }) |
|
|
414
|
+
| item | 覆盖行内容(作用域 { item, active }) |
|
|
415
|
+
| menu | 覆盖菜单项(默认重命名/删除;作用域 { item }) |
|
|
416
|
+
|
|
417
|
+
| Ref | 说明 |
|
|
418
|
+
| --- | --- |
|
|
419
|
+
| handleMenuCommand({ item, command }) | 程序化触发菜单指令转发(纯转发,同步上抛 menu-command) |
|
|
420
|
+
| renamePrompt(key) | 弹重命名输入框,确认后上抛 menu-command |
|
|
421
|
+
| deleteConfirm(key) | 弹删除确认框,确认后上抛 menu-command |
|
|
422
|
+
|
|
423
|
+
## 对话应用层 API
|
|
424
|
+
|
|
425
|
+
### AiChat
|
|
426
|
+
|
|
427
|
+
对话整框:顶栏(标题+模型多选)、消息区(空态 Welcome/Prompts)、输入区(Sender)、可选 Artifacts 面板。
|
|
428
|
+
|
|
429
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
430
|
+
| --- | --- | --- | --- |
|
|
431
|
+
| model | string | '' | 默认模型(未传 models 时作为单模型) |
|
|
432
|
+
| models | string[] | [] | 可选模型列表(有值时顶栏多选选择器) |
|
|
433
|
+
| baseURL | string | '' | OpenAI 兼容服务地址(未传 adapter 时构造 OpenAIAdapter) |
|
|
434
|
+
| apiKey | string | '' | 服务密钥 |
|
|
435
|
+
| adapter | ChatAdapter | undefined | 聊天适配器(与 baseURL 二选一,优先) |
|
|
436
|
+
| messages | MessageNode[](v-model:messages) | undefined | 消息链(半受控,见下) |
|
|
437
|
+
| title | string | '' | 顶栏标题(同时作空态欢迎页名称) |
|
|
438
|
+
| showHeader | boolean | false | 显示顶栏(标题 + 模型选择器;models 多选选择器在顶栏内,需开启才可见) |
|
|
439
|
+
| centerWelcome | boolean | true | 空态居中:未开始对话时欢迎内容与输入区整体垂直居中,首条消息后输入区恢复固定底部 |
|
|
440
|
+
| placeholder | string | '' | 输入框占位 |
|
|
441
|
+
| senderShadow | boolean | true | 输入区阴影效果(透传 Sender 的 shadow) |
|
|
442
|
+
| suggestions | AiChatSuggestion[] | [] | 空态建议列表(Prompts 项,key 必填) |
|
|
443
|
+
| features | AiChatFeatures | {} | 特性开关 { artifacts?, tts?, queue?, continueGenerate?, fullscreenEditor? }(当前仅 artifacts 生效) |
|
|
444
|
+
| allowAttachments | boolean | false | 开启输入区附件上传(回形针在操作栏左侧与提交按钮同行,已选列表在输入框上方;只传附件不输文字也可提交) |
|
|
445
|
+
| accept | string | '' | 附件 input accept 属性(透传 Attachments),如 'image/*' |
|
|
446
|
+
| attachmentLimit | number | 0 | 附件数量上限(0 不限,超限走 @attachment-exceed) |
|
|
447
|
+
| theme | 'light' \| 'dark' \| 'auto' | 'light' | 主题(向 html 注入/移除 dark class) |
|
|
448
|
+
|
|
449
|
+
**v-model:messages 半受控语义**:外部 `messages` 仅在 setup 时一次性深拷贝灌入(用于恢复历史);组件内部每次变更全量上抛 `update:messages`(同帧去抖),父组件需经 v-model 回写维持单向数据流;父组件后续直接改 prop **不会**回灌生效。
|
|
450
|
+
|
|
451
|
+
| Event | 说明 |
|
|
452
|
+
| --- | --- |
|
|
453
|
+
| update:messages | (MessageNode[]) 全量消息链(去抖后) |
|
|
454
|
+
| send | (text, files?) 用户发送(allowAttachments 开启时 files 为降维后的 ChatFile[]) |
|
|
455
|
+
| message-complete | (message) 链尾 assistant 生成完成 |
|
|
456
|
+
| error | (err, message?) 生成出错 |
|
|
457
|
+
| branch-change | (dir, message) 分支切换 |
|
|
458
|
+
| attachment-exceed | (file) 附件超过 attachmentLimit 上限 |
|
|
459
|
+
|
|
460
|
+
| Slot | 说明 |
|
|
461
|
+
| --- | --- |
|
|
462
|
+
| message-avatar / message-actions | 透传给 AiMessages(作用域见 AiMessage) |
|
|
463
|
+
| empty | 覆盖空态整区 |
|
|
464
|
+
| sender-prefix | 输入区前缀 |
|
|
465
|
+
|
|
466
|
+
| Ref | 说明 |
|
|
467
|
+
| --- | --- |
|
|
468
|
+
| sendMessage(text, files?) | 程序化发送 |
|
|
469
|
+
| stop() | 停止生成(保留半截内容,静默收口不发 complete/error) |
|
|
470
|
+
| regenerate(messageId?) | 重生成(缺省链尾 assistant) |
|
|
471
|
+
| appendToMessage(id, text) | 向指定消息追加文本 |
|
|
472
|
+
| clearMessages() | 清空消息树 |
|
|
473
|
+
| exportTxt() | 导出当前链为纯文本 |
|
|
474
|
+
| openArtifact(key) | 程序化打开某个 artifact |
|
|
475
|
+
|
|
476
|
+
### AiMessages
|
|
477
|
+
|
|
478
|
+
消息列表(空态渲染 Welcome + Prompts),吸底滚动。
|
|
479
|
+
|
|
480
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
481
|
+
| --- | --- | --- | --- |
|
|
482
|
+
| messages | MessageNode[] | —(必填) | 当前链消息(根 → 焦点) |
|
|
483
|
+
| nodes | Record<string, MessageNode> | {} | 全量节点表(分支计算需查父 childrenIds) |
|
|
484
|
+
| icon | string \| Component | '' | 空态欢迎页图标 |
|
|
485
|
+
| name | string | '' | 空态欢迎页名称 |
|
|
486
|
+
| description | string | '' | 空态欢迎页描述 |
|
|
487
|
+
| suggestions | AiMessagesSuggestion[] | [] | 空态建议列表 |
|
|
488
|
+
| suggestionsTitle | string | '' | 空态建议区标题 |
|
|
489
|
+
| scrollThreshold | number | 40 | 吸底阈值(px):距底不超过该值才自动跟随 |
|
|
490
|
+
| showActions | boolean | true | 显示消息操作按钮(复制/重生成) |
|
|
491
|
+
| showBranchSwitch | boolean | true | 显示分支切换器 |
|
|
492
|
+
|
|
493
|
+
| Event | 说明 |
|
|
494
|
+
| --- | --- |
|
|
495
|
+
| select / copy / regenerate / branch-change | 建议选中 / 复制 / 重生成 / 分支切换,载荷同 AiChat |
|
|
496
|
+
|
|
497
|
+
| Slot | 说明 |
|
|
498
|
+
| --- | --- |
|
|
499
|
+
| empty | 覆盖空态整区 |
|
|
500
|
+
| message-avatar / message-actions | 透传给每条 AiMessage |
|
|
501
|
+
|
|
502
|
+
| Ref | 说明 |
|
|
503
|
+
| --- | --- |
|
|
504
|
+
| scrollToBottom(smooth?) / scrollToTop() | 滚动控制 |
|
|
505
|
+
|
|
506
|
+
### AiMessage
|
|
507
|
+
|
|
508
|
+
单条消息(Markdown 正文 + 思考链折叠 + 附件 + 操作区)。
|
|
509
|
+
|
|
510
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
511
|
+
| --- | --- | --- | --- |
|
|
512
|
+
| message | MessageNode | —(必填) | 消息节点 |
|
|
513
|
+
| branch | AiMessageBranchInfo | undefined | 分支元数据 { count, index };缺省视为单分支 |
|
|
514
|
+
| showActions | boolean | true | 显示复制/重生成(仅 assistant) |
|
|
515
|
+
| showBranchSwitch | boolean | true | 显示分支切换器(branch.count > 1 时) |
|
|
516
|
+
| autoCollapse | boolean | true | Thinking autoCollapse 透传 |
|
|
517
|
+
|
|
518
|
+
| Event | 说明 |
|
|
519
|
+
| --- | --- |
|
|
520
|
+
| copy / regenerate / branch-change | (message) / (message) / (dir, message) |
|
|
521
|
+
|
|
522
|
+
| Slot | 说明 |
|
|
523
|
+
| --- | --- |
|
|
524
|
+
| message-avatar | 自定义头像(作用域 { message }) |
|
|
525
|
+
| message-files | 自定义附件渲染(作用域 { message, files }) |
|
|
526
|
+
| message-actions | 自定义操作区(作用域 { message }) |
|
|
527
|
+
|
|
528
|
+
| Ref | 说明 |
|
|
529
|
+
| --- | --- |
|
|
530
|
+
| message | 当前消息节点(computed) |
|
|
531
|
+
|
|
532
|
+
### AiArtifacts
|
|
533
|
+
|
|
534
|
+
Artifacts 预览面板(iframe 沙箱,从消息 ```html / ```svg 代码块提取)。
|
|
535
|
+
|
|
536
|
+
| Prop | 类型 | 默认 | 说明 |
|
|
537
|
+
| --- | --- | --- | --- |
|
|
538
|
+
| artifacts | AiArtifactItem[] | [] | artifact 列表 { key(必填), type: 'html'/'svg', content, title? } |
|
|
539
|
+
| activeKey | string | '' | 当前展示的 key;为空时面板隐藏 |
|
|
540
|
+
|
|
541
|
+
| Event | 说明 |
|
|
542
|
+
| --- | --- |
|
|
543
|
+
| close / change | 关闭 / 切换(key) |
|
|
544
|
+
| navigate | (href) iframe 内点击外链被拦截上抛 |
|
|
545
|
+
|
|
546
|
+
| Ref | 说明 |
|
|
547
|
+
| --- | --- |
|
|
548
|
+
| open(key) / close() | 程序化显隐 |
|
|
549
|
+
|
|
550
|
+
## Composables
|
|
551
|
+
|
|
552
|
+
### useChat(options)
|
|
553
|
+
|
|
554
|
+
聊天编排:发送 → 流式回填 → 队列/重生成/续写。
|
|
555
|
+
|
|
556
|
+
```ts
|
|
557
|
+
const chat = useChat({
|
|
558
|
+
adapter: () => OpenAIAdapter({ baseURL, apiKey }), // 实例或工厂;工厂每次 send 惰性取用
|
|
559
|
+
models: () => selectedModels.value, // 数组或工厂;多模型并行生成兄弟分支
|
|
560
|
+
initial: { messages, currentId }, // 恢复会话
|
|
561
|
+
onToolEvent: (ev) => { /* det-oms 风格 tool{status},展示「正在查询库存…」*/ }
|
|
562
|
+
})
|
|
563
|
+
// chat: { send, stop, regenerate, continueGenerate, enqueue, queue, isLoading,
|
|
564
|
+
// msgs, messages, currentId, chain }
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
- `send(text, files?)`:生成中调用被拒;多模型时各模型一个 assistant 节点并发回填
|
|
568
|
+
- `stop()`:中止全部在途请求,在途节点置 done(保留半截内容)
|
|
569
|
+
- `regenerate(messageId?)`:回退焦点到父节点重发生成新分支(旧回答保留)
|
|
570
|
+
- `continueGenerate(messageId?)`:以既有内容为前缀续写,增量追加到同一节点
|
|
571
|
+
- `enqueue(text, files?)`:生成中入队,完成后自动 flush 队首
|
|
572
|
+
- `onToolEvent`:适配器 `onTool` 回调的透传挂点(如 DetOmsChatAdapter 的 tool{status} 事件)
|
|
573
|
+
|
|
574
|
+
### DetOmsChatAdapter(config)
|
|
575
|
+
|
|
576
|
+
det-oms(`/api/ai-chat/*`)风格聊天适配器:五事件 SSE 协议(`message_start` / `tool` / `delta` / `message_end` / `error`),POST `/ai-chat/message/send`。会话管理(列表/归档/删除等)由消费方直接调后端,不经此适配器。
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
const adapter = DetOmsChatAdapter({
|
|
580
|
+
baseURL: '/api', // 或 https://host
|
|
581
|
+
headers: () => ({ Authorization: 'Bearer ' + token }), // 惰性取用
|
|
582
|
+
sessionId: () => currentSessionId.value, // 工厂:切换会话后下一次 send 生效
|
|
583
|
+
errorMessages: { 90602: '已有进行中的对话' } // 业务码 → 文案映射(可选)
|
|
584
|
+
})
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
- 同步段失败(Content-Type 非 `text/event-stream`)按 JSON 错误 `{code,message}` 走 `onError`;命中 `errorMessages` 时用映射文案(带 `code` 的 `DetOmsError`)
|
|
588
|
+
- `message_end` 的 `messageId`(最终文本行 id)经 `onBackendMessageId` 回填到 `MessageNode.backendId`——多轮工具循环下与 `message_start` 的首轮 id 不同,UI 落库/对账以它为准
|
|
589
|
+
- `tool` 事件经 `onTool` → `useChat.onToolEvent` → `AiChat @tool-event` 上抛,消费方展示「正在查询库存…」等中间态
|
|
590
|
+
- 配套 DTO 类型:`DetOmsSessionItem` / `DetOmsMessage` / `DetOmsSessionDetail` / `DetOmsMessagePage`(消费方直接调会话管理端点时用)
|
|
591
|
+
|
|
592
|
+
### useMessages(initial?)
|
|
593
|
+
|
|
594
|
+
树形消息状态中枢(响应式包装 `createMessageTree`)。
|
|
595
|
+
|
|
596
|
+
返回:`messages`(全量节点表)、`currentId`、`chain`(当前链 computed)、`add`、`remove`、`edit`、`appendContent`、`navigate(id?, dir?)`、`setCurrent(id)`、`setStorage(adapter)`、`load()`、`flush()`、`toJSON()`。绑定存储后树变更自动去抖(150ms)落盘。
|
|
597
|
+
|
|
598
|
+
### useXStream()
|
|
599
|
+
|
|
600
|
+
SSE 流消费:`startStream({ readableStream, transformStream? })`、`cancel()`、`data: Ref<SSEEvent[]>`、`error`、`isLoading`。内部完成 `\r\n`/跨 chunk 边界归一化。
|
|
601
|
+
|
|
602
|
+
### useSend() / XRequest
|
|
603
|
+
|
|
604
|
+
`useSend()`:`send({ url, method?, headers?, body?, transformStream?, onChunk? })`(SSE data 为 JSON 且含 content 字段时逐块回调)、`abort()`、`loading`、`error`。在飞行守卫:上一个 send 未结束时新 send 被忽略。
|
|
605
|
+
|
|
606
|
+
`XRequest(url, options)`:Promise 化,resolve 累计全文。
|
|
607
|
+
|
|
608
|
+
### useRecord()
|
|
609
|
+
|
|
610
|
+
语音录制 + 实时转写:`start()` / `stop()` / `isRecording` / `text` / `audioUrl` / `error`。SpeechRecognition 不可用时降级为仅录音。
|
|
611
|
+
|
|
612
|
+
## 自定义 ChatAdapter
|
|
613
|
+
|
|
614
|
+
实现 `ChatAdapter` 接口即可接入任意后端(自研网关、Ollama、非 OpenAI 协议):
|
|
615
|
+
|
|
616
|
+
```ts
|
|
617
|
+
import type { ChatAdapter } from '@fv-ui/ai-chat'
|
|
618
|
+
|
|
619
|
+
const myAdapter: ChatAdapter = {
|
|
620
|
+
async send(req, handlers) {
|
|
621
|
+
// req: { messages: [{ role, content }], model?, stream?, signal? }
|
|
622
|
+
try {
|
|
623
|
+
const res = await fetch('/api/chat', {
|
|
624
|
+
method: 'POST',
|
|
625
|
+
headers: { 'content-type': 'application/json' },
|
|
626
|
+
body: JSON.stringify(req),
|
|
627
|
+
signal: req.signal
|
|
628
|
+
})
|
|
629
|
+
if (!res.ok) { handlers.onError(new Error(`HTTP ${res.status}`)); return }
|
|
630
|
+
// 流式:自行逐块解析,回调增量
|
|
631
|
+
handlers.onDelta('一段正文')
|
|
632
|
+
handlers.onReasoning('一段思考链') // 可选
|
|
633
|
+
handlers.onUsage({ prompt_tokens: 10, completion_tokens: 20 }) // 可选
|
|
634
|
+
handlers.onDone() // 正常结束必须调用
|
|
635
|
+
} catch (e) {
|
|
636
|
+
if (req.signal?.aborted) return // 主动中止不算错误
|
|
637
|
+
handlers.onError(e instanceof Error ? e : new Error(String(e)))
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
abort() {
|
|
641
|
+
// 中止全部在途请求
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// 用法
|
|
646
|
+
<FvAiChat title="AI" :adapter="myAdapter" />
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
一次 `send` 对应一次完整生成:持续回调 `onDelta` 增量,最后必须 `onDone()` 或 `onError()`;`stop()` 时组件会调 `abort()`。
|
|
650
|
+
|
|
651
|
+
持久化可选 `StorageAdapter`(`get/set/remove`,async),内置 `createLocalStorageAdapter()`(localStorage 不可用时自动降级内存)与 `createMemoryAdapter()`;经 `useMessages().setStorage()` 绑定。
|
|
652
|
+
|
|
653
|
+
## 主题定制
|
|
654
|
+
|
|
655
|
+
组件样式基于 Element Plus CSS 变量,覆盖 EP 变量即完成主题定制:
|
|
656
|
+
|
|
657
|
+
```scss
|
|
658
|
+
// 全局或局部覆盖(需在引入 dist/style.css 之后)
|
|
659
|
+
:root {
|
|
660
|
+
--el-color-primary: #7c3aed;
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
高级定制可直接 `@use` 源 SCSS 入口:
|
|
665
|
+
|
|
666
|
+
```scss
|
|
667
|
+
@use '@fv-ui/ai-chat/src/styles/index.scss' with (
|
|
668
|
+
// scss 变量(若组件有定义)
|
|
669
|
+
);
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
> 源码 SCSS 入口仅供源码依赖场景;npm 消费方推荐使用构建产物 `dist/style.css` + CSS 变量覆盖。
|
|
673
|
+
|
|
674
|
+
## 暗色模式
|
|
675
|
+
|
|
676
|
+
`<FvAiChat theme="dark">` 或 `theme="auto"`(跟随 `prefers-color-scheme`):组件向 `<html>` 注入/移除 `dark` class,配合 element-plus 的暗色变量生效:
|
|
677
|
+
|
|
678
|
+
```ts
|
|
679
|
+
// main.ts 引入 EP 暗色变量
|
|
680
|
+
import 'element-plus/theme-chalk/dark/css-vars.css'
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
```vue
|
|
684
|
+
<FvAiChat theme="auto" ... />
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
注意:组件卸载时会移除 `html.dark`(单实例假设);多实例并用暗色需自行管理 class。
|
|
688
|
+
|
|
689
|
+
## Prism 主题引入
|
|
690
|
+
|
|
691
|
+
代码高亮默认配色由组件样式提供;要换成其他 Prism 主题,在 `dist/style.css` 之后引入覆盖:
|
|
692
|
+
|
|
693
|
+
```ts
|
|
694
|
+
import '@fv-ui/ai-chat/dist/style.css'
|
|
695
|
+
import '@fv-ui/ai-chat/styles/prism-tomorrow.min.css'
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
内置主题:`prism`(默认)、`prism-coy`、`prism-dark`、`prism-funky`、`prism-okaidia`、`prism-solarizedlight`、`prism-tomorrow`、`prism-twilight`。
|
|
699
|
+
|
|
700
|
+
## FAQ
|
|
701
|
+
|
|
702
|
+
**Q:页面是白屏/样式错乱?**
|
|
703
|
+
A:确认已引入 `element-plus/dist/index.css` 与 `@fv-ui/ai-chat/dist/style.css`,且 `app.use(ElementPlus)`。
|
|
704
|
+
|
|
705
|
+
**Q:`<FvAiChat>` 发送报"未配置 adapter 或 baseURL"?**
|
|
706
|
+
A:`adapter` 与 `baseURL` 至少传一个。
|
|
707
|
+
|
|
708
|
+
**Q:外层改了 `v-model:messages` 为什么界面没变?**
|
|
709
|
+
A:半受控语义:外部 messages 只在初始灌入一次,后续界面变化全量上抛 `update:messages`,请经 v-model 回写而不是直接改源数组。
|
|
710
|
+
|
|
711
|
+
**Q:多模型怎么用?**
|
|
712
|
+
A:`<FvAiChat model="a" :models="['a', 'b']">` 顶栏出现多选,选中的多个模型并行生成,回答互为分支可切换。
|
|
713
|
+
|
|
714
|
+
**Q:`features` 里的 tts/queue/continueGenerate/fullscreenEditor?**
|
|
715
|
+
A:接口已预留,当前版本仅 `artifacts` 生效,其余待后续版本接线。
|
|
716
|
+
|
|
717
|
+
**Q:KaTeX 公式没渲染?**
|
|
718
|
+
A:XMarkdown 内置 `@vscode/markdown-it-katex`,确认公式语法正确即可;无需额外引入。
|
|
719
|
+
|
|
720
|
+
**Q:Nuxt/SSR 环境首渲染报错 "DOMPurify 不可用:当前环境无 window"?**
|
|
721
|
+
A:XMarkdown 依赖浏览器 `window`(DOMPurify 消毒),不支持服务端首渲染。Nuxt 下请用 `<ClientOnly>` 包裹(或客户端挂载后再渲染):
|
|
722
|
+
|
|
723
|
+
```vue
|
|
724
|
+
<ClientOnly>
|
|
725
|
+
<FvAiChat ... />
|
|
726
|
+
</ClientOnly>
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
## 已知限制与 Roadmap
|
|
730
|
+
|
|
731
|
+
以下能力数据层已就绪,组合 UI 待后续版本接线(v0.2 计划):
|
|
732
|
+
|
|
733
|
+
- **消息列表懒加载**:`useMessages` 数据面支持分页注入,`AiMessages` 暂全量渲染;超长对话建议自行分页。
|
|
734
|
+
- **消息操作条**:当前内置复制/重新生成;编辑/删除/继续生成/朗读请经 `useChat`(`editMessage`/`removeMessage`/`continueGenerate`)或 `#message-actions` 插槽自建入口。
|
|
735
|
+
- **队列卡片**:`useChat.enqueue` 可用,`features.queue` 展示 UI 待接线。
|
|
736
|
+
- **语音输入**:`Sender` 的 `allowSpeech` 依赖浏览器 `SpeechRecognition`(Chrome/Edge 可用,Firefox 不支持转写,降级为仅录音、确认后无文本);录音/波形需要 HTTPS 或 localhost 环境。
|
|
737
|
+
- **多实例暗色互斥**:`theme` 采用单写者假设,多实例同屏请自行管理 `html.dark`。
|
|
738
|
+
|
|
739
|
+
## 类型与导出
|
|
740
|
+
|
|
741
|
+
包入口导出全部组件、`useChat`/`useMessages`/`useXStream`/`useSend`/`XRequest`/`useRecord`、`OpenAIAdapter`、`DetOmsChatAdapter`(+ `DetOmsError` 及配套 DTO 类型)、`createMessageTree`、`createLocalStorageAdapter`/`createMemoryAdapter`、`createSSEParser` 及全部公共类型(`MessageNode`、`ChatAdapter`、`ChatFile`、`MessageUsage`、`SSEEvent` 等),消费方:
|
|
742
|
+
|
|
743
|
+
```ts
|
|
744
|
+
import { FvAiChat, OpenAIAdapter, useChat } from '@fv-ui/ai-chat'
|
|
745
|
+
import type { MessageNode, ChatAdapter } from '@fv-ui/ai-chat'
|
|
746
|
+
```
|