@blueking/chat-x 0.0.5 → 0.0.6

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 (80) hide show
  1. package/dist/mcp/generated/docs/activity-message.md +428 -0
  2. package/dist/mcp/generated/docs/ai-image.md +227 -0
  3. package/dist/mcp/generated/docs/ai-loading.md +129 -0
  4. package/dist/mcp/generated/docs/ai-selection.md +436 -0
  5. package/dist/mcp/generated/docs/animation-text.md +199 -0
  6. package/dist/mcp/generated/docs/assistant-message.md +424 -0
  7. package/dist/mcp/generated/docs/chat-container.md +365 -0
  8. package/dist/mcp/generated/docs/chat-input.md +625 -0
  9. package/dist/mcp/generated/docs/cite-content.md +138 -0
  10. package/dist/mcp/generated/docs/code-content.md +199 -0
  11. package/dist/mcp/generated/docs/common-error-content.md +70 -0
  12. package/dist/mcp/generated/docs/constants.md +216 -0
  13. package/dist/mcp/generated/docs/content-render.md +238 -0
  14. package/dist/mcp/generated/docs/delete-tool.md +188 -0
  15. package/dist/mcp/generated/docs/desc-panel.md +139 -0
  16. package/dist/mcp/generated/docs/execution-summary.md +126 -0
  17. package/dist/mcp/generated/docs/file-content.md +300 -0
  18. package/dist/mcp/generated/docs/file-upload-btn.md +174 -0
  19. package/dist/mcp/generated/docs/flow-message.md +305 -0
  20. package/dist/mcp/generated/docs/highlight-keyword.md +144 -0
  21. package/dist/mcp/generated/docs/image-content.md +178 -0
  22. package/dist/mcp/generated/docs/image-preview-group.md +181 -0
  23. package/dist/mcp/generated/docs/image-preview.md +224 -0
  24. package/dist/mcp/generated/docs/info-message.md +124 -0
  25. package/dist/mcp/generated/docs/key-value-content.md +124 -0
  26. package/dist/mcp/generated/docs/latex-content.md +196 -0
  27. package/dist/mcp/generated/docs/loading-message.md +171 -0
  28. package/dist/mcp/generated/docs/markdown-content.md +186 -0
  29. package/dist/mcp/generated/docs/markdown-latex.md +208 -0
  30. package/dist/mcp/generated/docs/markdown-mermaid.md +250 -0
  31. package/dist/mcp/generated/docs/mermaid-content.md +185 -0
  32. package/dist/mcp/generated/docs/message-container.md +534 -0
  33. package/dist/mcp/generated/docs/message-render.md +329 -0
  34. package/dist/mcp/generated/docs/message-tools.md +376 -0
  35. package/dist/mcp/generated/docs/messages.md +472 -0
  36. package/dist/mcp/generated/docs/overflow-tips.md +209 -0
  37. package/dist/mcp/generated/docs/reasoning-message.md +233 -0
  38. package/dist/mcp/generated/docs/reference-content.md +132 -0
  39. package/dist/mcp/generated/docs/scroll-btn.md +155 -0
  40. package/dist/mcp/generated/docs/selection-footer.md +75 -0
  41. package/dist/mcp/generated/docs/shortcut-btn.md +202 -0
  42. package/dist/mcp/generated/docs/shortcut-btns.md +264 -0
  43. package/dist/mcp/generated/docs/shortcut-render.md +418 -0
  44. package/dist/mcp/generated/docs/text-content.md +74 -0
  45. package/dist/mcp/generated/docs/theme.md +388 -0
  46. package/dist/mcp/generated/docs/tool-btn.md +254 -0
  47. package/dist/mcp/generated/docs/tool-message.md +217 -0
  48. package/dist/mcp/generated/docs/toolcall-render.md +299 -0
  49. package/dist/mcp/generated/docs/use-animation-text.md +198 -0
  50. package/dist/mcp/generated/docs/use-clipboard.md +206 -0
  51. package/dist/mcp/generated/docs/use-command-selection.md +128 -0
  52. package/dist/mcp/generated/docs/use-container-scroll.md +56 -0
  53. package/dist/mcp/generated/docs/use-custom-tab.md +122 -0
  54. package/dist/mcp/generated/docs/use-global-config.md +154 -0
  55. package/dist/mcp/generated/docs/use-menu-keydown.md +164 -0
  56. package/dist/mcp/generated/docs/use-message-group.md +175 -0
  57. package/dist/mcp/generated/docs/use-observer-visible-list.md +189 -0
  58. package/dist/mcp/generated/docs/use-parent-scrolling.md +46 -0
  59. package/dist/mcp/generated/docs/user-feedback.md +229 -0
  60. package/dist/mcp/generated/docs/user-message.md +347 -0
  61. package/dist/mcp/generated/index.json +1311 -0
  62. package/dist/mcp/index.d.ts +2 -0
  63. package/dist/mcp/index.js +42 -0
  64. package/dist/mcp/index.js.map +1 -0
  65. package/dist/mcp/server.d.ts +2 -0
  66. package/dist/mcp/server.js +43 -0
  67. package/dist/mcp/server.js.map +1 -0
  68. package/dist/mcp/tools/get-component-doc.d.ts +19 -0
  69. package/dist/mcp/tools/get-component-doc.js +60 -0
  70. package/dist/mcp/tools/get-component-doc.js.map +1 -0
  71. package/dist/mcp/tools/list-components.d.ts +35 -0
  72. package/dist/mcp/tools/list-components.js +147 -0
  73. package/dist/mcp/tools/list-components.js.map +1 -0
  74. package/dist/mcp/tools/search-docs.d.ts +14 -0
  75. package/dist/mcp/tools/search-docs.js +82 -0
  76. package/dist/mcp/tools/search-docs.js.map +1 -0
  77. package/dist/mcp/utils/doc-loader.d.ts +35 -0
  78. package/dist/mcp/utils/doc-loader.js +64 -0
  79. package/dist/mcp/utils/doc-loader.js.map +1 -0
  80. package/package.json +5 -7
@@ -0,0 +1,198 @@
1
+ <!-- AI SUMMARY -->
2
+ ## 快速了解
3
+
4
+ useAnimationText 接收 MaybeRef<string> 与可选 AnimationConfig(fadeDuration、easing),返回 chunks 与 animationStyle。 监听文本变化:前缀追加则增量拆分为新 chunk 并触发动画,否则重置为单 chunk,适合流式输出逐段淡入。 全局样式已含 ai-markdown-fade-in。AnimationText 原子组件内部封装同一逻辑。
5
+
6
+ ### 关联组件
7
+ - **animation-text** — 封装 chunks 与样式渲染
8
+ - **markdown-content** — 流式 Markdown 文本展示场景可组合使用
9
+
10
+ ---
11
+ <!-- FULL DOC -->
12
+
13
+ # useAnimationText
14
+
15
+ > **分类**:composable
16
+
17
+ 文本淡入动画的组合式函数。将响应式文本按**增量**拆分为独立 chunk,每个新增 chunk 对应一次淡入动画,适用于 AI 流式输出的逐段渐显效果。
18
+
19
+ ## 工作原理
20
+
21
+ 每当 `text` 发生变化时,composable 通过比较新旧文本决定如何更新 chunks:
22
+
23
+ | 变化情况 | 处理方式 |
24
+ | ----------------------------------- | ------------------------- |
25
+ | 新文本以旧文本为前缀(追加) | 将增量部分追加为新 chunk |
26
+ | 新文本与旧文本完全不同(替换/重置) | chunks 重置为 `[newText]` |
27
+ | 文本未变化 | 无操作 |
28
+
29
+ 每个 chunk 对应一个独立的 DOM 节点,节点加入 DOM 时自动触发 `ai-markdown-fade-in` 淡入动画。
30
+
31
+ > `@keyframes ai-markdown-fade-in` 已内置于 `@blueking/chat-x` 全局样式,**无需手动定义**。
32
+
33
+ ## 基础用法
34
+
35
+ 将 `Ref<string>` 传入 composable,配合 `v-for` + `:style` 渲染各 chunk。
36
+
37
+ ```vue
38
+ <template>
39
+ <div>
40
+ <span
41
+ v-for="(chunk, index) in chunks"
42
+ :key="index"
43
+ :style="animationStyle"
44
+ >{{ chunk }}</span
45
+ >
46
+ </div>
47
+ </template>
48
+
49
+ <script setup lang="ts">
50
+ import { ref } from 'vue';
51
+ import { useAnimationText } from '@blueking/chat-x';
52
+
53
+ const text = ref('');
54
+ const { chunks, animationStyle } = useAnimationText(text);
55
+
56
+ // 模拟流式追加
57
+ async function startStreaming() {
58
+ const fullText = '这是一段模拟 AI 流式输出的文本内容...';
59
+ for (const char of fullText) {
60
+ text.value += char;
61
+ await new Promise(resolve => setTimeout(resolve, 50));
62
+ }
63
+ }
64
+
65
+ startStreaming();
66
+ </script>
67
+ ```
68
+
69
+ **渲染效果**
70
+
71
+ ## 自定义动画配置
72
+
73
+ 通过 `options` 调整淡入动画的持续时间和缓动函数。
74
+
75
+ ```vue
76
+ <script setup lang="ts">
77
+ import { ref } from 'vue';
78
+ import { useAnimationText } from '@blueking/chat-x';
79
+
80
+ const text = ref('');
81
+ const { chunks, animationStyle } = useAnimationText(text, {
82
+ fadeDuration: 600, // 淡入持续时间,默认 200ms
83
+ easing: 'ease-out', // CSS 缓动函数,默认 'ease-in-out'
84
+ });
85
+ </script>
86
+ ```
87
+
88
+ **渲染效果**(fadeDuration=600ms)
89
+
90
+ ## 文本重置行为
91
+
92
+ 当 `text` 被完全替换(新值不以旧值为前缀)时,chunks 会重置为单一片段,之前积累的 chunks 清空。这适用于多轮对话中切换消息的场景。
93
+
94
+ ```vue
95
+ <script setup lang="ts">
96
+ import { ref } from 'vue';
97
+ import { useAnimationText } from '@blueking/chat-x';
98
+
99
+ const text = ref('');
100
+ const { chunks, animationStyle } = useAnimationText(text);
101
+
102
+ async function nextMessage(newContent: string) {
103
+ // 直接替换整个 text,chunks 自动重置
104
+ text.value = '';
105
+ for (const char of newContent) {
106
+ text.value += char;
107
+ await new Promise(r => setTimeout(r, 50));
108
+ }
109
+ }
110
+ </script>
111
+ ```
112
+
113
+ **渲染效果**(两条消息依次播放,第二条时 chunks 重置)
114
+
115
+ ## 使用 AnimationText 组件
116
+
117
+ 如无需定制渲染逻辑,可直接使用封装好的 `AnimationText` 组件,内部已集成 `useAnimationText`:
118
+
119
+ ```vue
120
+ <template>
121
+ <AnimationText :text="text" />
122
+ </template>
123
+
124
+ <script setup lang="ts">
125
+ import { AnimationText } from '@blueking/chat-x';
126
+ </script>
127
+ ```
128
+
129
+ > 详见 [AnimationText 组件文档](/components/atomic/animation-text)。
130
+
131
+ ## API
132
+
133
+ ### 函数签名
134
+
135
+ ```typescript
136
+ function useAnimationText(
137
+ text: MaybeRef<string>,
138
+ options?: AnimationConfig,
139
+ ): {
140
+ chunks: Ref<string[]>;
141
+ animationStyle: ComputedRef<CSSProperties>;
142
+ };
143
+ ```
144
+
145
+ ### 参数
146
+
147
+ | 参数 | 类型 | 必填 | 说明 |
148
+ | --------- | ------------------ | ---- | ----------------------------------------- |
149
+ | `text` | `MaybeRef<string>` | 是 | 响应式文本,传入 `Ref<string>` 可追踪变化 |
150
+ | `options` | `AnimationConfig` | 否 | 动画配置 |
151
+
152
+ ### AnimationConfig
153
+
154
+ | 属性 | 类型 | 默认值 | 说明 |
155
+ | -------------- | -------- | --------------- | ---------------------- |
156
+ | `fadeDuration` | `number` | `200` | 淡入动画持续时间(ms) |
157
+ | `easing` | `string` | `'ease-in-out'` | CSS 缓动函数 |
158
+
159
+ ### 返回值
160
+
161
+ | 属性 | 类型 | 说明 |
162
+ | ---------------- | ---------------------------- | ------------------------------------------ |
163
+ | `chunks` | `Ref<string[]>` | 拆分后的文本片段数组,每个片段对应一次动画 |
164
+ | `animationStyle` | `ComputedRef<CSSProperties>` | 所有 chunk 共用的动画样式对象 |
165
+
166
+ ## 类型定义
167
+
168
+ ```typescript
169
+ import type { MaybeRef, Ref, ComputedRef, CSSProperties } from 'vue';
170
+
171
+ interface AnimationConfig {
172
+ fadeDuration?: number;
173
+ easing?: string;
174
+ }
175
+
176
+ function useAnimationText(
177
+ text: MaybeRef<string>,
178
+ options?: AnimationConfig,
179
+ ): {
180
+ chunks: Ref<string[]>;
181
+ animationStyle: ComputedRef<CSSProperties>;
182
+ };
183
+ ```
184
+
185
+ ## 注意事项
186
+
187
+ 1. **在组件 `setup` 中通过 `watch(text, ...)` 直接监听 Ref**:composable 内部使用 `watch(() => text, ...)` 的 getter 形式,当 `text` 是 `Ref` 时,getter 每次返回同一个 Ref 对象,Vue 判断为"值未变化",**watch 不会触发**。在自定义 setup 中如需直接响应文本变化,请用 `watch(text, handler)` 而非 `watch(() => text, handler)`。
188
+
189
+ 2. **`animationStyle` 为所有 chunk 共享的同一个对象**:动画效果来自每个 chunk 对应的 DOM 节点被创建时触发,而非样式本身的差异。
190
+
191
+ 3. **内置 CSS 关键帧**:引入 `@blueking/chat-x` 的全局样式后,`@keyframes ai-markdown-fade-in` 自动可用,无需手动定义。
192
+
193
+ 4. **性能**:长时间流式输出会积累大量 chunk 节点,动画完成后(`forwards`)这些节点保持 `opacity: 1`,对性能影响可接受;如有需要可在流式结束后将 `text` 合并为单次赋值以归并 chunk
194
+
195
+ ## 关联组件
196
+
197
+ - [AnimationText](../components/atomic/animation-text.md) — 默认封装组件
198
+ - [MarkdownContent](../components/atomic/markdown-content.md) — 富文本流式展示场景。
@@ -0,0 +1,206 @@
1
+ <!-- AI SUMMARY -->
2
+ ## 快速了解
3
+
4
+ useClipboard 返回 { copy },copy(text) 将字符串写入剪贴板,返回 Promise<void>。 优先使用 navigator.clipboard.writeText,失败或不支持时降级为隐藏 textarea + execCommand('copy')。 成功/失败均通过 bkui-vue Message 提示,调用方无需处理结果。CodeContent、MessageContainer、UserMessage 的复制能力内部使用。
5
+
6
+ ### 关联组件
7
+ - **code-content** — 复制代码块时取 innerText 后调用 copy
8
+ - **message-container** — AI 消息复制工具回调
9
+ - **user-message** — 用户消息复制工具回调
10
+
11
+ ---
12
+ <!-- FULL DOC -->
13
+
14
+ # useClipboard 剪贴板
15
+
16
+ > **分类**:composable
17
+
18
+ 复制文本到剪贴板的组合式函数。内置两级降级策略,并自动通过 bkui-vue `Message` 提示复制结果,调用方无需关心成功/失败处理。
19
+
20
+ ## 执行流程
21
+
22
+ ```
23
+ copy(text)
24
+
25
+ ├── isClipboardApiSupported(模块加载时判断一次)
26
+ │ = typeof navigator !== 'undefined' && 'clipboard' in navigator
27
+
28
+ ├── true → navigator.clipboard.writeText(text)
29
+ │ ├── 成功 → success = true
30
+ │ └── 失败(权限拒绝等)→ legacyCopy(text)
31
+
32
+ └── false → legacyCopy(text)
33
+ (创建隐藏 textarea,document.execCommand('copy'),finally 清理 DOM)
34
+
35
+ └── Message({ message: t('复制成功/失败'), theme: 'success/error' })
36
+ ```
37
+
38
+ > **注意**:`copy` 返回 `Promise<void>`,调用方无法获取成功/失败结果;通知由内部自动弹出,不可关闭或自定义。
39
+
40
+ ## 基础用法
41
+
42
+ ```vue
43
+ <template>
44
+ <button @click="handleCopy">复制文本</button>
45
+ </template>
46
+
47
+ <script setup lang="ts">
48
+ import { useClipboard } from '@blueking/chat-x';
49
+
50
+ const { copy } = useClipboard();
51
+
52
+ const handleCopy = () => {
53
+ copy('Hello, World!');
54
+ // 自动弹出"复制成功/失败"提示,无需额外处理
55
+ };
56
+ </script>
57
+ ```
58
+
59
+ ## 复制代码块内容
60
+
61
+ `CodeContent` 组件内部的实际用法,取 DOM 元素的 `innerText` 避免 HTML 实体:
62
+
63
+ ```vue
64
+ <template>
65
+ <div class="code-block">
66
+ <pre ref="codeRef"><code>{{ code }}</code></pre>
67
+ <button @click="handleCopyCode">复制代码</button>
68
+ </div>
69
+ </template>
70
+
71
+ <script setup lang="ts">
72
+ import { useTemplateRef } from 'vue';
73
+ import { useClipboard } from '@blueking/chat-x';
74
+
75
+ const codeRef = useTemplateRef<HTMLElement>('codeRef');
76
+ const { copy } = useClipboard();
77
+
78
+ // 使用 innerText 获取渲染后的纯文本,而非原始 token.content
79
+ const handleCopyCode = () => {
80
+ copy(codeRef.value?.innerText ?? '');
81
+ };
82
+ </script>
83
+ ```
84
+
85
+ ## 复制消息内容
86
+
87
+ `MessageContainer` 和 `UserMessage` 内部的实际用法:
88
+
89
+ ```vue
90
+ <script setup lang="ts">
91
+ import { useClipboard } from '@blueking/chat-x';
92
+
93
+ const { copy } = useClipboard();
94
+
95
+ // 在 onAction 工具回调中调用
96
+ const onAction = tool => {
97
+ if (tool.id === 'copy') {
98
+ copy(props.content ?? '');
99
+ }
100
+ };
101
+ </script>
102
+ ```
103
+
104
+ ## API
105
+
106
+ ### 返回值
107
+
108
+ | 属性名 | 类型 | 说明 |
109
+ | ------ | --------------------------------- | ---------------------------------------------------- |
110
+ | copy | `(text: string) => Promise<void>` | 复制文本;内部自动弹出结果提示,调用方无需处理返回值 |
111
+
112
+ ### copy(text)
113
+
114
+ ```typescript
115
+ const copy: (text: string) => Promise<void>;
116
+ ```
117
+
118
+ **参数:**
119
+
120
+ - `text: string`:要复制的文本内容
121
+
122
+ **内部行为:**
123
+
124
+ | 步骤 | 说明 |
125
+ | ------------ | -------------------------------------------------------------------------------------------------------------- |
126
+ | 1. 能力检测 | 模块导入时执行一次:`typeof navigator !== 'undefined' && 'clipboard' in navigator` |
127
+ | 2a. 现代路径 | `navigator.clipboard.writeText(text)`,捕获异常后降级到步骤 2b |
128
+ | 2b. 降级路径 | 创建隐藏 `textarea`(`position: fixed; left: -9999px; opacity: 0`),`execCommand('copy')`,`finally` 清理 DOM |
129
+ | 3. 通知 | 始终调用 `Message({ message: t('复制成功/失败'), theme: 'success/error' })` |
130
+
131
+ ## 实现源码
132
+
133
+ ```typescript
134
+ import { Message } from 'bkui-vue';
135
+ import { t } from '../lang/lang';
136
+
137
+ // 模块加载时检测一次,SSR 安全
138
+ const isClipboardApiSupported = typeof navigator !== 'undefined' && 'clipboard' in navigator;
139
+
140
+ const legacyCopy = (text: string): boolean => {
141
+ const textarea = document.createElement('textarea');
142
+ textarea.value = text;
143
+ textarea.style.cssText = 'position:fixed;left:-9999px;top:-9999px;opacity:0';
144
+ document.body.appendChild(textarea);
145
+ textarea.focus();
146
+ textarea.select();
147
+ try {
148
+ return document.execCommand('copy');
149
+ } catch {
150
+ return false;
151
+ } finally {
152
+ document.body.removeChild(textarea); // 无论成败都清理 DOM
153
+ }
154
+ };
155
+
156
+ export const useClipboard = () => {
157
+ const copy = async (text: string) => {
158
+ let success = false;
159
+ if (isClipboardApiSupported) {
160
+ try {
161
+ await navigator.clipboard.writeText(text);
162
+ success = true;
163
+ } catch {
164
+ success = legacyCopy(text); // Clipboard API 失败时降级
165
+ }
166
+ } else {
167
+ success = legacyCopy(text);
168
+ }
169
+ Message({
170
+ message: success ? t('复制成功') : t('复制失败'),
171
+ theme: success ? 'success' : 'error',
172
+ });
173
+ };
174
+
175
+ return { copy };
176
+ };
177
+ ```
178
+
179
+ ## 兼容性
180
+
181
+ | 策略 | 触发条件 | 要求 |
182
+ | ------------------------------- | --------------------------------------------- | ---------------------------------------------- |
183
+ | `navigator.clipboard.writeText` | 浏览器支持 Clipboard API | HTTPS 或 localhost;用户需授权剪贴板权限 |
184
+ | `document.execCommand('copy')` | 不支持 Clipboard API,或 `writeText` 抛出异常 | 需由用户事件(如 click)直接触发,否则可能失败 |
185
+
186
+ ## 内部使用场景
187
+
188
+ | 组件 | 用途 |
189
+ | ------------------ | ----------------------------------------- |
190
+ | `CodeContent` | 复制代码块,取 `codeRef.value?.innerText` |
191
+ | `MessageContainer` | 复制 AI 消息内容 |
192
+ | `UserMessage` | 复制用户消息内容 |
193
+
194
+ ## 注意事项
195
+
196
+ 1. **结果不对外暴露**:`copy` 返回 `Promise<void>`,调用方无法获知成功/失败;如需自定义通知,不应使用此 composable
197
+ 2. **通知使用 i18n**:消息文本通过 `t()` 函数处理,在多语言环境下自动切换,无法在外部覆盖
198
+ 3. **`isClipboardApiSupported` 模块级检测**:在文件 import 时执行一次(非响应式),SSR 环境下 `navigator` 为 `undefined` 时安全降级
199
+ 4. **`execCommand` 已废弃**:降级路径依赖 `document.execCommand('copy')`,该 API 在部分浏览器已标记废弃,但目前仍广泛可用
200
+ 5. **用户手势要求**:在没有用户交互上下文时(如定时器回调),两种方式都可能失败
201
+
202
+ ## 关联组件
203
+
204
+ - [CodeContent](../components/atomic/code-content.md) — 代码块复制
205
+ - [MessageContainer](../components/molecular/message-container.md) — 助手消息复制
206
+ - [UserMessage](../components/molecular/user-message.md) — 用户消息复制
@@ -0,0 +1,128 @@
1
+ <!-- AI SUMMARY -->
2
+ ## 快速了解
3
+
4
+ useCommandSelection 无参数,返回 commandSelection(ShallowRef 行列)与 GetCursorPosition(edix EditorCommand)。 在 editor.command(GetCursorPosition) 时把焦点端写入 commandSelection,供后续 DeleteTag、InsertTag 等命令计算范围。 仅在 AiSlashInput(@ 菜单插入)内部使用。
5
+
6
+ ### 关联组件
7
+ - **chat-input** — AiSlashInput 内部使用
8
+
9
+ ---
10
+ <!-- FULL DOC -->
11
+
12
+ # useCommandSelection 光标位置追踪
13
+
14
+ > **分类**:composable
15
+
16
+ 为 `edix` 富文本编辑器提供光标位置追踪能力的组合式函数。内部封装一个 `EditorCommand`,由编辑器调用后将光标的行列信息存入响应式变量,供后续编辑命令(如插入 tag、删除关键词)精确定位。
17
+
18
+ > 该 composable 仅在 `AiSlashInput` 内部使用,属于编辑器底层基础设施,**通常无需直接调用**。
19
+
20
+ ## 实现原理
21
+
22
+ ```
23
+ editor.command(GetCursorPosition)
24
+ │ edix 编辑器将 (doc, selection) 注入 EditorCommand
25
+
26
+ └── GetCursorPosition(doc, selection)
27
+ const [, focus] = selection // selection = [anchor, focus]
28
+ const [line, column] = focus // focus = [lineIndex, columnIndex]
29
+ commandSelection.value = { column, line }
30
+
31
+ commandSelection(shallowRef,初始值 { column: 0, line: 0 })
32
+ ```
33
+
34
+ **使用时机**:在 `@` 资源插入流程中,先执行 `GetCursorPosition` 快照当前光标,再据此计算删除范围和插入位置。
35
+
36
+ ## 概念演示
37
+
38
+ `commandSelection` 追踪编辑器光标的 `{ line, column }` 位置(行从 0 开始,column 为字符偏移量)。以下用原生 textarea 模拟等价的位置信息:
39
+
40
+ > 实际使用时,由 `editor.command(GetCursorPosition)` 触发写入,而非手动计算。
41
+
42
+ ## 在 AiSlashInput 中的实际用法
43
+
44
+ ```typescript
45
+ const { commandSelection, GetCursorPosition } = useCommandSelection();
46
+
47
+ // 用户从 @xxx 菜单中选择资源时:
48
+ const insertTagAtCursor = (tag: IAiSlashMenuItem) => {
49
+ // 1. 执行命令,快照当前光标位置 → 写入 commandSelection.value
50
+ editor.command(GetCursorPosition);
51
+
52
+ // 2. 读取快照的行列位置
53
+ const { column, line } = commandSelection.value;
54
+
55
+ // 3. 根据位置删除已输入的 "@keyword",插入 tag
56
+ editor.command(DeleteTag, [line, column - keyword.value.length - 1], [line, column]);
57
+ editor.command(InsertTag, [line, column], tag);
58
+ };
59
+ ```
60
+
61
+ ## API
62
+
63
+ ### 参数
64
+
65
+ 无。`useCommandSelection()` 不接受任何参数。
66
+
67
+ ### 返回值
68
+
69
+ | 属性名 | 类型 | 初始值 | 说明 |
70
+ | ------------------- | ---------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------ |
71
+ | `commandSelection` | `ShallowRef<{ column: number; line: number }>` | `{ column: 0, line: 0 }` | 存储最近一次执行 `GetCursorPosition` 时的光标行列位置 |
72
+ | `GetCursorPosition` | `EditorCommand<[]>` | — | 编辑器命令,由 `editor.command(GetCursorPosition)` 触发,将当前光标写入 `commandSelection` |
73
+
74
+ ## 类型说明
75
+
76
+ ```typescript
77
+ // EditorCommand:edix 编辑器的命令签名
78
+ type EditorCommand<A extends unknown[]> = (
79
+ doc: DocFragment,
80
+ selection: SelectionSnapshot, // [[anchorLine, anchorColumn], [focusLine, focusColumn]]
81
+ ...args: A
82
+ ) => Transaction | void;
83
+
84
+ // useCommandSelection 返回值
85
+ interface UseCommandSelectionReturn {
86
+ commandSelection: ShallowRef<{ column: number; line: number }>;
87
+ GetCursorPosition: EditorCommand<[]>;
88
+ }
89
+ ```
90
+
91
+ ## 实现源码
92
+
93
+ ```typescript
94
+ import { shallowRef } from 'vue';
95
+ import type { EditorCommand } from '../edix';
96
+
97
+ export const useCommandSelection = () => {
98
+ // 存储最近一次快照的光标位置
99
+ const commandSelection = shallowRef<{ column: number; line: number }>({
100
+ column: 0,
101
+ line: 0,
102
+ });
103
+
104
+ // EditorCommand:由 editor.command() 调用,注入 doc 和 selection
105
+ const GetCursorPosition: EditorCommand<[]> = (_doc, selection) => {
106
+ const [, focus] = selection; // 取 focus 端(忽略 anchor)
107
+ const [line, column] = focus;
108
+ commandSelection.value = { column, line };
109
+ // 不返回 Transaction,即只读取不修改文档
110
+ };
111
+
112
+ return {
113
+ commandSelection,
114
+ GetCursorPosition,
115
+ };
116
+ };
117
+ ```
118
+
119
+ ## 注意事项
120
+
121
+ 1. **只读命令**:`GetCursorPosition` 不返回 `Transaction`,不修改编辑器文档内容,仅记录位置
122
+ 2. **异步快照**:`commandSelection` 在 `editor.command(GetCursorPosition)` 执行后**同步**更新,下一行代码即可安全读取
123
+ 3. **`shallowRef` 而非 `ref`**:对象引用替换触发响应式,内部字段修改不触发(此处每次整体替换,无影响)
124
+ 4. **仅适用于 edix 编辑器**:`GetCursorPosition` 依赖 edix 的 `SelectionSnapshot` 格式,不适用于原生 contenteditable 或其他富文本库
125
+
126
+ ## 关联组件
127
+
128
+ - [ChatInput](../components/molecular/chat-input.md) — AiSlashInput 子模块使用
@@ -0,0 +1,56 @@
1
+ <!-- AI SUMMARY -->
2
+ ## 快速了解
3
+
4
+ useContainerScrollProvider 在滚动容器与底部锚点上绑定 IntersectionObserver、scroll、wheel,提供 isScrollBottom、scrollBottomHeight、autoScrollEnabled、toScrollBottom/toScrollTop 及防抖「返回底部」按钮状态。 useContainerScrollConsumer 通过 inject 在子组件中获取同一套控制,无需 props 透传。 典型用于流式输出时仅在用户位于底部时自动滚底。MessageContainer 与 ScrollBtn 配合使用。
5
+
6
+ ### 关联组件
7
+ - **message-container** — Provider 挂载于消息列表滚动区域
8
+ - **scroll-btn** — 使用 debouncedShowScrollBottomBtn 与 toScrollBottom
9
+ - **chat-container** — 组合消息区与输入区时的整体布局上下文
10
+
11
+ ---
12
+ <!-- FULL DOC -->
13
+
14
+ # useContainerScroll 容器滚动
15
+
16
+ > **分类**:composable
17
+
18
+ 为消息容器提供滚动控制的组合式函数对,通过 **Provider/Consumer** 模式在父子组件间共享滚动状态。
19
+
20
+ - `useContainerScrollProvider`:在容器组件中调用,创建滚动控制并通过 `provide` 向下共享
21
+ - `useContainerScrollConsumer`:在任意后代组件中调用,通过 `inject` 获取滚动控制
22
+
23
+ ## 工作原理
24
+
25
+ ```
26
+ useContainerScrollProvider(containerRef, bottomRef)
27
+
28
+ ├── IntersectionObserver 监听 bottomRef
29
+ │ 可见 → isScrollBottom=true, scrollBottomHeight=0, autoScrollEnabled=true
30
+ │ 不可见 → isScrollBottom=false, calculateScrollBottom()
31
+
32
+ ├── scroll 事件(passive)→ calculateScrollBottom()
33
+ │ scrollBottomHeight = max(0, scrollHeight - scrollTop - clientHeight)
34
+
35
+ ├── wheel 事件(passive)→ deltaY < 0 时 autoScrollEnabled=false
36
+ │ (用户向上滚动时暂停自动滚动)
37
+
38
+ ├── toScrollBottom() → autoScrollEnabled=true + bottomRef.scrollIntoView('smooth')
39
+ ├── toScrollTop() → containerRef.scrollTo({ top:0, behavior:'smooth' })
40
+
41
+ └── provide(CONTAINER_SCROLL_TOKEN, computed(() => ({
42
+ autoScrollEnabled: autoScrollEnabled.value, // 解包为 boolean
43
+ isScrollBottom, // ShallowRef<boolean>(保持响应式)
44
+ scrollBottomHeight, // ShallowRef<number>(保持响应式)
45
+ debouncedShowScrollBottomBtn, // customRef,防抖显示返回底部按钮
46
+ toScrollBottom,
47
+ toScrollTop,
48
+ })))
49
+
50
+ useContainerScrollConsumer()
51
+ └── inject(CONTAINER_SCROLL_TOKEN) → ComputedRef<ContainerScrollData> | undefined
52
+ ```
53
+
54
+ > **注意**:Consumer 获得的 `ComputedRef` 中,`isScrollBottom` 和 `scrollBottomHeight` 是 `ShallowRef` 对象(非解包值),需要通过 `.value` 访问。
55
+
56
+ ## 渲染示例