@blueking/chat-x 0.0.5 → 0.0.7

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.
Files changed (85) hide show
  1. package/dist/components/ai-selection/ai-selection.vue.d.ts +2 -2
  2. package/dist/components/index.d.ts +2 -1
  3. package/dist/index.css +1 -1
  4. package/dist/index.js +6 -6
  5. package/dist/index.js.map +1 -1
  6. package/dist/mcp/generated/docs/activity-message.md +428 -0
  7. package/dist/mcp/generated/docs/ai-image.md +227 -0
  8. package/dist/mcp/generated/docs/ai-loading.md +129 -0
  9. package/dist/mcp/generated/docs/ai-selection.md +436 -0
  10. package/dist/mcp/generated/docs/animation-text.md +199 -0
  11. package/dist/mcp/generated/docs/assistant-message.md +424 -0
  12. package/dist/mcp/generated/docs/chat-container.md +402 -0
  13. package/dist/mcp/generated/docs/chat-input.md +625 -0
  14. package/dist/mcp/generated/docs/cite-content.md +138 -0
  15. package/dist/mcp/generated/docs/code-content.md +199 -0
  16. package/dist/mcp/generated/docs/common-error-content.md +70 -0
  17. package/dist/mcp/generated/docs/constants.md +216 -0
  18. package/dist/mcp/generated/docs/content-render.md +238 -0
  19. package/dist/mcp/generated/docs/delete-tool.md +188 -0
  20. package/dist/mcp/generated/docs/desc-panel.md +139 -0
  21. package/dist/mcp/generated/docs/execution-summary.md +126 -0
  22. package/dist/mcp/generated/docs/file-content.md +300 -0
  23. package/dist/mcp/generated/docs/file-upload-btn.md +174 -0
  24. package/dist/mcp/generated/docs/flow-message.md +305 -0
  25. package/dist/mcp/generated/docs/highlight-keyword.md +144 -0
  26. package/dist/mcp/generated/docs/image-content.md +178 -0
  27. package/dist/mcp/generated/docs/image-preview-group.md +181 -0
  28. package/dist/mcp/generated/docs/image-preview.md +224 -0
  29. package/dist/mcp/generated/docs/info-message.md +124 -0
  30. package/dist/mcp/generated/docs/key-value-content.md +124 -0
  31. package/dist/mcp/generated/docs/latex-content.md +196 -0
  32. package/dist/mcp/generated/docs/loading-message.md +171 -0
  33. package/dist/mcp/generated/docs/markdown-content.md +186 -0
  34. package/dist/mcp/generated/docs/markdown-latex.md +208 -0
  35. package/dist/mcp/generated/docs/markdown-mermaid.md +250 -0
  36. package/dist/mcp/generated/docs/mermaid-content.md +185 -0
  37. package/dist/mcp/generated/docs/message-container.md +534 -0
  38. package/dist/mcp/generated/docs/message-render.md +329 -0
  39. package/dist/mcp/generated/docs/message-tools.md +376 -0
  40. package/dist/mcp/generated/docs/messages.md +472 -0
  41. package/dist/mcp/generated/docs/overflow-tips.md +209 -0
  42. package/dist/mcp/generated/docs/reasoning-message.md +233 -0
  43. package/dist/mcp/generated/docs/reference-content.md +132 -0
  44. package/dist/mcp/generated/docs/scroll-btn.md +155 -0
  45. package/dist/mcp/generated/docs/selection-footer.md +75 -0
  46. package/dist/mcp/generated/docs/shortcut-btn.md +202 -0
  47. package/dist/mcp/generated/docs/shortcut-btns.md +264 -0
  48. package/dist/mcp/generated/docs/shortcut-render.md +418 -0
  49. package/dist/mcp/generated/docs/text-content.md +74 -0
  50. package/dist/mcp/generated/docs/theme.md +388 -0
  51. package/dist/mcp/generated/docs/tool-btn.md +254 -0
  52. package/dist/mcp/generated/docs/tool-message.md +217 -0
  53. package/dist/mcp/generated/docs/toolcall-render.md +299 -0
  54. package/dist/mcp/generated/docs/use-animation-text.md +198 -0
  55. package/dist/mcp/generated/docs/use-clipboard.md +206 -0
  56. package/dist/mcp/generated/docs/use-command-selection.md +128 -0
  57. package/dist/mcp/generated/docs/use-container-scroll.md +56 -0
  58. package/dist/mcp/generated/docs/use-custom-tab.md +122 -0
  59. package/dist/mcp/generated/docs/use-global-config.md +154 -0
  60. package/dist/mcp/generated/docs/use-menu-keydown.md +164 -0
  61. package/dist/mcp/generated/docs/use-message-group.md +175 -0
  62. package/dist/mcp/generated/docs/use-observer-visible-list.md +189 -0
  63. package/dist/mcp/generated/docs/use-parent-scrolling.md +46 -0
  64. package/dist/mcp/generated/docs/user-feedback.md +229 -0
  65. package/dist/mcp/generated/docs/user-message.md +347 -0
  66. package/dist/mcp/generated/index.json +1311 -0
  67. package/dist/mcp/index.d.ts +2 -0
  68. package/dist/mcp/index.js +42 -0
  69. package/dist/mcp/index.js.map +1 -0
  70. package/dist/mcp/server.d.ts +2 -0
  71. package/dist/mcp/server.js +43 -0
  72. package/dist/mcp/server.js.map +1 -0
  73. package/dist/mcp/tools/get-component-doc.d.ts +19 -0
  74. package/dist/mcp/tools/get-component-doc.js +60 -0
  75. package/dist/mcp/tools/get-component-doc.js.map +1 -0
  76. package/dist/mcp/tools/list-components.d.ts +35 -0
  77. package/dist/mcp/tools/list-components.js +147 -0
  78. package/dist/mcp/tools/list-components.js.map +1 -0
  79. package/dist/mcp/tools/search-docs.d.ts +14 -0
  80. package/dist/mcp/tools/search-docs.js +82 -0
  81. package/dist/mcp/tools/search-docs.js.map +1 -0
  82. package/dist/mcp/utils/doc-loader.d.ts +35 -0
  83. package/dist/mcp/utils/doc-loader.js +64 -0
  84. package/dist/mcp/utils/doc-loader.js.map +1 -0
  85. package/package.json +5 -7
@@ -0,0 +1,189 @@
1
+ <!-- AI SUMMARY -->
2
+ ## 快速了解
3
+
4
+ useObserverVisibleList 根据 containerRef、itemRefs、gap、ComputedRef items 与可选 moreItemRef,用 ResizeObserver + 贪心算法得到 visibleItems。 依赖每项真实 offsetWidth,隐藏项需仍挂载于 DOM。返回 calculateVisibleMenuItems 供必要时手动触发。 ShortcutBtns 内部用于快捷指令溢出收入「更多」菜单。
5
+
6
+ ### 关联组件
7
+ - **shortcut-btns** — 唯一内置使用方
8
+
9
+ ---
10
+ <!-- FULL DOC -->
11
+
12
+ # useObserverVisibleList 可见列表计算
13
+
14
+ > **分类**:composable
15
+
16
+ 基于 `ResizeObserver` 的容器宽度感知组合式函数:遍历列表项的实际 `offsetWidth`,使用贪心算法计算在容器中能完整显示的项目子集,并为"更多"按钮动态预留空间。
17
+
18
+ > 该 composable 仅在 `ShortcutBtns` 内部使用,**通常直接使用 `ShortcutBtns` 组件即可**。
19
+
20
+ ## 工作原理
21
+
22
+ ```
23
+ calculateVisibleMenuItems():
24
+ await nextTick()
25
+ containerWidth = containerRef.value.offsetWidth
26
+
27
+ Set<T> list = {}
28
+ totalWidth = 0
29
+
30
+ for i in params.items.value: // items 是 ComputedRef<T[]>,通过 .value 访问
31
+ itemRef = itemRefs.value[i]
32
+ buttonWidth = itemRef.offsetWidth
33
+ gap = list.size > 0 ? params.gap : 0 // 首项不加 gap
34
+ neededWidth = totalWidth + buttonWidth + gap
35
+ moreItemWidth = moreItemRef?.value?.$el?.offsetWidth ?? 0 // 动态读取
36
+
37
+ if neededWidth + params.gap + moreItemWidth <= containerWidth:
38
+ list.add(item)
39
+ totalWidth = neededWidth
40
+ else:
41
+ break ← 立即中止,不跳过尝试后续项
42
+
43
+ visibleItems.value = Array.from(list)
44
+
45
+ 触发时机:
46
+ ├── onMounted:ResizeObserver.observe(containerRef) + nextTick 初始计算
47
+ ├── watch([itemRefs, moreItemRef]):DOM 引用更新时(nextTick 后)重新计算
48
+ └── onScopeDispose:ResizeObserver.disconnect()
49
+ ```
50
+
51
+ ## 渲染示例
52
+
53
+ `ShortcutBtns` 内部使用 `useObserverVisibleList`,缩放浏览器窗口观察溢出效果:
54
+
55
+ ## 直接使用示例
56
+
57
+ ```vue
58
+ <template>
59
+ <div
60
+ ref="containerRef"
61
+ class="btn-bar"
62
+ >
63
+ <!-- 所有项始终渲染,溢出项用 CSS 隐藏(offsetWidth 仍可读) -->
64
+ <template
65
+ v-for="(item, i) in items"
66
+ :key="item.id"
67
+ >
68
+ <button
69
+ :ref="el => setItemRef(el as HTMLElement, i)"
70
+ :class="['btn-item', { 'btn-item--hidden': !visibleItems.includes(item) }]"
71
+ @click="handleClick(item)"
72
+ >
73
+ {{ item.name }}
74
+ </button>
75
+ </template>
76
+
77
+ <!-- 更多按钮:仅在有隐藏项时显示 -->
78
+ <MoreBtn
79
+ v-show="hiddenItems.length > 0"
80
+ ref="moreBtnRef"
81
+ @click="showMoreMenu"
82
+ />
83
+ </div>
84
+ </template>
85
+
86
+ <script setup lang="ts">
87
+ import { computed, shallowRef, useTemplateRef } from 'vue';
88
+ import { useObserverVisibleList } from '@blueking/chat-x';
89
+
90
+ interface Item {
91
+ id: string;
92
+ name: string;
93
+ }
94
+
95
+ const containerRef = useTemplateRef<HTMLElement>('containerRef');
96
+ const moreBtnRef = useTemplateRef('moreBtnRef');
97
+
98
+ // 必须是 ShallowRef,由调用方维护(DOM 更新后写入)
99
+ const itemRefs = shallowRef<(HTMLElement | null)[]>([]);
100
+
101
+ const items = shallowRef<Item[]>([
102
+ { id: '1', name: '按钮 1' },
103
+ { id: '2', name: '按钮 2' },
104
+ { id: '3', name: '按钮 3' },
105
+ { id: '4', name: '按钮 4' },
106
+ { id: '5', name: '按钮 5' },
107
+ ]);
108
+
109
+ const { visibleItems, calculateVisibleMenuItems } = useObserverVisibleList<Item>(containerRef, itemRefs, {
110
+ gap: 4, // 按钮间距(必填)
111
+ items: computed(() => items.value), // 需传入 ComputedRef<T[]>,内部通过 .value 访问
112
+ moreItemRef: moreBtnRef, // 可选,动态读取"更多"按钮宽度
113
+ });
114
+
115
+ const hiddenItems = computed(() => items.value.filter(i => !visibleItems.value.includes(i)));
116
+
117
+ const setItemRef = (el: HTMLElement | null, index: number) => {
118
+ itemRefs.value[index] = el;
119
+ };
120
+
121
+ // items 变化时:重置 itemRefs(触发 watch → 重新计算)
122
+ watch(items, () => {
123
+ itemRefs.value = new Array(items.value.length).fill(null);
124
+ });
125
+ </script>
126
+
127
+ <style scoped>
128
+ .btn-bar {
129
+ display: flex;
130
+ gap: 4px;
131
+ overflow: hidden;
132
+ }
133
+
134
+ /* 隐藏项必须保持在 DOM 中(offsetWidth 才可读),用定位脱离文档流 */
135
+ .btn-item--hidden {
136
+ position: absolute;
137
+ visibility: hidden;
138
+ pointer-events: none;
139
+ opacity: 0;
140
+ }
141
+ </style>
142
+ ```
143
+
144
+ ## API
145
+
146
+ ```typescript
147
+ function useObserverVisibleList<T>(
148
+ containerRef: TemplateRef<HTMLElement>,
149
+ itemRefs: ShallowRef<(HTMLElement | null)[]>,
150
+ params: {
151
+ gap: number; // 必填,按钮间距(px)
152
+ items: ComputedRef<T[]>; // 必填,响应式项目数组(ComputedRef)
153
+ moreItemRef?: TemplateRef<InstanceType<typeof ShortcutBtn>>; // 可选,"更多"按钮引用
154
+ },
155
+ ): {
156
+ visibleItems: ShallowRef<T[]>;
157
+ calculateVisibleMenuItems: () => Promise<void>;
158
+ };
159
+ ```
160
+
161
+ ### 参数说明
162
+
163
+ | 参数 | 类型 | 必填 | 说明 |
164
+ | -------------------- | ------------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------- |
165
+ | `containerRef` | `TemplateRef<HTMLElement>` | 是 | 容器 DOM 引用;`ResizeObserver` 监听此元素宽度变化 |
166
+ | `itemRefs` | `ShallowRef<(HTMLElement \| null)[]>` | 是 | 各项 DOM 引用数组;`watch` 监听其变化触发重新计算 |
167
+ | `params.gap` | `number` | 是 | 相邻按钮间距(px);首项不加 gap |
168
+ | `params.items` | `ComputedRef<T[]>` | 是 | **响应式**项目数组,需传入 `ComputedRef`(如 `computed(() => list.value)`);内部通过 `.value` 访问最新数据 |
169
+ | `params.moreItemRef` | `TemplateRef<ShortcutBtn>` | 否 | "更多"按钮引用;每次计算从 `$el.offsetWidth` 动态读取宽度;缺省时按 0 计算 |
170
+
171
+ ### 返回值
172
+
173
+ | 属性名 | 类型 | 说明 |
174
+ | --------------------------- | --------------------- | ------------------------------------------------------------------ |
175
+ | `visibleItems` | `ShallowRef<T[]>` | 当前能完整放入容器的项目子集(引用与 `params.items` 中的对象一致) |
176
+ | `calculateVisibleMenuItems` | `() => Promise<void>` | 手动触发计算(内部有 `await nextTick()`);一般无需调用 |
177
+
178
+ ## 注意事项
179
+
180
+ 1. **`items` 必须为 `ComputedRef`**:`params.items` 现在接受 `ComputedRef<T[]>` 类型,内部通过 `.value` 读取最新数据。建议使用 `computed(() => props.shortcuts)` 包装后传入,确保 items 变化时计算逻辑能访问到最新数据。注意 `itemRefs` 的重置仍需在外部维护(`itemRefs.value = new Array(items.value.length).fill(null)`),以触发 `watch([itemRefs, moreItemRef])` 重新计算
181
+ 2. **隐藏项必须留在 DOM**:算法依赖 `itemRef.offsetWidth` 读取每项实际宽度;使用 `position: absolute; visibility: hidden` 隐藏而非 `display: none`
182
+ 3. **算法贪心且单调**:遇到第一个放不下的项就立即 `break`,不跳过继续尝试后续较短的项
183
+ 4. **`moreItemRef` 宽度动态读取**:每次计算循环中实时读取 `$el.offsetWidth`,宽度可以随内容变化(如显示隐藏数量文字时)
184
+ 5. **`ResizeObserver` 仅在 `onMounted` 时绑定一次**:若 `containerRef.value` 在挂载时为 `null`,则不会监听容器宽度变化
185
+ 6. **`moreItemRef` 类型限定为 `ShortcutBtn`**:强依赖内部 `$el` expose,若用于其他组件需确保 expose 了 `$el`
186
+
187
+ ## 关联组件
188
+
189
+ - [ShortcutBtns](../components/molecular/shortcut-btns.md) — 快捷指令条与「更多」
@@ -0,0 +1,46 @@
1
+ <!-- AI SUMMARY -->
2
+ ## 快速了解
3
+
4
+ useParentScrolling(domRef) 在挂载后通过 getScrollParent 找到最近可滚动祖先,监听 scroll 与 scrollend,返回 isScrolling 与 scrollParent。 scroll 时将 isScrolling 置 true,300ms 无滚动或 scrollend 时置 false,适合滚动时隐藏浮层等交互。getScrollParent 可单独导出使用。 当前源码无组件内引用,供业务或后续浮层组件按需集成。
5
+
6
+ ---
7
+ <!-- FULL DOC -->
8
+
9
+ # useParentScrolling 父容器滚动监听
10
+
11
+ > **分类**:composable
12
+
13
+ 向上递归查找**最近可滚动祖先**,监听其 `scroll` / `scrollend` 事件,提供 `isScrolling` 状态。常用于滚动时自动关闭浮层、禁用交互等场景。
14
+
15
+ 同时导出辅助函数 `getScrollParent`,可单独使用。
16
+
17
+ ## 工作原理
18
+
19
+ ```
20
+ getScrollParent(node):
21
+ ├── !node → null
22
+ ├── !(node instanceof HTMLElement) → getScrollParent(parentElement) 或 document.body
23
+ ├── scrollHeight > clientHeight
24
+ │ && overflowY in ['scroll', 'auto', 'overlay'] → return node(找到)
25
+ └── else → getScrollParent(parentElement) 或 document.body(fallback)
26
+
27
+ useParentScrolling(domRef):
28
+ onMounted:
29
+ scrollParent = getScrollParent(toValue(domRef))
30
+ removeEventListener(防御性清理)
31
+ addEventListener('scroll', handleScroll) ← 非 passive
32
+ addEventListener('scrollend', handleScrollEnd)
33
+
34
+ handleScroll:
35
+ isScrolling = true
36
+ clearTimeout(timer)
37
+ timer = setTimeout(() => isScrolling = false, 300) ← 300ms 无滚动后重置
38
+
39
+ handleScrollEnd:
40
+ isScrolling = false ← 原生 scrollend 事件立即重置(浏览器兼容性见下)
41
+
42
+ onScopeDispose:
43
+ removeEventListener(自动清理)
44
+ ```
45
+
46
+ ## 渲染示例
@@ -0,0 +1,229 @@
1
+ <!-- AI SUMMARY -->
2
+ ## 快速了解
3
+
4
+ UserFeedback 在点赞或踩之后展示原因收集面板:多选预设标签、补充说明与异步加载原因列表(骨架屏)。 由 MessageTools 在 like/unlike 流程中挂载,提交后向父级回传原因列表与备注。
5
+
6
+ ### 关联组件
7
+ - **message-tools** — 点赞/踩操作触发并收集反馈
8
+
9
+ ---
10
+ <!-- FULL DOC -->
11
+
12
+ # MessageUserFeedback 用户反馈
13
+
14
+ > **层级**:分子组件 · **功能域**:工具与反馈
15
+
16
+ AI 消息点赞/踩后收集用户具体反馈原因的弹出面板。支持多选预设原因标签、补充文字说明(textarea)、异步加载原因列表(骨架屏)。
17
+
18
+ > **通常不需要直接使用。** `MessageTools` 在用户点击 `like`/`unlike` 工具按钮时,会通过内置 Tippy 弹出层自动呈现本组件,并驱动 `loading` 和 `reasonList`。
19
+
20
+ ## 组件结构
21
+
22
+ ```
23
+ .ai-user-feedback(width: 400px,padding: 16px,gap: 16px)
24
+ ├── .ai-feedback-title(font-size: 16px,color: #313238)
25
+ │ props.title
26
+
27
+ ├── .ai-feedback-reason-list(flex-wrap,gap: 4px)
28
+ │ loading=true → 8 × .reason-item.ai-skeleton-element(每项 width: 70px)
29
+ │ loading=false → v-for reasonList → .reason-item
30
+ │ is-active(已选):color #1768ef,bg #e1ecff
31
+ │ :hover :同上(与 is-active 共享样式)
32
+
33
+ ├── .ai-feedback-other
34
+ │ bkui-vue Input(type=textarea,rows=3,placeholder="说出您的想法")
35
+ │ → v-model 绑定内部 shallowRef otherReason
36
+
37
+ └── .ai-feedback-footer(justify-content: flex-end,gap: 8px)
38
+ Button primary "提交"(width: 64px,disabled:selectedReasons 空且 otherReason 空)
39
+ Button default "取消"(width: 64px)
40
+ ```
41
+
42
+ ## 基础用法
43
+
44
+ ```vue
45
+ <template>
46
+ <MessageUserFeedback
47
+ title="什么原因让你满意?"
48
+ :reason-list="reasonList"
49
+ @submit="handleSubmit"
50
+ @cancel="handleCancel"
51
+ />
52
+ </template>
53
+
54
+ <script setup lang="ts">
55
+ import { MessageUserFeedback } from '@blueking/chat-x';
56
+
57
+ const reasonList = ['回答准确', '信息全面', '表达清晰', '解决了问题', '示例恰当'];
58
+
59
+ const handleSubmit = (selectedReasons: string[], otherReason: string) => {
60
+ // selectedReasons: 当前已勾选的原因(提交后组件不自动清空)
61
+ // otherReason: textarea 中的补充说明(可为空字符串)
62
+ console.log('已选原因:', selectedReasons, '补充说明:', otherReason);
63
+ };
64
+
65
+ const handleCancel = () => {
66
+ // cancel 触发前组件已清空 selectedReasons 和 otherReason
67
+ console.log('用户取消了反馈');
68
+ };
69
+ </script>
70
+ ```
71
+
72
+ **满意反馈**
73
+
74
+ **不满意反馈**
75
+
76
+ ## 加载状态
77
+
78
+ 原因列表通常需要异步获取,将 `loading` 设为 `true` 时渲染 **8 个**骨架屏占位块(固定数量,与 `reasonList` 无关),数据就绪后切换为 `false`:
79
+
80
+ ```vue
81
+ <template>
82
+ <MessageUserFeedback
83
+ title="什么原因让你满意?"
84
+ :reason-list="reasonList"
85
+ :loading="isLoading"
86
+ @submit="handleSubmit"
87
+ @cancel="handleCancel"
88
+ />
89
+ </template>
90
+
91
+ <script setup lang="ts">
92
+ import { ref } from 'vue';
93
+ import { MessageUserFeedback } from '@blueking/chat-x';
94
+
95
+ const isLoading = ref(true);
96
+ const reasonList = ref<string[]>([]);
97
+
98
+ async function fetchReasons() {
99
+ isLoading.value = true;
100
+ try {
101
+ reasonList.value = await fetchFeedbackReasons();
102
+ } finally {
103
+ isLoading.value = false;
104
+ }
105
+ }
106
+ </script>
107
+ ```
108
+
109
+ **骨架屏效果**
110
+
111
+ ## 交互说明
112
+
113
+ | 操作 | 行为说明 |
114
+ | ------------ | ------------------------------------------------------------------------------------------------------------- |
115
+ | 点击原因标签 | **多选**,高亮(`is-active`);再次点击取消选中;hover 效果与选中态相同 |
116
+ | 填写补充说明 | 可选;不选任何标签时,仅有补充说明文本也可解锁提交按钮 |
117
+ | 点击提交 | `selectedReasons.length === 0 && otherReason === ''` 时禁用;点击后触发 `submit` 事件,**组件不自动清空状态** |
118
+ | 点击取消 | 先将 `selectedReasons` 和 `otherReason` 清空(两者均重置),再触发 `cancel` 事件 |
119
+
120
+ > **提交与取消的状态差异**:点击"提交"后内部 `selectedReasons` / `otherReason` **不重置**,由父组件决定后续行为(如关闭弹层);点击"取消"则**立即重置**两者,再触发事件。
121
+
122
+ ## 与 MessageTools 配合使用
123
+
124
+ `MessageUserFeedback` 在实际场景中由 `MessageTools` 驱动,整体流程:
125
+
126
+ ```
127
+ 用户点击 like / unlike
128
+
129
+ MessageTools 内部:loading=true,弹出反馈面板(骨架屏)
130
+
131
+ 调用 onAction(tool) → 返回 string[](支持 async)
132
+
133
+ loading=false,展示原因标签列表
134
+
135
+ 用户选择原因 → 点击提交
136
+
137
+ 触发 onAgentFeedback(tool, messages, reasonList, otherReason)
138
+ ```
139
+
140
+ ```vue
141
+ <template>
142
+ <MessageContainer
143
+ :messages="messages"
144
+ :on-agent-action="handleAgentAction"
145
+ :on-agent-feedback="handleFeedback"
146
+ />
147
+ </template>
148
+
149
+ <script setup lang="ts">
150
+ import { MessageContainer, type IToolBtn, type Message } from '@blueking/chat-x';
151
+
152
+ const messages: Message[] = [
153
+ /* ... */
154
+ ];
155
+
156
+ // 点击 like/unlike 时调用,返回原因列表(支持异步)
157
+ const handleAgentAction = async (tool: IToolBtn): Promise<string[]> => {
158
+ if (tool.id === 'like') {
159
+ return ['回答准确', '信息全面', '表达清晰', '解决了问题'];
160
+ }
161
+ if (tool.id === 'unlike') {
162
+ return ['信息错误', '回答不相关', '解释不清楚', '没有解决问题'];
163
+ }
164
+ return [];
165
+ };
166
+
167
+ // 用户提交反馈后触发
168
+ const handleFeedback = (tool: IToolBtn, messages: Message[], reasonList: string[], otherReason: string) => {
169
+ console.log(`${tool.id} 反馈:`, { reasonList, otherReason });
170
+ // 上报到后端
171
+ };
172
+ </script>
173
+ ```
174
+
175
+ 若需在自定义位置独立使用,可配合 `vue-tippy`:
176
+
177
+ ```vue
178
+ <template>
179
+ <Tippy
180
+ :arrow="false"
181
+ interactive
182
+ trigger="click"
183
+ theme="ai-chat-box-light light"
184
+ :offset="[0, 6]"
185
+ :append-to="() => document.body"
186
+ >
187
+ <button>👍 点赞</button>
188
+ <template #content>
189
+ <MessageUserFeedback
190
+ title="什么原因让你满意?"
191
+ :reason-list="reasonList"
192
+ :loading="isLoading"
193
+ @submit="handleSubmit"
194
+ @cancel="handleCancel"
195
+ />
196
+ </template>
197
+ </Tippy>
198
+ </template>
199
+ ```
200
+
201
+ ## API
202
+
203
+ ### Props
204
+
205
+ | 属性名 | 类型 | 必填 | 默认值 | 说明 |
206
+ | ---------- | ---------- | ---- | ------- | ------------------------------------------------------ |
207
+ | title | `string` | ✓ | — | 面板标题文本 |
208
+ | reasonList | `string[]` | ✓ | — | 预设原因标签列表;`loading=true` 时列表不渲染 |
209
+ | loading | `boolean` | — | `false` | `true` 时显示 8 个骨架屏占位块,隐藏 `reasonList` 内容 |
210
+
211
+ ### Events
212
+
213
+ | 事件名 | 参数签名 | 触发时机 |
214
+ | ------ | --------------------------------------------- | ----------------------------------------------------------- |
215
+ | submit | `(reasonList: string[], otherReason: string)` | 点击提交按钮时(至少一项原因或补充说明不为空) |
216
+ | cancel | — | 点击取消按钮时(内部已重置 selectedReasons 和 otherReason) |
217
+
218
+ ### 内部状态
219
+
220
+ 以下为组件内部 `shallowRef` 状态,不暴露为 props/emits:
221
+
222
+ | 状态名 | 类型 | 说明 |
223
+ | --------------- | ---------- | ------------------------------------------- |
224
+ | selectedReasons | `string[]` | 当前已选原因列表;取消时重置,提交时不重置 |
225
+ | otherReason | `string` | textarea 补充说明;取消时重置,提交时不重置 |
226
+
227
+ ## 关联组件
228
+
229
+ - [MessageTools](./message-tools.md) — 点赞/踩入口与反馈联动