@blueking/chat-x 0.0.51-beta.5 → 0.0.52-dev.1
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 +15 -8
- package/dist/ag-ui/types/contents.d.ts +2 -0
- package/dist/ag-ui/types/messages.d.ts +6 -1
- package/dist/common/constants.d.ts +2 -0
- package/dist/components/ai-buttons/add-menu-btn/add-menu-btn.vue.d.ts +24 -0
- package/dist/components/chat-content/collapsible-content/collapsible-content.vue.d.ts +28 -0
- package/dist/components/chat-content/file-content/upload-file-item.vue.d.ts +5 -2
- package/dist/components/chat-content/file-content/upload-image-item.vue.d.ts +1 -0
- package/dist/components/chat-content/flow-agent-content/use-flow-node-actions.d.ts +1 -1
- package/dist/components/chat-input/ai-slash-input/ai-slash-input.vue.d.ts +21 -10
- package/dist/components/chat-input/ai-slash-input/command.d.ts +6 -3
- package/dist/components/chat-input/ai-slash-input/constants.d.ts +2 -0
- package/dist/components/chat-input/ai-slash-input/use-menu-trigger.d.ts +17 -0
- package/dist/components/chat-input/build-default-placeholder.d.ts +9 -3
- package/dist/components/chat-input/chat-input.vue.d.ts +30 -18
- package/dist/components/chat-input/input-menu/constants.d.ts +60 -0
- package/dist/components/chat-input/input-menu/index.d.ts +4 -0
- package/dist/components/chat-input/input-menu/input-menu-option.vue.d.ts +14 -0
- package/dist/components/chat-input/input-menu/input-menu-panel.vue.d.ts +17 -0
- package/dist/components/chat-input/input-menu/use-input-menu.d.ts +22 -0
- package/dist/components/chat-input/model-selector/types.d.ts +3 -2
- package/dist/components/chat-message/message-container/message-container.vue.d.ts +1 -1
- package/dist/components/chat-message/user-message/user-message.vue.d.ts +1 -1
- package/dist/components/mention/create-mention-tippy.d.ts +27 -0
- package/dist/components/mention/index.d.ts +4 -0
- package/dist/components/{chat-input/ai-slash-input/ai-prompt-list/ai-prompt-list.vue.d.ts → mention/mention-popover.vue.d.ts} +3 -2
- package/dist/components/{chat-input/ai-slash-input/ai-skill-list/ai-skill-list.vue.d.ts → mention/mention-tag.vue.d.ts} +7 -3
- package/dist/components/{chat-input/ai-slash-input/ai-slash-menu/ai-slash-menu.vue.d.ts → mention/mention-text.vue.d.ts} +3 -3
- package/dist/components/resource-icon/index.d.ts +1 -0
- package/dist/components/resource-icon/resource-icon.vue.d.ts +11 -0
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/use-artifact-preview.d.ts +5 -2
- package/dist/composables/use-common.d.ts +0 -9
- package/dist/composables/use-global-config.d.ts +4 -0
- package/dist/composables/use-input-mention.d.ts +13 -0
- package/dist/composables/use-message-group.d.ts +1011 -72
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/input.d.ts +21 -0
- package/dist/icons/menu-item.d.ts +22 -0
- package/dist/index.css +1 -1
- package/dist/index.js +4953 -4429
- package/dist/index.js.map +1 -1
- package/dist/lang/lang.d.ts +13 -3
- package/dist/mcp/generated/docs/activity-message.md +3 -3
- package/dist/mcp/generated/docs/add-menu-btn.md +91 -0
- package/dist/mcp/generated/docs/ai-slash-input.md +72 -26
- package/dist/mcp/generated/docs/assistant-message.md +7 -1
- package/dist/mcp/generated/docs/chat-container.md +45 -6
- package/dist/mcp/generated/docs/chat-input.md +327 -483
- package/dist/mcp/generated/docs/collapsible-content.md +78 -0
- package/dist/mcp/generated/docs/constants.md +36 -3
- package/dist/mcp/generated/docs/execution-summary.md +1 -23
- package/dist/mcp/generated/docs/file-artifact-panel.md +24 -5
- package/dist/mcp/generated/docs/file-content.md +4 -0
- package/dist/mcp/generated/docs/file-upload-btn.md +15 -12
- package/dist/mcp/generated/docs/flow-agent-content.md +10 -12
- package/dist/mcp/generated/docs/input-menu-panel.md +166 -0
- package/dist/mcp/generated/docs/mention-tag.md +83 -0
- package/dist/mcp/generated/docs/mention-text.md +86 -0
- package/dist/mcp/generated/docs/message-container.md +1 -1
- package/dist/mcp/generated/docs/message-render.md +1 -1
- package/dist/mcp/generated/docs/message-tools.md +15 -4
- package/dist/mcp/generated/docs/messages.md +3 -0
- package/dist/mcp/generated/docs/model-selector.md +4 -2
- package/dist/mcp/generated/docs/overflow-tips.md +3 -3
- package/dist/mcp/generated/docs/resource-icon.md +102 -0
- package/dist/mcp/generated/docs/text-content.md +7 -3
- package/dist/mcp/generated/docs/theme.md +27 -35
- package/dist/mcp/generated/docs/use-artifact-preview.md +7 -3
- package/dist/mcp/generated/docs/use-command-selection.md +1 -1
- package/dist/mcp/generated/docs/use-container-scroll.md +2 -1
- package/dist/mcp/generated/docs/use-flow-node-actions.md +8 -15
- package/dist/mcp/generated/docs/use-global-config.md +11 -4
- package/dist/mcp/generated/docs/use-input-mention.md +119 -0
- package/dist/mcp/generated/docs/use-menu-keydown.md +15 -16
- package/dist/mcp/generated/docs/use-message-group.md +3 -6
- package/dist/mcp/generated/docs/user-message.md +39 -17
- package/dist/mcp/generated/index.json +233 -81
- package/dist/types/editor.d.ts +0 -29
- package/dist/types/index.d.ts +1 -0
- package/dist/types/input-menu.d.ts +46 -0
- package/dist/utils/artifact-tags.d.ts +36 -0
- package/dist/utils/collect-message-artifacts.d.ts +14 -0
- package/dist/utils/file.d.ts +3 -3
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/upload-file.d.ts +6 -1
- package/package.json +3 -2
- package/skills/blueking-chat-x/SKILL.md +143 -0
- package/skills/blueking-chat-x/references/_index.md +153 -0
- package/skills/blueking-chat-x/references/components/activity-layout.md +134 -0
- package/skills/blueking-chat-x/references/components/activity-message.md +486 -0
- package/skills/blueking-chat-x/references/components/add-menu-btn.md +88 -0
- package/skills/blueking-chat-x/references/components/ai-image.md +230 -0
- package/skills/blueking-chat-x/references/components/ai-loading.md +131 -0
- package/skills/blueking-chat-x/references/components/ai-selection.md +439 -0
- package/skills/blueking-chat-x/references/components/ai-slash-input.md +102 -0
- package/skills/blueking-chat-x/references/components/animation-text.md +202 -0
- package/skills/blueking-chat-x/references/components/assistant-message.md +504 -0
- package/skills/blueking-chat-x/references/components/chat-container.md +912 -0
- package/skills/blueking-chat-x/references/components/chat-input.md +614 -0
- package/skills/blueking-chat-x/references/components/cite-content.md +142 -0
- package/skills/blueking-chat-x/references/components/code-content.md +211 -0
- package/skills/blueking-chat-x/references/components/collapsible-content.md +76 -0
- package/skills/blueking-chat-x/references/components/common-error-content.md +73 -0
- package/skills/blueking-chat-x/references/components/content-render.md +233 -0
- package/skills/blueking-chat-x/references/components/delete-tool.md +191 -0
- package/skills/blueking-chat-x/references/components/desc-panel.md +162 -0
- package/skills/blueking-chat-x/references/components/detail-section.md +91 -0
- package/skills/blueking-chat-x/references/components/execution-summary.md +150 -0
- package/skills/blueking-chat-x/references/components/file-artifact-panel.md +307 -0
- package/skills/blueking-chat-x/references/components/file-content.md +340 -0
- package/skills/blueking-chat-x/references/components/file-icon.md +109 -0
- package/skills/blueking-chat-x/references/components/file-upload-btn.md +162 -0
- package/skills/blueking-chat-x/references/components/flow-agent-content.md +266 -0
- package/skills/blueking-chat-x/references/components/flow-agent-node-detail.md +236 -0
- package/skills/blueking-chat-x/references/components/highlight-keyword.md +146 -0
- package/skills/blueking-chat-x/references/components/image-content.md +182 -0
- package/skills/blueking-chat-x/references/components/image-preview-group.md +184 -0
- package/skills/blueking-chat-x/references/components/image-preview.md +226 -0
- package/skills/blueking-chat-x/references/components/info-message.md +144 -0
- package/skills/blueking-chat-x/references/components/input-attachment.md +49 -0
- package/skills/blueking-chat-x/references/components/input-info-alert.md +42 -0
- package/skills/blueking-chat-x/references/components/input-menu-panel.md +162 -0
- package/skills/blueking-chat-x/references/components/interrupt-message.md +212 -0
- package/skills/blueking-chat-x/references/components/key-value-content.md +128 -0
- package/skills/blueking-chat-x/references/components/knowledge-rag-content.md +122 -0
- package/skills/blueking-chat-x/references/components/latex-content.md +200 -0
- package/skills/blueking-chat-x/references/components/loading-message.md +192 -0
- package/skills/blueking-chat-x/references/components/markdown-content.md +232 -0
- package/skills/blueking-chat-x/references/components/mention-tag.md +79 -0
- package/skills/blueking-chat-x/references/components/mention-text.md +83 -0
- package/skills/blueking-chat-x/references/components/mermaid-content.md +189 -0
- package/skills/blueking-chat-x/references/components/message-container.md +645 -0
- package/skills/blueking-chat-x/references/components/message-loading.md +118 -0
- package/skills/blueking-chat-x/references/components/message-render.md +327 -0
- package/skills/blueking-chat-x/references/components/message-time.md +177 -0
- package/skills/blueking-chat-x/references/components/message-tools.md +427 -0
- package/skills/blueking-chat-x/references/components/model-selector.md +157 -0
- package/skills/blueking-chat-x/references/components/preview-toolbar.md +42 -0
- package/skills/blueking-chat-x/references/components/questions-container.md +85 -0
- package/skills/blueking-chat-x/references/components/reasoning-message.md +232 -0
- package/skills/blueking-chat-x/references/components/reference-content.md +135 -0
- package/skills/blueking-chat-x/references/components/reference-doc-content.md +109 -0
- package/skills/blueking-chat-x/references/components/resource-icon.md +98 -0
- package/skills/blueking-chat-x/references/components/scroll-btn.md +159 -0
- package/skills/blueking-chat-x/references/components/selection-footer.md +78 -0
- package/skills/blueking-chat-x/references/components/selection-question.md +88 -0
- package/skills/blueking-chat-x/references/components/shortcut-btn.md +204 -0
- package/skills/blueking-chat-x/references/components/shortcut-btns.md +266 -0
- package/skills/blueking-chat-x/references/components/shortcut-render.md +424 -0
- package/skills/blueking-chat-x/references/components/simple-table.md +101 -0
- package/skills/blueking-chat-x/references/components/text-content.md +80 -0
- package/skills/blueking-chat-x/references/components/tool-approval-card.md +183 -0
- package/skills/blueking-chat-x/references/components/tool-btn.md +317 -0
- package/skills/blueking-chat-x/references/components/tool-message.md +235 -0
- package/skills/blueking-chat-x/references/components/toolcall-render.md +348 -0
- package/skills/blueking-chat-x/references/components/user-feedback.md +233 -0
- package/skills/blueking-chat-x/references/components/user-message.md +444 -0
- package/skills/blueking-chat-x/references/components/user-question-answered-card.md +104 -0
- package/skills/blueking-chat-x/references/components/user-question-card.md +231 -0
- package/skills/blueking-chat-x/references/components/user-question-choice.md +105 -0
- package/skills/blueking-chat-x/references/components/user-question-option.md +42 -0
- package/skills/blueking-chat-x/references/components/vnode-renderer.md +122 -0
- package/skills/blueking-chat-x/references/composables/use-animation-text.md +196 -0
- package/skills/blueking-chat-x/references/composables/use-artifact-preview.md +235 -0
- package/skills/blueking-chat-x/references/composables/use-clipboard.md +203 -0
- package/skills/blueking-chat-x/references/composables/use-command-selection.md +150 -0
- package/skills/blueking-chat-x/references/composables/use-container-scroll.md +57 -0
- package/skills/blueking-chat-x/references/composables/use-custom-tab.md +158 -0
- package/skills/blueking-chat-x/references/composables/use-flow-node-actions.md +164 -0
- package/skills/blueking-chat-x/references/composables/use-full-screen.md +112 -0
- package/skills/blueking-chat-x/references/composables/use-global-config.md +155 -0
- package/skills/blueking-chat-x/references/composables/use-input-mention.md +115 -0
- package/skills/blueking-chat-x/references/composables/use-menu-keydown.md +161 -0
- package/skills/blueking-chat-x/references/composables/use-message-group.md +244 -0
- package/skills/blueking-chat-x/references/composables/use-observer-visible-list.md +188 -0
- package/skills/blueking-chat-x/references/composables/use-parent-scrolling.md +46 -0
- package/skills/blueking-chat-x/references/directives/index.md +61 -0
- package/skills/blueking-chat-x/references/directives/overflow-tips.md +205 -0
- package/skills/blueking-chat-x/references/icons/index.md +185 -0
- package/skills/blueking-chat-x/references/plugins/index.md +56 -0
- package/skills/blueking-chat-x/references/plugins/markdown-container.md +55 -0
- package/skills/blueking-chat-x/references/plugins/markdown-latex.md +205 -0
- package/skills/blueking-chat-x/references/plugins/markdown-mermaid.md +247 -0
- package/skills/blueking-chat-x/references/theme/theme.md +423 -0
- package/skills/blueking-chat-x/references/types/constants.md +340 -0
- package/skills/blueking-chat-x/references/types/index.md +120 -0
- package/skills/blueking-chat-x/references/types/interrupt.md +379 -0
- package/skills/blueking-chat-x/references/types/messages.md +556 -0
- package/skills/blueking-chat-x/references/types/schema.md +91 -0
- package/skills/blueking-chat-x/references/utils/index.md +196 -0
- package/skills/blueking-chat-x/scripts/generate-references.mjs +536 -0
- package/dist/components/chat-input/ai-slash-editor/ai-slash-editor.vue.d.ts +0 -28
- package/dist/components/chat-input/ai-slash-editor/theme.d.ts +0 -24
- package/dist/mcp/generated/docs/ai-prompt-list.md +0 -45
- package/dist/mcp/generated/docs/ai-skill-list.md +0 -75
- package/dist/mcp/generated/docs/ai-slash-editor.md +0 -43
- package/dist/mcp/generated/docs/ai-slash-menu.md +0 -42
|
@@ -1,53 +1,50 @@
|
|
|
1
1
|
<!-- AI SUMMARY -->
|
|
2
2
|
## 快速了解
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
聊天输入区,组合富文本输入、统一菜单、快捷指令、附件、引用、发送/停止等交互。 菜单数据由单一 menuSources 提供,按 type 分发到 `/` `@` `\` 与左下角 + 号四种触发方式。 源码位置:src/components/chat-input/chat-input.vue。
|
|
5
5
|
|
|
6
6
|
### 关联组件
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
7
|
+
- **ai-slash-input** — 内部富文本编辑区,负责触发符识别与标签插入
|
|
8
|
+
- **input-menu-panel** — 输入框正上方的统一菜单面板
|
|
9
|
+
- **add-menu-btn** — 左下角 + 号,唤起聚合菜单
|
|
10
|
+
- **mention-tag** — 菜单选中的资源以标签形式嵌入编辑器
|
|
11
11
|
- **model-selector** — 传入 models 后在发送按钮左侧默认展示模型选择器
|
|
12
|
+
- **shortcut-btns** — 底部附件区默认展示的快捷指令列表
|
|
12
13
|
- **cite-content** — 消息引用区展示选中的上下文片段
|
|
14
|
+
- **chat-container** — 顶层聊天布局中作为输入区子组件
|
|
13
15
|
|
|
14
16
|
---
|
|
15
17
|
<!-- FULL DOC -->
|
|
16
18
|
|
|
17
19
|
# ChatInput 聊天输入框
|
|
18
|
-
## 源码事实
|
|
19
|
-
|
|
20
|
-
- **源码位置**:`src/components/chat-input/chat-input.vue`
|
|
21
|
-
- **能力域**:输入交互
|
|
22
|
-
- **能力说明**:聊天输入区,组合富文本输入、快捷指令、附件、引用、发送/停止等交互。
|
|
23
20
|
|
|
24
21
|
> **能力域**:输入交互
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
## 源码事实
|
|
24
|
+
|
|
25
|
+
- **源码位置**:`src/components/chat-input/chat-input.vue`
|
|
26
|
+
- **能力说明**:聊天输入区,组合富文本输入、统一菜单、快捷指令、附件、引用、发送/停止等交互。
|
|
27
|
+
- **菜单数据源唯一**:`menuSources` 一份数组按 `type` 分发到 `/`、`@`、`\` 三个触发符与左下角 + 号,组件内部不再区分 `skills` / `prompts` / `resources`。
|
|
27
28
|
|
|
28
29
|
## 组件结构
|
|
29
30
|
|
|
30
31
|
```
|
|
31
|
-
ai-chat-input-container(padding: 0 16px 16px
|
|
32
|
-
├── slot#top
|
|
33
|
-
├── slot#interrupt
|
|
34
|
-
└── chat-input
|
|
35
|
-
├──
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
├──
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
ai-chat-input-container(padding: 0 16px 16px)
|
|
33
|
+
├── slot#top(框体外顶部)
|
|
34
|
+
├── slot#interrupt(框体外顶部,位于 top 之后)
|
|
35
|
+
└── chat-input-wrapper(相对定位;宽度 168px ~ 1000px)
|
|
36
|
+
├── InputMenuPanel(绝对定位于框体上方 8px、与框体等宽,菜单激活且有条目时渲染)
|
|
37
|
+
└── chat-input(框体,min-height 110px,max-height 由 inputMaxHeight 控制)
|
|
38
|
+
├── slot#input-header(默认:cite 非空时渲染 CiteContent)
|
|
39
|
+
├── slot#files(默认:有上传文件时渲染 FileContent)
|
|
40
|
+
├── AiSlashInput(富文本编辑区,默认保持 4 行高度)
|
|
41
|
+
└── InputAttachment(底部工具栏,固定 32px 高)
|
|
42
|
+
├── slot#default → 隐藏 file input + AddMenuBtn(+ 号)+ 分隔线 + slot#attachment
|
|
43
|
+
├── slot#before-send → slot#model-selector(默认 ModelSelector)
|
|
44
|
+
└── slot#send-icon(默认:发送 / 停止图标)
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
>
|
|
47
|
-
|
|
48
|
-
### AiSlashInput 与 modelValue 同步
|
|
49
|
-
|
|
50
|
-
内部编辑器 `AiSlashInput` 在 **`modelValue` 由外部异步更新**(例如从历史会话回填、父组件重置)且与当前文档不一致时,会通过 `useCommandSelection` 提供的 `GetDocSnapshot` 读取编辑器快照,与 `docToString(modelValue)` 比对后,必要时执行 `ReplaceAll` 将编辑器内容同步为新的 `modelValue`,避免内外状态脱节。
|
|
47
|
+
> `slot#attachment` 只替换快捷指令区,`AddMenuBtn` 在其外部,使用该插槽不会移除 + 号。`slot#send-icon` 只替换图标,点击逻辑与按钮样式仍由组件控制。
|
|
51
48
|
|
|
52
49
|
## 基础用法
|
|
53
50
|
|
|
@@ -55,6 +52,7 @@ ai-chat-input-container(padding: 0 16px 16px,底部间距 16px)
|
|
|
55
52
|
<template>
|
|
56
53
|
<ChatInput
|
|
57
54
|
v-model="inputValue"
|
|
55
|
+
:menu-sources="menuSources"
|
|
58
56
|
:message-status="messageStatus"
|
|
59
57
|
:on-send-message="handleSendMessage"
|
|
60
58
|
:on-stop-sending="handleStopSending"
|
|
@@ -63,13 +61,22 @@ ai-chat-input-container(padding: 0 16px 16px,底部间距 16px)
|
|
|
63
61
|
|
|
64
62
|
<script setup lang="ts">
|
|
65
63
|
import { ref } from 'vue';
|
|
66
|
-
import { ChatInput, MessageStatus, type TagSchema } from '@blueking/chat-x';
|
|
64
|
+
import { ChatInput, MessageStatus, type IInputMenuItem, type TagSchema } from '@blueking/chat-x';
|
|
67
65
|
|
|
68
66
|
const inputValue = ref('');
|
|
69
67
|
const messageStatus = ref(MessageStatus.Complete);
|
|
70
68
|
|
|
69
|
+
const menuSources: IInputMenuItem[] = [
|
|
70
|
+
{ id: 'translate', type: 'skill', name: '翻译', description: '把选中的文本翻译成目标语言' },
|
|
71
|
+
{ id: 'database-server', type: 'mcp', name: 'database-server' },
|
|
72
|
+
{ id: 'weather', type: 'tool', name: '天气查询' },
|
|
73
|
+
{ id: 'kb-api', type: 'knowledgebase', name: 'API 接口文档' },
|
|
74
|
+
{ id: 'prompt-article', type: 'prompt', name: '写文章', content: '帮我写一篇关于 {topic} 的文章' },
|
|
75
|
+
];
|
|
76
|
+
|
|
71
77
|
const handleSendMessage = async (content: string, docSchema: TagSchema) => {
|
|
72
78
|
// content:纯文本字符串(无文件时)或 InputContent[] 数组(有文件时)
|
|
79
|
+
// docSchema:当前编辑器文档,含标签结构,可持久化用于回显
|
|
73
80
|
messageStatus.value = MessageStatus.Streaming;
|
|
74
81
|
// ... 发送 AI 请求
|
|
75
82
|
messageStatus.value = MessageStatus.Complete;
|
|
@@ -81,270 +88,234 @@ ai-chat-input-container(padding: 0 16px 16px,底部间距 16px)
|
|
|
81
88
|
</script>
|
|
82
89
|
```
|
|
83
90
|
|
|
84
|
-
**渲染效果**(输入 `/`
|
|
91
|
+
**渲染效果**(输入 `/` `@` `\` 或点击左下角 + 号唤出菜单)
|
|
85
92
|
|
|
86
|
-
##
|
|
93
|
+
## 统一菜单(menuSources)
|
|
87
94
|
|
|
88
|
-
|
|
95
|
+
### 触发方式与分组
|
|
89
96
|
|
|
90
|
-
|
|
91
|
-
| ----------------------------- | ------------------------------------------------------ | ------------------------ |
|
|
92
|
-
| `complete` / `stop` / `error` | 蓝色发送按钮,点击触发 `onSendMessage` | 灰色禁用 |
|
|
93
|
-
| `streaming` / `pending` / `fetching` | 蓝色停止按钮(Loading 图标),点击触发 `onStopSending` | 蓝色停止按钮(仍可点击) |
|
|
94
|
-
| `disabled` | 灰色禁用,点击无效 | 灰色禁用 |
|
|
95
|
-
|
|
96
|
-
> **实现细节**:组件内部用 `messageState` 计算属性决定实际按钮状态:当 `messageStatus` 为 `pending`、`streaming` 或 `fetching` 时直接使用该状态(确保停止按钮始终可用);否则**已有上传附件时视为可发送**(纯附件消息无需输入文字);再否则当输入为空或仅含空白字符时强制为 `disabled`,其余情况使用 `messageStatus` 的值。`fetching` 时按 Enter **不会**触发发送(避免请求中与 Loading 占位阶段重复提交)。
|
|
97
|
+
菜单面板固定展示在输入框**正上方**并与输入框等宽(不跟随光标),最大高度 400px,超出滚动。四种触发方式共用同一份 `menuSources`,各自展示的分组不同:
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
| 触发方式 | 分组顺序 | 说明 |
|
|
100
|
+
| ---------------- | -------------------------------------------------------------- | ------------------------------------------ |
|
|
101
|
+
| `/` | Skill、MCP、工具 | 智能体能力 |
|
|
102
|
+
| `@` | 知识库、会话产物 | 可引用的上下文资源 |
|
|
103
|
+
| `\` | Prompt | 提示词模板 |
|
|
104
|
+
| + 号(`plus`) | 添加、Skill、MCP、工具、知识库、会话产物、Prompt | 聚合全部分组,「添加」组下方有分隔线 |
|
|
99
105
|
|
|
100
|
-
|
|
106
|
+
分组与 `type` 的对应关系:
|
|
101
107
|
|
|
102
|
-
|
|
108
|
+
| 分组标题 | 覆盖的 `type` | 备注 |
|
|
109
|
+
| ---------- | -------------------------- | ------------------------------------------------ |
|
|
110
|
+
| 添加 | `file` | 组件内置项,只出现在 + 号菜单 |
|
|
111
|
+
| Skill | `skill` | 插入后序列化为 `/<id>` |
|
|
112
|
+
| MCP | `mcp` | |
|
|
113
|
+
| 工具 | `tool` | |
|
|
114
|
+
| 知识库 | `knowledgebase`、`doc` | 后端两种历史命名合并为一个分组 |
|
|
115
|
+
| 会话产物 | `artifact` | 无数据时不展示该分组 |
|
|
116
|
+
| Prompt | `prompt` | 选中后整体替换输入框内容 |
|
|
103
117
|
|
|
104
|
-
|
|
105
|
-
| ----------- | ------------------ | -------------------------------------------------------------------- |
|
|
106
|
-
| `interrupt` | `Interrupt` | 当前激活的 `UserQuestionInterrupt` |
|
|
107
|
-
| `payload` | `InterruptResume` | skip resume(`status: 'cancelled'`,`payload.answers` 为空数组) |
|
|
108
|
-
|
|
109
|
-
此场景下容器**不会**自动清空 `modelValue`,业务侧需在 `onSendMessage` 内自行处理消息发送与 `resumeAgent` 的先后顺序。结构化作答仍通过 `UserQuestionCard` → `onInterruptResume` 完成。
|
|
110
|
-
|
|
111
|
-
```typescript
|
|
112
|
-
const handleSendMessage = async (
|
|
113
|
-
content: UserMessage['content'],
|
|
114
|
-
docSchema: TagSchema,
|
|
115
|
-
options?: { interrupt?: Interrupt; payload?: InterruptResume },
|
|
116
|
-
) => {
|
|
117
|
-
if (options?.interrupt && options?.payload) {
|
|
118
|
-
await resumeAgent({ interruptId: options.interrupt.id, resume: options.payload });
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
await sendMessage(content, docSchema);
|
|
122
|
-
};
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### sendDisabledTip(业务阻塞发送)
|
|
118
|
+
### 关键行为
|
|
126
119
|
|
|
127
|
-
|
|
120
|
+
- **过滤**:触发符之后输入的文本作为关键字,按 `name` 不区分大小写包含匹配。+ 号菜单的关键字取「唤起时光标位置 → 当前光标」之间的文本。
|
|
121
|
+
- **折叠**:每个分组默认展示 `menuGroupItemLimit`(默认 4)条,超出折叠为「更多 +N」,点击展开;关键字或触发方式变化后折叠状态重置。
|
|
122
|
+
- **去重**:已插入编辑器的标签按 `type:id` 从候选中剔除,不会重复出现。
|
|
123
|
+
- **空面板不弹出**:初始化或搜索后没有任何匹配条目时,面板不会展示。空分组也不会被算进结果(见 [InputMenuPanel](/components/input/input-menu-panel))。
|
|
124
|
+
- **内置「文件」项**:`supportUpload` 为 `true` 时注入,**不要**写进 `menuSources`。选中后唤起隐藏文件选择器并走 `onUpload`;`accept` 与入队校验见 [文件上传](#file-upload)。
|
|
125
|
+
- **+ 号显隐**:`supportUpload` 为 `false` 且 `menuSources` 为空时不渲染 + 号。
|
|
126
|
+
- **选中动作**:`prompt` 整体替换输入框内容(取 `content`,缺省取 `name`);`file` 唤起文件选择器;其余类型插入资源标签并补一个空格。
|
|
128
127
|
|
|
129
128
|
```vue
|
|
130
129
|
<template>
|
|
131
130
|
<ChatInput
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
:
|
|
131
|
+
:model-value="inputValue"
|
|
132
|
+
:menu-sources="menuSources"
|
|
133
|
+
:menu-group-item-limit="6"
|
|
134
|
+
:message-status="messageStatus"
|
|
135
135
|
:on-send-message="handleSendMessage"
|
|
136
|
-
:
|
|
137
|
-
|
|
138
|
-
<template #interrupt>
|
|
139
|
-
<div class="input-alert">{{ interruptTip }}</div>
|
|
140
|
-
</template>
|
|
141
|
-
</ChatInput>
|
|
136
|
+
@update:model-value="handleModelValueUpdate"
|
|
137
|
+
/>
|
|
142
138
|
</template>
|
|
139
|
+
|
|
140
|
+
<script setup lang="ts">
|
|
141
|
+
import { ref } from 'vue';
|
|
142
|
+
import { ChatInput, MessageStatus, type IInputMenuItem, type TagSchema } from '@blueking/chat-x';
|
|
143
|
+
|
|
144
|
+
const inputValue = ref<string | TagSchema>('');
|
|
145
|
+
|
|
146
|
+
const handleModelValueUpdate = (value: string | TagSchema, selectedResourceList: IInputMenuItem[]) => {
|
|
147
|
+
inputValue.value = value;
|
|
148
|
+
// selectedResourceList 为当前编辑器内已插入的菜单条目(能在 menuSources 中反查到的部分)
|
|
149
|
+
console.log('已选资源:', selectedResourceList);
|
|
150
|
+
};
|
|
151
|
+
</script>
|
|
143
152
|
```
|
|
144
153
|
|
|
145
|
-
|
|
154
|
+
> `v-model` 仍可用(Vue 自动绑定第一个参数);需要 `selectedResourceList` 时改用 `@update:model-value` 显式监听。
|
|
146
155
|
|
|
147
|
-
###
|
|
156
|
+
### 图标
|
|
148
157
|
|
|
149
|
-
|
|
158
|
+
条目的 `icon` 支持图片 URL 字符串或 Vue 组件,缺省时由 [ResourceIcon](/components/helper/resource-icon) 按 `type` 兜底(`artifact` 按文件名后缀推导)。
|
|
150
159
|
|
|
151
|
-
|
|
160
|
+
::: warning 组件形式的图标不会随标签保留
|
|
161
|
+
插入编辑器后,标签会把图标序列化到 DOM 属性上(这样文档可以脱离 `menuSources` 独立还原),因此只有字符串 URL 能被保留;传 Vue 组件时标签内会回退为类型默认图标。
|
|
162
|
+
:::
|
|
152
163
|
|
|
153
|
-
|
|
164
|
+
### 发送后的文本形态
|
|
154
165
|
|
|
155
|
-
|
|
166
|
+
标签在发送文本中按类型序列化:`skill` 输出 `/<id>`,其余类型输出 `@<name>`。因此 `skill` 的 `id` 需要是后端可识别的编码。
|
|
156
167
|
|
|
157
|
-
|
|
158
|
-
<template>
|
|
159
|
-
<ChatInput
|
|
160
|
-
v-model="inputValue"
|
|
161
|
-
v-model:cite="citeContent"
|
|
162
|
-
:message-status="messageStatus"
|
|
163
|
-
:on-send-message="handleSendMessage"
|
|
164
|
-
:on-stop-sending="handleStopSending"
|
|
165
|
-
/>
|
|
166
|
-
</template>
|
|
168
|
+
## 从 skills / prompts / resources 迁移
|
|
167
169
|
|
|
168
|
-
|
|
169
|
-
import { ref } from 'vue';
|
|
170
|
-
import { ChatInput, MessageStatus, type TagSchema } from '@blueking/chat-x';
|
|
170
|
+
旧版三个数据源与 `AiSkillList` / `AiPromptList` / `AiSlashMenu` 三个菜单组件已移除,统一为 `menuSources`:
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
| 旧 API | 新写法 |
|
|
173
|
+
| ---------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
174
|
+
| `skills: ISkillListItem[]` | `menuSources` 中 `type: 'skill'`;`skill_code` → `id`,`skill_name` → `name` |
|
|
175
|
+
| `prompts: string[]` | `type: 'prompt'`;`name` 为菜单展示名,`content` 为插入正文 |
|
|
176
|
+
| `resources: IAiSlashMenuItem[]` | `type` 取 `tool` / `mcp` / `knowledgebase` / `doc` / `artifact` |
|
|
177
|
+
| `resources` 中的 `type: 'shortcut'` | 不再进入菜单,快捷指令走 `shortcuts` + `shortcutId` |
|
|
178
|
+
| `ISkillListItem` / `IAiSlashMenuItem` | 统一为 `IInputMenuItem`(`resourceTypeMap`、`ResourceType` 一并移除) |
|
|
179
|
+
| `\` 唤出 Prompt、`/` 唤出 Skill | 触发符不变,`@` 新增「会话产物」分组,并新增 + 号聚合菜单 |
|
|
175
180
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
```typescript
|
|
182
|
+
// 旧
|
|
183
|
+
const skills = [{ skill_code: 'translate', skill_name: '翻译', description: '翻译文本', icon: '' }];
|
|
184
|
+
const prompts = ['帮我写一篇关于 {topic} 的文章'];
|
|
185
|
+
const resources = [{ id: 'tool1', name: '天气查询', type: 'tool', icon: '' }];
|
|
186
|
+
|
|
187
|
+
// 新
|
|
188
|
+
const menuSources: IInputMenuItem[] = [
|
|
189
|
+
{ id: 'translate', type: 'skill', name: '翻译', description: '翻译文本' },
|
|
190
|
+
{ id: 'prompt-article', type: 'prompt', name: '写文章', content: '帮我写一篇关于 {topic} 的文章' },
|
|
191
|
+
{ id: 'tool1', type: 'tool', name: '天气查询' },
|
|
192
|
+
];
|
|
193
|
+
```
|
|
181
194
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
195
|
+
## 占位符
|
|
196
|
+
|
|
197
|
+
未传 `placeholder` 时,按 `menuSources` 中**实际存在的类型**动态拼接提示行(没有对应资源就不显示该行),最后一行始终保留:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
输入 "/" 唤出 Skill,工具,MCP // menuSources 含 skill / tool / mcp 任一
|
|
201
|
+
输入 "@" 唤出会话产物,知识库 // 含 knowledgebase / doc / artifact 任一
|
|
202
|
+
输入 "\" 唤出 Prompt // 含 prompt
|
|
203
|
+
通过 Shift + Enter 进行换行输入 // 始终显示
|
|
186
204
|
```
|
|
187
205
|
|
|
188
|
-
|
|
206
|
+
显式传入 `placeholder`(含空字符串)时完全覆盖上述文案,支持用 `\n` 换行。
|
|
189
207
|
|
|
190
|
-
##
|
|
208
|
+
## 发送状态(messageStatus)
|
|
191
209
|
|
|
192
|
-
|
|
210
|
+
`messageStatus` 控制底部按钮渲染,但**输入框为空且没有附件时始终置灰禁用**,无论传入什么值。
|
|
193
211
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
212
|
+
| `messageStatus` | 有内容或已有附件 | 空且无附件 |
|
|
213
|
+
| ------------------------------------ | ------------------------------------------------------ | ------------------------ |
|
|
214
|
+
| `complete` / `stop` / `error` | 蓝色发送按钮,点击触发 `onSendMessage` | 灰色禁用 |
|
|
215
|
+
| `streaming` / `pending` / `fetching` | 蓝色停止按钮(Loading 图标),点击触发 `onStopSending` | 蓝色停止按钮(仍可点击) |
|
|
216
|
+
| `disabled` | 灰色禁用,点击无效 | 灰色禁用 |
|
|
197
217
|
|
|
198
|
-
|
|
199
|
-
{ skill_code: 'translate', skill_name: '翻译', description: '翻译文本', icon: '' },
|
|
200
|
-
{ skill_code: 'summarize', skill_name: '总结', description: '总结内容', icon: 'https://example.com/icon.png' },
|
|
201
|
-
];
|
|
202
|
-
</script>
|
|
203
|
-
```
|
|
218
|
+
内部由 `messageState` 计算:`pending` / `streaming` / `fetching` 直接沿用传入状态(保证停止按钮始终可用);否则**已有上传附件即视为可发送**(纯附件消息无需文字);再否则输入为空或仅空白字符时强制为 `disabled`。`fetching` 时按 Enter **不会**触发发送,避免请求中重复提交。
|
|
204
219
|
|
|
205
|
-
|
|
220
|
+
### onSendMessage 第三参数 options(UserQuestion 上下文)
|
|
206
221
|
|
|
207
|
-
|
|
222
|
+
`ChatInput` 自身调用 `onSendMessage` 时只传前两个参数。当组件被 [ChatContainer](/components/setup/chat-container) 包裹且存在待回答 `UserQuestion` 中断时,容器会在用户点击发送时注入第三个参数:
|
|
208
223
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
224
|
+
| 字段 | 类型 | 说明 |
|
|
225
|
+
| ----------- | ----------------- | ---------------------------------------------------------------- |
|
|
226
|
+
| `interrupt` | `Interrupt` | 当前激活的 `UserQuestionInterrupt` |
|
|
227
|
+
| `payload` | `InterruptResume` | skip resume(`status: 'cancelled'`,`payload.answers` 为空数组) |
|
|
228
|
+
|
|
229
|
+
此场景下容器**不会**自动清空 `modelValue`,业务侧需在 `onSendMessage` 内自行处理消息发送与 `resumeAgent` 的先后顺序。结构化作答仍通过 `UserQuestionCard` → `onInterruptResume` 完成。
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
const handleSendMessage = async (
|
|
233
|
+
content: UserMessage['content'],
|
|
234
|
+
docSchema: TagSchema,
|
|
235
|
+
options?: { interrupt?: Interrupt; payload?: InterruptResume },
|
|
236
|
+
) => {
|
|
237
|
+
if (options?.interrupt && options?.payload) {
|
|
238
|
+
await resumeAgent({ interruptId: options.interrupt.id, resume: options.payload });
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
await sendMessage(content, docSchema);
|
|
242
|
+
};
|
|
219
243
|
```
|
|
220
244
|
|
|
221
|
-
|
|
245
|
+
### sendDisabledTip(业务阻塞发送)
|
|
246
|
+
|
|
247
|
+
需要临时阻止发送但仍允许输入时传入 `sendDisabledTip`:置灰发送按钮、按钮 tooltip 展示该文案,并拦截点击发送、Enter 发送与 `triggerSendMessage()`。
|
|
222
248
|
|
|
223
|
-
|
|
249
|
+
## 引用消息(v-model:cite)
|
|
224
250
|
|
|
225
|
-
|
|
251
|
+
通过 `v-model:cite` 绑定引用内容,引用区显示在编辑器上方,用户可点击关闭取消引用。发送时引用内容需自行读取 `cite` 变量,`onSendMessage` 的 `content` 不包含它。
|
|
226
252
|
|
|
227
253
|
```vue
|
|
228
254
|
<template>
|
|
229
255
|
<ChatInput
|
|
230
|
-
|
|
256
|
+
v-model="inputValue"
|
|
257
|
+
v-model:cite="citeContent"
|
|
231
258
|
:message-status="messageStatus"
|
|
232
|
-
:prompts="prompts"
|
|
233
|
-
:resources="resources"
|
|
234
259
|
:on-send-message="handleSendMessage"
|
|
235
|
-
:on-stop-sending="handleStopSending"
|
|
236
|
-
@update:model-value="handleModelValueUpdate"
|
|
237
260
|
/>
|
|
238
261
|
</template>
|
|
239
262
|
|
|
240
263
|
<script setup lang="ts">
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const inputValue = ref('');
|
|
245
|
-
const messageStatus = ref(MessageStatus.Complete);
|
|
246
|
-
|
|
247
|
-
const resources: IAiSlashMenuItem[] = [
|
|
248
|
-
{ id: 'tool1', name: '天气查询', type: 'tool', icon: 'icon-tool' },
|
|
249
|
-
{ id: 'tool2', name: '代码执行', type: 'tool', icon: 'icon-tool' },
|
|
250
|
-
{ id: 'mcp1', name: 'db-server', type: 'mcp', icon: 'icon-mcp' },
|
|
251
|
-
{ id: 'doc1', name: 'API 文档', type: 'doc', icon: 'icon-doc' },
|
|
252
|
-
{ id: 'sc1', name: '翻译助手', type: 'shortcut', icon: 'icon-shortcut' },
|
|
253
|
-
];
|
|
254
|
-
|
|
255
|
-
const handleModelValueUpdate = (value: string | TagSchema, selectedResourceList: IAiSlashMenuItem[]) => {
|
|
256
|
-
inputValue.value = value;
|
|
257
|
-
// selectedResourceList 为当前编辑器中已选中的 @ 资源列表
|
|
258
|
-
console.log('已选资源:', selectedResourceList);
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
const handleSendMessage = async (content: string, docSchema: TagSchema) => {
|
|
262
|
-
console.log('发送内容(含 @ 引用):', docSchema);
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const handleStopSending = async () => {
|
|
266
|
-
messageStatus.value = MessageStatus.Stop;
|
|
264
|
+
const handleSendMessage = async (content: string) => {
|
|
265
|
+
console.log('引用内容:', citeContent.value);
|
|
266
|
+
citeContent.value = ''; // 发送后自行清空
|
|
267
267
|
};
|
|
268
268
|
</script>
|
|
269
269
|
```
|
|
270
270
|
|
|
271
|
-
> 基础用法 demo 已集成 Prompt 和资源功能,可在上方输入框中体验 `/` 和 `@` 操作。
|
|
272
|
-
>
|
|
273
|
-
> **注意**:`v-model` 仍可使用(Vue 自动取第一个参数绑定),但如需获取 `selectedResourceList`,应使用 `@update:model-value` 显式监听。
|
|
274
|
-
|
|
275
271
|
## 快捷指令
|
|
276
272
|
|
|
277
|
-
|
|
273
|
+
`shortcuts` 传入列表,底部工具栏展示快捷指令按钮;`shortcutId` 控制选中态:
|
|
278
274
|
|
|
279
|
-
- `shortcutId` 为空 →
|
|
280
|
-
- `shortcutId`
|
|
275
|
+
- `shortcutId` 为空 → 显示全部快捷指令按钮
|
|
276
|
+
- `shortcutId` 命中某个 `shortcut.id` → 收起列表,显示已选指令 + 关闭图标
|
|
281
277
|
|
|
282
278
|
```vue
|
|
283
279
|
<template>
|
|
284
280
|
<ChatInput
|
|
285
281
|
v-model="inputValue"
|
|
286
|
-
:message-status="messageStatus"
|
|
287
282
|
:shortcuts="shortcuts"
|
|
288
283
|
:shortcut-id="selectedShortcutId"
|
|
289
284
|
:on-send-message="handleSendMessage"
|
|
290
|
-
:on-stop-sending="handleStopSending"
|
|
291
285
|
@select-shortcut="selectedShortcutId = $event.id"
|
|
292
286
|
@delete-shortcut="selectedShortcutId = ''"
|
|
293
287
|
/>
|
|
294
288
|
</template>
|
|
295
|
-
|
|
296
|
-
<script setup lang="ts">
|
|
297
|
-
import { ref } from 'vue';
|
|
298
|
-
import { ChatInput, MessageStatus, type TagSchema, type Shortcut } from '@blueking/chat-x';
|
|
299
|
-
|
|
300
|
-
const inputValue = ref('');
|
|
301
|
-
const messageStatus = ref(MessageStatus.Complete);
|
|
302
|
-
const selectedShortcutId = ref('');
|
|
303
|
-
|
|
304
|
-
const shortcuts: Shortcut[] = [
|
|
305
|
-
{ id: 'translate', name: '翻译' },
|
|
306
|
-
{ id: 'explain', name: '解释代码' },
|
|
307
|
-
{ id: 'summarize', name: '总结' },
|
|
308
|
-
];
|
|
309
|
-
|
|
310
|
-
const handleSendMessage = async (content: string, docSchema: TagSchema) => {
|
|
311
|
-
console.log('发送内容:', content, '当前快捷指令:', selectedShortcutId.value);
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const handleStopSending = async () => {
|
|
315
|
-
messageStatus.value = MessageStatus.Stop;
|
|
316
|
-
};
|
|
317
|
-
</script>
|
|
318
289
|
```
|
|
319
290
|
|
|
320
|
-
**渲染效果**(底部显示快捷指令按钮,点击选中后按钮变为已选状态)
|
|
321
|
-
|
|
322
291
|
## 文件上传 {#file-upload}
|
|
323
292
|
|
|
324
|
-
`supportUpload`
|
|
293
|
+
`supportUpload` 默认 `true`。附件入口有三条路径:**+ 号菜单的「文件」项**、**拖拽到输入框**、**粘贴(Ctrl+V)**。`type: 'file'` 是组件内置动作项,不要写进 `menuSources`。
|
|
294
|
+
|
|
295
|
+
选中「文件」后唤起隐藏 `input[type=file]`,与拖拽 / 粘贴共用同一套 `handleUpload` / `onUpload`。系统选择器与入队校验都走 `accept` prop(默认 `DEFAULT_UPLOAD_ACCEPT`,含图片 / 文档 / 文本 / 代码扩展名)。
|
|
325
296
|
|
|
326
|
-
-
|
|
327
|
-
- 支持**点击选择**、**拖拽上传**、**粘贴上传**(Ctrl+V)
|
|
328
|
-
- `onUpload` 一次选择传入**全部** `File[]`,返回同序的结果数组(也可对单文件返回单个对象);元素为 `{ download_url?: string; id?: string; status?: 'failed' | 'success' }`
|
|
297
|
+
- `onUpload` 一次选择传入**全部** `File[]`,返回同序的结果数组(也可对单文件返回单个对象);元素为 `{ download_url?: string; id?: string; path?: string; status?: 'failed' | 'success' }`
|
|
329
298
|
- 文件自动去重(基于 `name + size + lastModified` 复合键),不会重复上传
|
|
299
|
+
- 上传成功后将响应 `path` 保存为附件的 `outputId`(`id` 缺省时也以 `path` 回填)。**展示名始终取本地 `File.name`(编辑态回填时取 `filename`),选中即可见、不等上传返回,上传完成后也不会跳变**;`artifact` 标签的 `label` 与 `@` 菜单条目名同源于此。具有 `outputId` 的文件立即进入 `@` / `+` 的「会话产物」菜单和容器预览侧栏;发送时保留 `outputId`,已发送附件与助手产物共用引用、预览和下载能力。仅有 `id`、URL 或文件名的旧附件不作为会话产物收集。
|
|
300
|
+
- 取消附件时立即从 UI 移除,并触发 `deleteFile` 事件(模板使用 `@delete-file`),参数为 `Partial<UploadFile>`。业务方可根据 `file.id` 调用删除接口;组件不等待接口结果,成功或失败均不恢复附件。上传中 / 上传失败的附件也会触发事件,此时 `id` 可能为空,由业务方决定是否调用接口。发送后清空列表不会触发此事件。
|
|
330
301
|
- **上传中或存在失败附件时禁止发送**(点击、Enter、`triggerSendMessage` 均拦截)。失败附件需用户删除后才能再发;不要把附件 Pending 映射成 `MessageStatus.Pending`
|
|
331
|
-
-
|
|
302
|
+
- 拖拽只响应从系统拖入的文件(编辑器内部标签拖动不会误触发),悬停时框体切换为蓝色描边 + 浅蓝底
|
|
303
|
+
- 发送成功后待发送列表自动清空;文件加入列表后光标自动回到输入区
|
|
332
304
|
|
|
333
|
-
|
|
305
|
+
**个数、大小与格式校验**:
|
|
334
306
|
|
|
335
307
|
- 列表最多保留 **`MAX_UPLOAD_FILES`(9)** 个待发送附件;已满时再次选择/拖入/粘贴文件会弹出 **bkui-vue `Message` 错误提示**(`formatUploadNotAddedMessage`),且不会继续入队。
|
|
336
|
-
- 在未满的前提下:空文件、单文件大小 **`>= MAX_UPLOAD_FILE_SIZE
|
|
308
|
+
- 在未满的前提下:空文件、单文件大小 **`>= MAX_UPLOAD_FILE_SIZE`(45MB,即 `45 * 1024 * 1024` 字节)** 会被跳过并弹出超大小/个数提示。与已有文件重复的项只去重、不弹这条误导文案。
|
|
337
309
|
- **文件类型**:默认使用 `DEFAULT_UPLOAD_ACCEPT`(图片 / 文档 / 文本 / 代码扩展名列表)。系统文件选择框带 `accept` 过滤;选择后、拖拽、粘贴仍会再按扩展名校验,不支持的格式弹出「因格式不支持未添加」并不会入队。可通过 `accept` prop 覆盖(空字符串表示不限制)。
|
|
338
|
-
-
|
|
310
|
+
- 个数上限、重复、大小与类型校验都在 `ChatInput` 的 `handleUpload` 中统一处理(含 + 号菜单唤起的系统文件选择器、拖拽和粘贴)。
|
|
339
311
|
|
|
340
|
-
|
|
312
|
+
**发送内容格式**(有文件时 `content` 变为数组):
|
|
341
313
|
|
|
342
314
|
```typescript
|
|
343
|
-
// onSendMessage 的 content 参数变为数组:
|
|
344
315
|
[
|
|
345
|
-
{ type: 'binary', url: '...', mimeType: 'image/png', filename: 'a.png' },
|
|
346
|
-
{ type: 'binary', url: '...', mimeType: 'application/pdf', filename: 'b.pdf' },
|
|
347
|
-
//
|
|
316
|
+
{ type: 'binary', url: '...', mimeType: 'image/png', filename: 'a.png', size: 10240 },
|
|
317
|
+
{ type: 'binary', url: '...', mimeType: 'application/pdf', filename: 'b.pdf', size: 20480 },
|
|
318
|
+
// 输入框有实际文字时才追加文本段,纯附件消息不带空文本
|
|
348
319
|
{ type: 'text', text: '请帮我分析这两个文件' },
|
|
349
320
|
];
|
|
350
321
|
```
|
|
@@ -353,6 +324,7 @@ const handleSendMessage = async (
|
|
|
353
324
|
<template>
|
|
354
325
|
<ChatInput
|
|
355
326
|
v-model="inputValue"
|
|
327
|
+
:accept="DEFAULT_UPLOAD_ACCEPT"
|
|
356
328
|
:message-status="messageStatus"
|
|
357
329
|
:on-send-message="handleSendMessage"
|
|
358
330
|
:on-stop-sending="handleStopSending"
|
|
@@ -362,11 +334,19 @@ const handleSendMessage = async (
|
|
|
362
334
|
|
|
363
335
|
<script setup lang="ts">
|
|
364
336
|
import { ref } from 'vue';
|
|
365
|
-
import {
|
|
337
|
+
import {
|
|
338
|
+
ChatInput,
|
|
339
|
+
DEFAULT_UPLOAD_ACCEPT,
|
|
340
|
+
MessageStatus,
|
|
341
|
+
type TagSchema,
|
|
342
|
+
type UserMessage,
|
|
343
|
+
} from '@blueking/chat-x';
|
|
366
344
|
|
|
367
345
|
const inputValue = ref('');
|
|
368
346
|
const messageStatus = ref(MessageStatus.Complete);
|
|
369
347
|
|
|
348
|
+
// accept 约束「文件」项 / 拖拽 / 粘贴以及入队校验(默认已含图片扩展名)。
|
|
349
|
+
|
|
370
350
|
const handleSendMessage = async (content: UserMessage['content'], docSchema: TagSchema) => {
|
|
371
351
|
if (Array.isArray(content)) {
|
|
372
352
|
// 有文件时 content 为数组
|
|
@@ -384,7 +364,7 @@ const handleSendMessage = async (
|
|
|
384
364
|
messageStatus.value = MessageStatus.Stop;
|
|
385
365
|
};
|
|
386
366
|
|
|
387
|
-
//
|
|
367
|
+
// 「文件」选中后走这里:一次选择多个文件只回调一次,按文件顺序返回结果
|
|
388
368
|
const handleUpload = async (files: File[]) => {
|
|
389
369
|
const formData = new FormData();
|
|
390
370
|
files.forEach(file => formData.append('files', file));
|
|
@@ -394,104 +374,82 @@ const handleSendMessage = async (
|
|
|
394
374
|
</script>
|
|
395
375
|
```
|
|
396
376
|
|
|
397
|
-
|
|
377
|
+
**渲染效果**(点击左下角 +,「添加」分组里选「文件」)
|
|
398
378
|
|
|
399
|
-
|
|
379
|
+
### 预设上传文件(defaultUploadFiles)
|
|
400
380
|
|
|
401
|
-
|
|
381
|
+
设置初始已上传文件,出现在文件预览区并随下次发送一起携带:
|
|
402
382
|
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
];
|
|
416
|
-
</script>
|
|
383
|
+
```typescript
|
|
384
|
+
import { type UploadFile, UploadStatus } from '@blueking/chat-x';
|
|
385
|
+
|
|
386
|
+
const defaultFiles: UploadFile[] = [
|
|
387
|
+
{
|
|
388
|
+
type: 'binary',
|
|
389
|
+
url: 'https://example.com/report.pdf',
|
|
390
|
+
filename: 'report.pdf',
|
|
391
|
+
mimeType: 'application/pdf',
|
|
392
|
+
status: UploadStatus.Success,
|
|
393
|
+
},
|
|
394
|
+
];
|
|
417
395
|
```
|
|
418
396
|
|
|
419
|
-
##
|
|
397
|
+
## 模型选择
|
|
420
398
|
|
|
421
|
-
|
|
399
|
+
传入 `models` 后在发送按钮左侧展示 [ModelSelector](/components/input/model-selector)。选中值(模型的 `llm_name`)通过 `v-model:selected-model` 双向绑定,`@model-change` 可获取完整模型对象,能力标签由组件依据 `property` 自动派生。
|
|
422
400
|
|
|
423
401
|
```vue
|
|
424
402
|
<template>
|
|
425
403
|
<ChatInput
|
|
426
404
|
v-model="inputValue"
|
|
427
|
-
:
|
|
428
|
-
:
|
|
405
|
+
v-model:selected-model="selectedModel"
|
|
406
|
+
:models="models"
|
|
429
407
|
:on-send-message="handleSendMessage"
|
|
430
|
-
|
|
408
|
+
@model-change="handleModelChange"
|
|
431
409
|
/>
|
|
432
410
|
</template>
|
|
433
|
-
|
|
434
|
-
<script setup lang="ts">
|
|
435
|
-
// 多行占位符
|
|
436
|
-
const placeholder = `你好,我是 AI 小鲸!
|
|
437
|
-
输入 "/" 唤出 Prompt
|
|
438
|
-
输入 "@" 唤出工具
|
|
439
|
-
按 Shift + Enter 换行`;
|
|
440
|
-
</script>
|
|
441
411
|
```
|
|
442
412
|
|
|
443
|
-
|
|
413
|
+
也可通过 `#model-selector` 插槽完全自定义,插槽参数为 `{ models, selectedModel }`:
|
|
444
414
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
415
|
+
```vue
|
|
416
|
+
<template>
|
|
417
|
+
<ChatInput v-model="inputValue" :models="models">
|
|
418
|
+
<template #model-selector="{ models, selectedModel }">
|
|
419
|
+
<span>当前:{{ selectedModel || '未选择' }}(共 {{ models.length }} 个)</span>
|
|
420
|
+
</template>
|
|
421
|
+
</ChatInput>
|
|
422
|
+
</template>
|
|
423
|
+
```
|
|
448
424
|
|
|
449
|
-
|
|
450
|
-
| -------------- | ------------------------------------------ | ------------------------------------------ |
|
|
451
|
-
| `top` | 框体外部顶部(`.chat-input` 上方) | 无 |
|
|
452
|
-
| `interrupt` | 框体外部顶部,位于 `top` 之后 | 无,通常用于展示审批/中断提示 |
|
|
453
|
-
| `input-header` | 框体内部顶部(编辑器上方) | `cite` 不为空时渲染引用区(`CiteContent`) |
|
|
454
|
-
| `files` | 文件预览区 | 有上传文件时渲染 `FileContent` |
|
|
455
|
-
| `attachment` | 底部工具栏快捷指令区(FileUploadBtn 右侧) | 快捷指令按钮列表 / 已选快捷指令 |
|
|
456
|
-
| `send-icon` | 发送按钮内的图标 | 发送图标 / 停止图标(按钮容器由组件控制) |
|
|
425
|
+
## 自定义插槽
|
|
457
426
|
|
|
458
427
|
```vue
|
|
459
428
|
<template>
|
|
460
|
-
<ChatInput
|
|
461
|
-
|
|
462
|
-
:message-status="messageStatus"
|
|
463
|
-
:on-send-message="handleSendMessage"
|
|
464
|
-
:on-stop-sending="handleStopSending"
|
|
465
|
-
>
|
|
466
|
-
<!-- 框体外顶部,适合展示模型信息、Token 消耗等 -->
|
|
429
|
+
<ChatInput v-model="inputValue" :on-send-message="handleSendMessage">
|
|
430
|
+
<!-- 框体外顶部,适合展示模型信息、Token 消耗 -->
|
|
467
431
|
<template #top>
|
|
468
432
|
<div class="input-tips">当前模型: GPT-4 · 剩余 Token: 12,800</div>
|
|
469
433
|
</template>
|
|
470
434
|
|
|
471
|
-
<!--
|
|
435
|
+
<!-- 框体外顶部,适合展示中断、审批提示 -->
|
|
472
436
|
<template #interrupt>
|
|
473
437
|
<div class="input-alert">当前会话有待审批单,暂时不能继续发送</div>
|
|
474
438
|
</template>
|
|
475
439
|
|
|
476
|
-
<!--
|
|
440
|
+
<!-- 替换引用区 -->
|
|
477
441
|
<template #input-header>
|
|
478
442
|
<div class="custom-header">自定义头部内容</div>
|
|
479
443
|
</template>
|
|
480
444
|
|
|
481
445
|
<!-- 替换文件预览区,接收 files 参数 -->
|
|
482
446
|
<template #files="{ files }">
|
|
483
|
-
<
|
|
484
|
-
<span
|
|
485
|
-
v-for="file in files"
|
|
486
|
-
:key="file.filename"
|
|
487
|
-
>{{ file.filename }}</span
|
|
488
|
-
>
|
|
489
|
-
</div>
|
|
447
|
+
<span v-for="file in files" :key="file.filename">{{ file.filename }}</span>
|
|
490
448
|
</template>
|
|
491
449
|
|
|
492
|
-
<!--
|
|
450
|
+
<!-- 替换快捷指令区(+ 号仍在左侧) -->
|
|
493
451
|
<template #attachment>
|
|
494
|
-
<button @click="handleCustomAction"
|
|
452
|
+
<button @click="handleCustomAction">自定义操作</button>
|
|
495
453
|
</template>
|
|
496
454
|
|
|
497
455
|
<!-- 替换发送按钮图标(点击逻辑不变) -->
|
|
@@ -502,72 +460,15 @@ const handleSendMessage = async (
|
|
|
502
460
|
</template>
|
|
503
461
|
```
|
|
504
462
|
|
|
505
|
-
**渲染效果**(顶部自定义模型信息与中断提示)
|
|
506
|
-
|
|
507
|
-
## 模型选择
|
|
508
|
-
|
|
509
|
-
传入 `models` 后,会在发送按钮左侧展示 [ModelSelector](/components/input/model-selector)。选中值(模型的 `llm_name`)通过 `v-model:selected-model` 双向绑定,`@model-change` 可获取完整模型对象。能力标签由组件依据 `property` 自动派生。
|
|
510
|
-
|
|
511
|
-
```vue
|
|
512
|
-
<template>
|
|
513
|
-
<ChatInput
|
|
514
|
-
v-model="inputValue"
|
|
515
|
-
v-model:selected-model="selectedModel"
|
|
516
|
-
:message-status="messageStatus"
|
|
517
|
-
:models="models"
|
|
518
|
-
:on-send-message="handleSendMessage"
|
|
519
|
-
@model-change="handleModelChange"
|
|
520
|
-
/>
|
|
521
|
-
</template>
|
|
522
|
-
|
|
523
|
-
<script setup lang="ts">
|
|
524
|
-
import { ref } from 'vue';
|
|
525
|
-
import { ChatInput, MessageStatus, type IModelOption, type TagSchema } from '@blueking/chat-x';
|
|
526
|
-
|
|
527
|
-
const inputValue = ref('');
|
|
528
|
-
// 选中值为 llm_name
|
|
529
|
-
const selectedModel = ref('GPT-4');
|
|
530
|
-
const messageStatus = ref(MessageStatus.Complete);
|
|
531
|
-
const models: IModelOption[] = [
|
|
532
|
-
{ id: 1, llm_name: 'GPT-4', property: { support_thinking: true } },
|
|
533
|
-
{ id: 2, llm_name: 'Claude 3', property: {} },
|
|
534
|
-
];
|
|
535
|
-
|
|
536
|
-
const handleSendMessage = async (content: string, docSchema: TagSchema) => {
|
|
537
|
-
/* 发送时可读取 selectedModel.value */
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
const handleModelChange = (model: IModelOption) => {
|
|
541
|
-
console.log('切换模型:', model);
|
|
542
|
-
};
|
|
543
|
-
</script>
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
也可通过 `#model-selector` 插槽完全自定义选择器,插槽参数为 `{ models, selectedModel }`:
|
|
547
|
-
|
|
548
|
-
```vue
|
|
549
|
-
<template>
|
|
550
|
-
<ChatInput v-model="inputValue" :models="models">
|
|
551
|
-
<template #model-selector="{ models, selectedModel }">
|
|
552
|
-
<span>当前:{{ selectedModel || '未选择' }}(共 {{ models.length }} 个)</span>
|
|
553
|
-
</template>
|
|
554
|
-
</ChatInput>
|
|
555
|
-
</template>
|
|
556
|
-
```
|
|
557
|
-
|
|
558
463
|
## Expose(模板引用)
|
|
559
464
|
|
|
560
|
-
通过 `ref` 获取组件实例后可调用以下方法:
|
|
561
|
-
|
|
562
465
|
```vue
|
|
563
466
|
<template>
|
|
564
|
-
<ChatInput
|
|
565
|
-
ref="chatInputRef"
|
|
566
|
-
v-model="inputValue"
|
|
567
|
-
:on-send-message="handleSendMessage"
|
|
568
|
-
/>
|
|
467
|
+
<ChatInput ref="chatInputRef" v-model="inputValue" :on-send-message="handleSendMessage" />
|
|
569
468
|
<button @click="chatInputRef?.focus()">聚焦输入框</button>
|
|
570
|
-
<button @click="chatInputRef?.
|
|
469
|
+
<button @click="chatInputRef?.insertMention({ id: 'output-1', type: 'artifact', name: '巡检报告.pdf' })">
|
|
470
|
+
引用产物
|
|
471
|
+
</button>
|
|
571
472
|
</template>
|
|
572
473
|
|
|
573
474
|
<script setup lang="ts">
|
|
@@ -575,130 +476,129 @@ const handleSendMessage = async (
|
|
|
575
476
|
import { ChatInput } from '@blueking/chat-x';
|
|
576
477
|
|
|
577
478
|
const chatInputRef = useTemplateRef<InstanceType<typeof ChatInput>>('chatInputRef');
|
|
578
|
-
const inputValue = ref('');
|
|
579
|
-
const handleSendMessage = async (content: string) => {
|
|
580
|
-
/* ... */
|
|
581
|
-
};
|
|
582
479
|
</script>
|
|
583
480
|
```
|
|
584
481
|
|
|
482
|
+
> 在 [ChatContainer](/components/setup/chat-container) 内部时,消息区与侧栏可以直接用 [useInputMention](/composables/use-input-mention) 把资源「@ 进输入框」,无需自行持有 `ChatInput` 实例。
|
|
483
|
+
|
|
585
484
|
## API
|
|
586
485
|
|
|
587
486
|
### Props
|
|
588
487
|
|
|
589
|
-
| 属性名 | 类型
|
|
590
|
-
| ------------------ |
|
|
591
|
-
| modelValue | `string \| TagSchema`
|
|
592
|
-
|
|
|
593
|
-
|
|
|
594
|
-
|
|
|
595
|
-
|
|
|
596
|
-
|
|
|
597
|
-
|
|
|
598
|
-
|
|
|
599
|
-
| models | `IModelOption[]`
|
|
600
|
-
|
|
|
601
|
-
|
|
|
602
|
-
|
|
|
603
|
-
|
|
|
604
|
-
|
|
|
605
|
-
|
|
|
606
|
-
|
|
|
607
|
-
|
|
|
608
|
-
|
|
|
609
|
-
| onStopSending | `() => Promise<void>` | - | - | 停止发送回调,点击停止按钮时触发 |
|
|
488
|
+
| 属性名 | 类型 | 默认值 | 必填 | 说明 |
|
|
489
|
+
| ------------------ | ---------------------------------------------------- | -------- | ---- | -------------------------------------------------------------------- |
|
|
490
|
+
| modelValue | `string \| TagSchema` | - | ✅ | 编辑器的值,支持 `v-model` |
|
|
491
|
+
| menuSources | `IInputMenuItem[]` | `[]` | - | 统一菜单数据源,按 `type` 分发到 `/` `@` `\` 与 + 号 |
|
|
492
|
+
| menuGroupItemLimit | `number` | `4` | - | 每个分组默认展示条数,超出折叠为「更多 +N」 |
|
|
493
|
+
| selectedModel | `string` | - | - | 当前选中模型的 `llm_name`,支持 `v-model:selected-model` |
|
|
494
|
+
| cite | `string` | `''` | - | 引用内容,支持 `v-model:cite`,不为空时显示引用区 |
|
|
495
|
+
| messageStatus | `MessageStatus` | - | - | 消息状态,控制按钮;输入为空且无附件时内部强制 `disabled` |
|
|
496
|
+
| shortcuts | `Shortcut[]` | - | - | 快捷指令列表,显示在底部工具栏 |
|
|
497
|
+
| shortcutId | `string` | - | - | 当前选中的快捷指令 ID,命中时列表收起为已选样式 |
|
|
498
|
+
| models | `IModelOption[]` | - | - | 可选模型列表,非空时在发送按钮左侧展示模型选择器 |
|
|
499
|
+
| placeholder | `string` | 动态默认 | - | 编辑器占位符,支持多行;未传时按 `menuSources` 的类型动态拼接 |
|
|
500
|
+
| inputMaxHeight | `number` | `280` | - | 框体最大高度(px),有文件时自动叠加文件预览区高度 |
|
|
501
|
+
| defaultUploadFiles | `UploadFile[]` | - | - | 预设已上传的文件列表 |
|
|
502
|
+
| sendDisabledTip | `string` | - | - | 阻塞发送时的 tooltip;传入后点击、Enter 与 `triggerSendMessage()` 均不发送 |
|
|
503
|
+
| supportUpload | `boolean` | `true` | - | 是否开启上传能力(内置「文件」菜单项、拖拽与粘贴) |
|
|
504
|
+
| accept | `string` | `DEFAULT_UPLOAD_ACCEPT` | - | 「文件」项 / 拖拽 / 粘贴的过滤类型,同时用于入队后的扩展名校验;空字符串表示不限制 |
|
|
505
|
+
| tippyOptions | `AITippyProps` | - | - | 透传给 AddMenuBtn、InputAttachment、ModelSelector 的 tooltip 配置 |
|
|
506
|
+
| onSendMessage | `(content: UserMessage['content'], docSchema: TagSchema, options?: { interrupt?: Interrupt; payload?: InterruptResume }) => Promise<void>` | - | - | 发送回调;无文件时 `content` 为字符串,有文件时为数组;第三参数由 [ChatContainer](/components/setup/chat-container) 在 UserQuestion 场景注入 |
|
|
507
|
+
| onStopSending | `() => Promise<void>` | - | - | 停止发送回调 |
|
|
610
508
|
| onUpload | `(files: File[]) => Promise<ChatInputUploadResult \| ChatInputUploadResult[]>` | - | - | 文件上传回调(一次选择批量传入);上传中/失败附件会阻塞发送 |
|
|
611
509
|
|
|
612
|
-
### 默认占位符
|
|
613
|
-
|
|
614
|
-
未传入 `placeholder` 时,根据当前 `skills` / `prompts` / `resources` 是否非空动态拼接(有对应能力才显示该行),始终保留换行提示:
|
|
615
|
-
|
|
616
|
-
```
|
|
617
|
-
输入 "/" 唤出 Skill // 仅当 skills 非空
|
|
618
|
-
输入 "\" 唤出 Prompt // 仅当 prompts 非空
|
|
619
|
-
输入 "@" 唤出 工具和 MCP // 仅当 resources 非空
|
|
620
|
-
通过 Shift + Enter 进行换行输入 // 始终显示
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
显式传入 `placeholder`(含空字符串)时完全覆盖上述默认文案。三种列表都为空时,只显示换行提示。
|
|
624
|
-
|
|
625
510
|
### Events
|
|
626
511
|
|
|
627
|
-
| 事件名 | 参数
|
|
628
|
-
| ----------------- |
|
|
629
|
-
| update:modelValue | `(value: string \| TagSchema, selectedResourceList:
|
|
630
|
-
| modelChange | `(model: IModelOption)`
|
|
631
|
-
| selectShortcut | `(shortcut: Shortcut)`
|
|
632
|
-
| deleteShortcut | -
|
|
512
|
+
| 事件名 | 参数 | 触发时机 |
|
|
513
|
+
| ----------------- | --------------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
514
|
+
| update:modelValue | `(value: string \| TagSchema, selectedResourceList: IInputMenuItem[])` | 编辑器值变化;第二参数为文档中能在 `menuSources` 反查到的条目 |
|
|
515
|
+
| modelChange | `(model: IModelOption)` | 用户切换模型 |
|
|
516
|
+
| selectShortcut | `(shortcut: Shortcut)` | 点击底部快捷指令按钮 |
|
|
517
|
+
| deleteShortcut | - | 点击已选快捷指令旁的关闭按钮 |
|
|
518
|
+
| deleteFile | `(file: Partial<UploadFile>)` | 用户取消附件;携带文件 `id`、状态等信息,UI 立即移除,不等待删除接口 |
|
|
633
519
|
|
|
634
520
|
### Slots
|
|
635
521
|
|
|
636
|
-
| 插槽名
|
|
637
|
-
|
|
|
638
|
-
| top
|
|
639
|
-
| interrupt
|
|
640
|
-
| input-header
|
|
641
|
-
| files
|
|
642
|
-
| attachment
|
|
643
|
-
| model-selector | `{ models: IModelOption[]; selectedModel: string \| undefined }` | 发送按钮左侧模型选择区,默认渲染 ModelSelector
|
|
644
|
-
| send-icon
|
|
522
|
+
| 插槽名 | 参数 | 说明 |
|
|
523
|
+
| -------------- | ---------------------------------------------------------------- | -------------------------------------------------------- |
|
|
524
|
+
| top | - | 框体外部顶部,适合展示模型 / Token 信息 |
|
|
525
|
+
| interrupt | - | 框体外部顶部,位于 `top` 之后,适合展示审批 / 中断提示 |
|
|
526
|
+
| input-header | - | 框体内顶部,替换引用区(`CiteContent`) |
|
|
527
|
+
| files | `{ files: Partial<UploadFile>[] }` | 文件预览区 |
|
|
528
|
+
| attachment | - | 底部快捷指令区,`AddMenuBtn` 在其左侧,不受此插槽影响 |
|
|
529
|
+
| model-selector | `{ models: IModelOption[]; selectedModel: string \| undefined }` | 发送按钮左侧模型选择区,默认渲染 `ModelSelector` |
|
|
530
|
+
| send-icon | - | 发送按钮内图标,点击逻辑与样式仍由组件控制 |
|
|
645
531
|
|
|
646
532
|
### Expose
|
|
647
533
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
|
651
|
-
|
|
|
534
|
+
`uploadedArtifacts`:只读 `AIFileInfo[]`,包含输入框中已上传且具有 `outputId` 的附件;供容器合并到侧栏预览列表,取消或发送后同步更新。
|
|
535
|
+
|
|
536
|
+
| 方法名 | 类型 | 说明 |
|
|
537
|
+
| ------------------ | --------------------------------- | ------------------------------------------------ |
|
|
538
|
+
| focus | `() => void` | 聚焦编辑器并把光标置于末尾 |
|
|
539
|
+
| insertMention | `(item: IInputMenuItem) => void` | 把条目以标签形式追加到文档末尾(不依赖当前光标) |
|
|
540
|
+
| triggerSendMessage | `() => void` | 手动触发发送逻辑 |
|
|
652
541
|
|
|
653
542
|
## 键盘快捷键
|
|
654
543
|
|
|
655
|
-
| 快捷键 | 说明
|
|
656
|
-
| --------------- |
|
|
657
|
-
| `Enter` |
|
|
658
|
-
| `Shift + Enter` | 换行
|
|
659
|
-
| `/`
|
|
660
|
-
|
|
|
661
|
-
|
|
|
662
|
-
|
|
544
|
+
| 快捷键 | 说明 |
|
|
545
|
+
| --------------- | ------------------------------------------------ |
|
|
546
|
+
| `Enter` | 发送消息;菜单展开时改为选中当前高亮条目 |
|
|
547
|
+
| `Shift + Enter` | 换行 |
|
|
548
|
+
| `/` `@` `\` | 唤出对应菜单 |
|
|
549
|
+
| `↑` / `↓` | 在菜单条目间导航 |
|
|
550
|
+
| `Esc` | 关闭菜单 |
|
|
551
|
+
|
|
552
|
+
> 点击输入区之外(`mousedown` 捕获阶段)同样会关闭菜单。
|
|
663
553
|
|
|
664
554
|
## 类型定义
|
|
665
555
|
|
|
666
556
|
> `MessageStatus` 完整取值见 [常量枚举](../../types/constants)。与输入区相关:`pending` / `streaming` / `fetching` → 停止按钮;`complete` / `completed` / `error` / `stop` → 发送;`disabled` → 置灰。
|
|
667
557
|
|
|
668
558
|
```typescript
|
|
669
|
-
import type {
|
|
559
|
+
import type { Component } from 'vue';
|
|
560
|
+
|
|
561
|
+
// 菜单可选项类型;file 为组件内置动作项,不由业务方提供
|
|
562
|
+
type MenuItemType = 'file' | 'skill' | 'mcp' | 'tool' | 'knowledgebase' | 'doc' | 'artifact' | 'prompt';
|
|
563
|
+
|
|
564
|
+
// 菜单触发方式;plus 由左下角 + 号唤起
|
|
565
|
+
type MenuTrigger = '/' | '@' | '\\' | 'plus';
|
|
566
|
+
|
|
567
|
+
interface IInputMenuItem {
|
|
568
|
+
id: string;
|
|
569
|
+
name: string;
|
|
570
|
+
type: MenuItemType;
|
|
571
|
+
/** Prompt 全文;选中 prompt 时整体替换输入框内容 */
|
|
572
|
+
content?: string;
|
|
573
|
+
/** 描述文案,有值时 hover 弹出气泡说明 */
|
|
574
|
+
description?: string;
|
|
575
|
+
disabled?: boolean;
|
|
576
|
+
/** 图标 URL 或 Vue 组件;缺省按 type 回退,artifact 按文件名后缀推导 */
|
|
577
|
+
icon?: Component | string;
|
|
578
|
+
}
|
|
670
579
|
|
|
671
580
|
// 上传状态
|
|
672
581
|
enum UploadStatus {
|
|
673
|
-
Pending = 'pending',
|
|
674
|
-
Success = 'success',
|
|
675
|
-
Error = 'error',
|
|
582
|
+
Pending = 'pending',
|
|
583
|
+
Success = 'success',
|
|
584
|
+
Error = 'error',
|
|
676
585
|
}
|
|
677
586
|
|
|
678
587
|
// 上传文件
|
|
679
588
|
type UploadFile = {
|
|
680
589
|
type: 'binary';
|
|
681
|
-
url?: string;
|
|
682
|
-
filename?: string;
|
|
683
|
-
mimeType?: string;
|
|
684
|
-
file?: File;
|
|
685
|
-
status?: UploadStatus;
|
|
590
|
+
url?: string;
|
|
591
|
+
filename?: string;
|
|
592
|
+
mimeType?: string;
|
|
593
|
+
file?: File;
|
|
594
|
+
status?: UploadStatus;
|
|
686
595
|
};
|
|
687
596
|
|
|
688
|
-
// 资源菜单项(@ 触发)
|
|
689
|
-
interface IAiSlashMenuItem {
|
|
690
|
-
id: string;
|
|
691
|
-
name: string;
|
|
692
|
-
icon: string;
|
|
693
|
-
type: 'tool' | 'mcp' | 'doc' | 'shortcut'; // 分组类型
|
|
694
|
-
}
|
|
695
|
-
|
|
696
597
|
// onSendMessage 的 content 参数
|
|
697
598
|
type SendContent =
|
|
698
|
-
| string
|
|
599
|
+
| string
|
|
699
600
|
| Array<
|
|
700
|
-
|
|
701
|
-
{ type: 'binary'; url?: string; mimeType: string; filename: string } | { type: 'text'; text: string }
|
|
601
|
+
{ type: 'binary'; url?: string; mimeType: string; filename: string; size?: number } | { type: 'text'; text: string }
|
|
702
602
|
>;
|
|
703
603
|
|
|
704
604
|
// onSendMessage 完整签名(第三参数由 ChatContainer 在 UserQuestion 场景注入)
|
|
@@ -709,70 +609,14 @@ type OnSendMessage = (
|
|
|
709
609
|
) => Promise<void>;
|
|
710
610
|
```
|
|
711
611
|
|
|
712
|
-
## 完整集成示例
|
|
713
|
-
|
|
714
|
-
```vue
|
|
715
|
-
<template>
|
|
716
|
-
<ChatInput
|
|
717
|
-
v-model="inputValue"
|
|
718
|
-
v-model:cite="citeContent"
|
|
719
|
-
:message-status="messageStatus"
|
|
720
|
-
:prompts="prompts"
|
|
721
|
-
:resources="resources"
|
|
722
|
-
:shortcuts="shortcuts"
|
|
723
|
-
:shortcut-id="shortcutId"
|
|
724
|
-
:on-send-message="handleSendMessage"
|
|
725
|
-
:on-stop-sending="handleStopSending"
|
|
726
|
-
:on-upload="handleUpload"
|
|
727
|
-
@select-shortcut="shortcutId = $event.id"
|
|
728
|
-
@delete-shortcut="shortcutId = ''"
|
|
729
|
-
/>
|
|
730
|
-
</template>
|
|
731
|
-
|
|
732
|
-
<script setup lang="ts">
|
|
733
|
-
import { ref } from 'vue';
|
|
734
|
-
import { ChatInput, MessageStatus, type TagSchema, type Shortcut } from '@blueking/chat-x';
|
|
735
|
-
|
|
736
|
-
const inputValue = ref('');
|
|
737
|
-
const citeContent = ref('');
|
|
738
|
-
const messageStatus = ref(MessageStatus.Complete);
|
|
739
|
-
const shortcutId = ref('');
|
|
740
|
-
|
|
741
|
-
const prompts = ['帮我写一篇关于 {topic} 的文章', '解释一下这段代码'];
|
|
742
|
-
const resources = [{ id: 'tool1', name: '天气查询', type: 'tool', icon: '' }];
|
|
743
|
-
const shortcuts: Shortcut[] = [
|
|
744
|
-
{ id: 'translate', name: '翻译' },
|
|
745
|
-
{ id: 'explain', name: '解释' },
|
|
746
|
-
];
|
|
747
|
-
|
|
748
|
-
const handleSendMessage = async (content, docSchema: TagSchema) => {
|
|
749
|
-
messageStatus.value = MessageStatus.Streaming;
|
|
750
|
-
try {
|
|
751
|
-
await callAI(content, { cite: citeContent.value, shortcut: shortcutId.value });
|
|
752
|
-
} finally {
|
|
753
|
-
messageStatus.value = MessageStatus.Complete;
|
|
754
|
-
citeContent.value = '';
|
|
755
|
-
}
|
|
756
|
-
};
|
|
757
|
-
|
|
758
|
-
const handleStopSending = async () => {
|
|
759
|
-
messageStatus.value = MessageStatus.Stop;
|
|
760
|
-
abortAICall();
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
const handleUpload = async (files: File[]) => {
|
|
764
|
-
const formData = new FormData();
|
|
765
|
-
files.forEach(file => formData.append('files', file));
|
|
766
|
-
const res = await fetch('/api/upload', { method: 'POST', body: formData });
|
|
767
|
-
return res.json();
|
|
768
|
-
};
|
|
769
|
-
</script>
|
|
770
|
-
```
|
|
771
|
-
|
|
772
612
|
## 关联组件
|
|
773
613
|
|
|
774
|
-
- [
|
|
775
|
-
- [
|
|
776
|
-
- [
|
|
614
|
+
- [AiSlashInput](/components/input/ai-slash-input) — 内部富文本编辑区与标签插入
|
|
615
|
+
- [InputMenuPanel](/components/input/input-menu-panel) — 统一菜单面板与分组折叠逻辑
|
|
616
|
+
- [AddMenuBtn](/components/input/add-menu-btn) — 左下角 + 号聚合菜单入口
|
|
617
|
+
- [MentionTag](/components/rendering/mention-tag) — 编辑器与消息中的资源标签
|
|
618
|
+
- [ModelSelector](/components/input/model-selector) — 模型下拉选择器
|
|
619
|
+
- [ShortcutBtns](/components/input/shortcut-btns) / [ShortcutBtn](/components/input/shortcut-btn) — 快捷指令按钮
|
|
620
|
+
- [CiteContent](/components/rendering/cite-content) — 引用区内容展示
|
|
777
621
|
- [ChatContainer](/components/setup/chat-container) — 顶层布局中包裹输入区
|
|
778
|
-
- [
|
|
622
|
+
- [useInputMention](/composables/use-input-mention) — 从消息区 / 侧栏把资源插入输入框
|