@huyooo/ai-chat-frontend-vue 0.1.6 → 0.1.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 (159) hide show
  1. package/README.md +367 -0
  2. package/dist/adapter.d.ts +7 -7
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/components/ChatPanel.vue.d.ts +120 -9
  5. package/dist/components/ChatPanel.vue.d.ts.map +1 -1
  6. package/dist/components/common/ConfirmDialog.vue.d.ts +30 -0
  7. package/dist/components/common/ConfirmDialog.vue.d.ts.map +1 -0
  8. package/dist/components/common/CopyButton.vue.d.ts +18 -0
  9. package/dist/components/common/CopyButton.vue.d.ts.map +1 -0
  10. package/dist/components/common/IndexingSettings.vue.d.ts +3 -0
  11. package/dist/components/common/IndexingSettings.vue.d.ts.map +1 -0
  12. package/dist/components/common/SettingsPanel.vue.d.ts +16 -0
  13. package/dist/components/common/SettingsPanel.vue.d.ts.map +1 -0
  14. package/dist/components/common/Toast.vue.d.ts +18 -0
  15. package/dist/components/common/Toast.vue.d.ts.map +1 -0
  16. package/dist/components/common/ToggleSwitch.vue.d.ts +10 -0
  17. package/dist/components/common/ToggleSwitch.vue.d.ts.map +1 -0
  18. package/dist/components/{chat/ui → header}/ChatHeader.vue.d.ts +5 -3
  19. package/dist/components/header/ChatHeader.vue.d.ts.map +1 -0
  20. package/dist/components/input/AtFilePicker.vue.d.ts +21 -0
  21. package/dist/components/input/AtFilePicker.vue.d.ts.map +1 -0
  22. package/dist/components/{ChatInput.vue.d.ts → input/ChatInput.vue.d.ts} +16 -14
  23. package/dist/components/input/ChatInput.vue.d.ts.map +1 -0
  24. package/dist/components/input/DropdownSelector.vue.d.ts +42 -0
  25. package/dist/components/input/DropdownSelector.vue.d.ts.map +1 -0
  26. package/dist/components/input/ImagePreviewModal.vue.d.ts +17 -0
  27. package/dist/components/input/ImagePreviewModal.vue.d.ts.map +1 -0
  28. package/dist/components/input/at-views/AtBranchView.vue.d.ts +18 -0
  29. package/dist/components/input/at-views/AtBranchView.vue.d.ts.map +1 -0
  30. package/dist/components/input/at-views/AtBrowserView.vue.d.ts +18 -0
  31. package/dist/components/input/at-views/AtBrowserView.vue.d.ts.map +1 -0
  32. package/dist/components/input/at-views/AtChatsView.vue.d.ts +18 -0
  33. package/dist/components/input/at-views/AtChatsView.vue.d.ts.map +1 -0
  34. package/dist/components/input/at-views/AtDocsView.vue.d.ts +18 -0
  35. package/dist/components/input/at-views/AtDocsView.vue.d.ts.map +1 -0
  36. package/dist/components/input/at-views/AtFilesView.vue.d.ts +23 -0
  37. package/dist/components/input/at-views/AtFilesView.vue.d.ts.map +1 -0
  38. package/dist/components/input/at-views/AtTerminalsView.vue.d.ts +18 -0
  39. package/dist/components/input/at-views/AtTerminalsView.vue.d.ts.map +1 -0
  40. package/dist/components/message/MessageBubble.vue.d.ts +45 -0
  41. package/dist/components/message/MessageBubble.vue.d.ts.map +1 -0
  42. package/dist/components/message/PartsRenderer.vue.d.ts +15 -0
  43. package/dist/components/message/PartsRenderer.vue.d.ts.map +1 -0
  44. package/dist/components/message/WelcomeMessage.vue.d.ts +14 -0
  45. package/dist/components/message/WelcomeMessage.vue.d.ts.map +1 -0
  46. package/dist/components/message/blocks/CodeBlock.vue.d.ts +11 -0
  47. package/dist/components/message/blocks/CodeBlock.vue.d.ts.map +1 -0
  48. package/dist/components/{chat/SearchResultBlock.vue.d.ts → message/blocks/TextBlock.vue.d.ts} +3 -4
  49. package/dist/components/message/blocks/TextBlock.vue.d.ts.map +1 -0
  50. package/dist/components/message/blocks/index.d.ts +6 -0
  51. package/dist/components/message/blocks/index.d.ts.map +1 -0
  52. package/dist/components/message/parts/CollapsibleCard.vue.d.ts +45 -0
  53. package/dist/components/message/parts/CollapsibleCard.vue.d.ts.map +1 -0
  54. package/dist/components/{chat/ToolCallBlock.vue.d.ts → message/parts/ErrorPart.vue.d.ts} +4 -5
  55. package/dist/components/message/parts/ErrorPart.vue.d.ts.map +1 -0
  56. package/dist/components/{chat/ThinkingBlock.vue.d.ts → message/parts/ImagePart.vue.d.ts} +3 -3
  57. package/dist/components/message/parts/ImagePart.vue.d.ts.map +1 -0
  58. package/dist/components/message/parts/SearchPart.vue.d.ts +12 -0
  59. package/dist/components/message/parts/SearchPart.vue.d.ts.map +1 -0
  60. package/dist/components/{chat/messages/ExecutionSteps.vue.d.ts → message/parts/TextPart.vue.d.ts} +2 -9
  61. package/dist/components/message/parts/TextPart.vue.d.ts.map +1 -0
  62. package/dist/components/message/parts/ThinkingPart.vue.d.ts +12 -0
  63. package/dist/components/message/parts/ThinkingPart.vue.d.ts.map +1 -0
  64. package/dist/components/message/parts/ToolCallPart.vue.d.ts +19 -0
  65. package/dist/components/message/parts/ToolCallPart.vue.d.ts.map +1 -0
  66. package/dist/components/message/parts/ToolResultPart.vue.d.ts +14 -0
  67. package/dist/components/message/parts/ToolResultPart.vue.d.ts.map +1 -0
  68. package/dist/components/message/parts/index.d.ts +12 -0
  69. package/dist/components/message/parts/index.d.ts.map +1 -0
  70. package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts +4 -0
  71. package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts.map +1 -0
  72. package/dist/components/message/tool-results/SearchResults.vue.d.ts +4 -0
  73. package/dist/components/message/tool-results/SearchResults.vue.d.ts.map +1 -0
  74. package/dist/components/message/tool-results/WeatherCard.vue.d.ts +4 -0
  75. package/dist/components/message/tool-results/WeatherCard.vue.d.ts.map +1 -0
  76. package/dist/components/message/tool-results/index.d.ts +7 -0
  77. package/dist/components/message/tool-results/index.d.ts.map +1 -0
  78. package/dist/components/message/welcome-types.d.ts +28 -0
  79. package/dist/components/message/welcome-types.d.ts.map +1 -0
  80. package/dist/composables/useChat.d.ts +99 -44
  81. package/dist/composables/useChat.d.ts.map +1 -1
  82. package/dist/composables/useImageUpload.d.ts +55 -0
  83. package/dist/composables/useImageUpload.d.ts.map +1 -0
  84. package/dist/index.d.ts +25 -26
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +55871 -1252
  87. package/dist/style.css +1 -1
  88. package/dist/types/index.d.ts +113 -53
  89. package/dist/types/index.d.ts.map +1 -1
  90. package/dist/utils/fileIcon.d.ts +13 -0
  91. package/dist/utils/fileIcon.d.ts.map +1 -0
  92. package/package.json +12 -6
  93. package/src/adapter.ts +12 -70
  94. package/src/components/ChatPanel.vue +329 -110
  95. package/src/components/common/ConfirmDialog.vue +208 -0
  96. package/src/components/common/CopyButton.vue +71 -0
  97. package/src/components/common/IndexingSettings.vue +580 -0
  98. package/src/components/common/SettingsPanel.vue +293 -0
  99. package/src/components/common/Toast.vue +90 -0
  100. package/src/components/common/ToggleSwitch.vue +75 -0
  101. package/src/components/{chat/ui → header}/ChatHeader.vue +170 -93
  102. package/src/components/input/AtFilePicker.vue +657 -0
  103. package/src/components/input/ChatInput.vue +653 -0
  104. package/src/components/input/DropdownSelector.vue +322 -0
  105. package/src/components/input/ImagePreviewModal.vue +238 -0
  106. package/src/components/input/at-views/AtBranchView.vue +63 -0
  107. package/src/components/input/at-views/AtBrowserView.vue +63 -0
  108. package/src/components/input/at-views/AtChatsView.vue +63 -0
  109. package/src/components/input/at-views/AtDocsView.vue +63 -0
  110. package/src/components/input/at-views/AtFilesView.vue +255 -0
  111. package/src/components/input/at-views/AtTerminalsView.vue +63 -0
  112. package/src/components/message/ContentRenderer.vue +61 -0
  113. package/src/components/message/MessageBubble.vue +411 -0
  114. package/src/components/message/PartsRenderer.vue +101 -0
  115. package/src/components/message/ToolResultRenderer.vue +27 -0
  116. package/src/components/message/WelcomeMessage.vue +308 -0
  117. package/src/components/message/blocks/CodeBlock.vue +113 -0
  118. package/src/components/message/blocks/TextBlock.vue +21 -0
  119. package/src/components/message/blocks/index.ts +6 -0
  120. package/src/components/message/parts/CollapsibleCard.vue +135 -0
  121. package/src/components/message/parts/ErrorPart.vue +51 -0
  122. package/src/components/message/parts/ImagePart.vue +98 -0
  123. package/src/components/message/parts/SearchPart.vue +101 -0
  124. package/src/components/message/parts/TextPart.vue +28 -0
  125. package/src/components/message/parts/ThinkingPart.vue +54 -0
  126. package/src/components/message/parts/ToolCallPart.vue +460 -0
  127. package/src/components/message/parts/ToolResultPart.vue +78 -0
  128. package/src/components/message/parts/index.ts +13 -0
  129. package/src/components/message/tool-results/DefaultToolResult.vue +43 -0
  130. package/src/components/message/tool-results/SearchResults.vue +133 -0
  131. package/src/components/message/tool-results/WeatherCard.vue +139 -0
  132. package/src/components/message/tool-results/index.ts +7 -0
  133. package/src/components/message/welcome-types.ts +47 -0
  134. package/src/composables/useChat.ts +807 -155
  135. package/src/composables/useImageUpload.ts +228 -0
  136. package/src/index.ts +93 -46
  137. package/src/styles.css +47 -0
  138. package/src/types/index.ts +146 -98
  139. package/src/utils/fileIcon.ts +49 -0
  140. package/dist/components/ChatInput.vue.d.ts.map +0 -1
  141. package/dist/components/chat/SearchResultBlock.vue.d.ts.map +0 -1
  142. package/dist/components/chat/ThinkingBlock.vue.d.ts.map +0 -1
  143. package/dist/components/chat/ToolCallBlock.vue.d.ts.map +0 -1
  144. package/dist/components/chat/messages/ExecutionSteps.vue.d.ts.map +0 -1
  145. package/dist/components/chat/messages/MessageBubble.vue.d.ts +0 -28
  146. package/dist/components/chat/messages/MessageBubble.vue.d.ts.map +0 -1
  147. package/dist/components/chat/ui/ChatHeader.vue.d.ts.map +0 -1
  148. package/dist/components/chat/ui/WelcomeMessage.vue.d.ts +0 -7
  149. package/dist/components/chat/ui/WelcomeMessage.vue.d.ts.map +0 -1
  150. package/dist/preload/preload.d.ts +0 -6
  151. package/dist/preload/preload.d.ts.map +0 -1
  152. package/src/components/ChatInput.vue +0 -649
  153. package/src/components/chat/SearchResultBlock.vue +0 -155
  154. package/src/components/chat/ThinkingBlock.vue +0 -109
  155. package/src/components/chat/ToolCallBlock.vue +0 -213
  156. package/src/components/chat/messages/ExecutionSteps.vue +0 -281
  157. package/src/components/chat/messages/MessageBubble.vue +0 -272
  158. package/src/components/chat/ui/WelcomeMessage.vue +0 -135
  159. package/src/preload/preload.ts +0 -79
@@ -0,0 +1,460 @@
1
+ <template>
2
+ <CollapsibleCard
3
+ v-model="expanded"
4
+ :icon="statusIcon"
5
+ :icon-color="statusIconColor"
6
+ :title="title"
7
+ :spinning="status === 'running'"
8
+ >
9
+ <template #header-actions>
10
+ <CopyButton :text="commandText" title="复制" />
11
+ </template>
12
+ <div class="tool-call-content">
13
+ <!-- 命令显示区域 -->
14
+ <div class="tool-call-command">
15
+ <div class="command-header">
16
+ <span class="command-label">执行:</span>
17
+ <span class="command-name">{{ commandDisplay }}</span>
18
+ </div>
19
+ <code class="command-code">{{ commandText }}</code>
20
+ </div>
21
+
22
+ <!-- 执行结果(如果有) -->
23
+ <div v-if="result !== null" class="tool-call-result">
24
+ <div class="result-header">
25
+ <span class="result-label">结果:</span>
26
+ <span class="result-name"></span>
27
+ </div>
28
+ <code class="result-content">{{ formattedResult }}</code>
29
+ </div>
30
+
31
+ <!-- 底部操作区域 -->
32
+ <div class="tool-call-footer">
33
+ <!-- 左侧:模式选择(始终可用,切换后影响后续操作) -->
34
+ <div class="footer-left">
35
+ <DropdownSelector
36
+ :value="currentMode"
37
+ :options="modeOptions"
38
+ @select="handleModeChange"
39
+ />
40
+ </div>
41
+
42
+ <!-- 右侧:执行状态或按钮 -->
43
+ <div class="footer-right">
44
+ <!-- pending 状态:显示跳过/运行按钮 -->
45
+ <div v-if="status === 'pending'" class="action-buttons">
46
+ <button class="btn btn-skip" @click="handleSkip">跳过</button>
47
+ <button class="btn btn-run" @click="handleRun">
48
+ 运行
49
+ <Icon icon="lucide:arrow-right" width="14" />
50
+ </button>
51
+ </div>
52
+ <!-- running 状态:显示运行中状态 + 取消按钮 -->
53
+ <template v-else-if="status === 'running'">
54
+ <div class="execution-status running">
55
+ <Icon icon="lucide:loader-2" width="14" class="spinning" />
56
+ <span>运行中</span>
57
+ </div>
58
+ <button class="btn btn-cancel" @click="handleCancel">
59
+ <Icon icon="lucide:x" width="14" />
60
+ 取消
61
+ </button>
62
+ </template>
63
+ <!-- done/error 状态:只显示状态 -->
64
+ <div v-else :class="['execution-status', status]">
65
+ <Icon :icon="statusDisplayIcon" width="14" />
66
+ <span>{{ statusText }}</span>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </CollapsibleCard>
72
+ </template>
73
+
74
+ <script setup lang="ts">
75
+ import { ref, computed, watch } from 'vue'
76
+ import { Icon } from '@iconify/vue'
77
+ import DropdownSelector from '../../input/DropdownSelector.vue'
78
+ import CollapsibleCard from './CollapsibleCard.vue'
79
+ import CopyButton from '../../common/CopyButton.vue'
80
+ import type { ChatAdapter } from '../../../adapter'
81
+ import type { AutoRunConfig, AutoRunMode } from '@huyooo/ai-chat-bridge-electron/renderer'
82
+ import type { StepsExpandedType } from '../../../types'
83
+
84
+ interface Props {
85
+ id: string
86
+ name: string
87
+ args?: Record<string, unknown>
88
+ status: 'pending' | 'running' | 'done' | 'error' | 'cancelled' | 'skipped'
89
+ result: unknown | null
90
+ expandedType?: StepsExpandedType
91
+ // 通过 props 传递,不使用 inject
92
+ adapter?: ChatAdapter
93
+ autoRunConfig?: AutoRunConfig
94
+ onSaveConfig?: (config: AutoRunConfig) => Promise<void>
95
+ }
96
+
97
+ const props = withDefaults(defineProps<Props>(), {
98
+ expandedType: 'auto',
99
+ })
100
+
101
+ // 模式选项
102
+ const modeOptions = [
103
+ { value: 'manual', label: '执行前询问我' },
104
+ { value: 'run-everything', label: '自动执行' },
105
+ ]
106
+
107
+ // 当前模式:直接从配置读取
108
+ const currentMode = computed(() => props.autoRunConfig?.mode ?? 'run-everything')
109
+
110
+ // 命令显示
111
+ const commandDisplay = computed(() => {
112
+ if (props.name === 'execute_command' && props.args?.command) {
113
+ const cmd = String(props.args.command)
114
+ const parts = cmd.trim().split(/\s+/)
115
+ return parts.slice(0, 3).join(' ') + (parts.length > 3 ? '...' : '')
116
+ }
117
+ return props.name
118
+ })
119
+
120
+ const commandText = computed(() => {
121
+ if (props.name === 'execute_command' && props.args?.command) {
122
+ return String(props.args.command)
123
+ }
124
+ if (props.args && Object.keys(props.args).length > 0) {
125
+ try {
126
+ return JSON.stringify(props.args, null, 2)
127
+ } catch {
128
+ return String(props.args)
129
+ }
130
+ }
131
+ return props.name
132
+ })
133
+
134
+ // 格式化执行结果
135
+ const formattedResult = computed(() => {
136
+ if (props.result === null) return ''
137
+ if (typeof props.result === 'string') return props.result
138
+ try {
139
+ return JSON.stringify(props.result, null, 2)
140
+ } catch {
141
+ return String(props.result)
142
+ }
143
+ })
144
+
145
+ // 折叠状态
146
+ function getInitialExpanded(): boolean {
147
+ if (props.expandedType === 'open') return true
148
+ if (props.expandedType === 'close') return false
149
+ return props.status === 'pending' || props.status === 'running'
150
+ }
151
+
152
+ const expanded = ref(getInitialExpanded())
153
+
154
+ // auto 模式下:状态变化时自动折叠/展开
155
+ watch(() => props.status, (newStatus) => {
156
+ if (props.expandedType === 'auto') {
157
+ expanded.value = newStatus === 'pending' || newStatus === 'running'
158
+ }
159
+ })
160
+
161
+ // 状态相关计算属性
162
+ const statusIcon = computed(() => {
163
+ const icons: Record<string, string> = {
164
+ error: 'lucide:x-circle',
165
+ done: 'lucide:check-circle',
166
+ running: 'lucide:loader-2',
167
+ pending: 'lucide:clock',
168
+ cancelled: 'lucide:ban',
169
+ skipped: 'lucide:skip-forward',
170
+ }
171
+ return icons[props.status] ?? 'lucide:clock'
172
+ })
173
+
174
+ const statusIconColor = computed(() => {
175
+ const colors: Record<string, string> = {
176
+ error: 'var(--chat-error, #ef4444)',
177
+ done: 'var(--chat-success, #22c55e)',
178
+ running: 'var(--chat-accent, #3b82f6)',
179
+ pending: 'var(--chat-text-muted, #888)',
180
+ cancelled: 'var(--chat-warning, #f59e0b)',
181
+ skipped: 'var(--chat-text-muted, #888)',
182
+ }
183
+ return colors[props.status] ?? 'var(--chat-text-muted, #888)'
184
+ })
185
+
186
+ const statusDisplayIcon = computed(() => {
187
+ const icons: Record<string, string> = {
188
+ done: 'lucide:check-circle-2',
189
+ error: 'lucide:x-circle',
190
+ running: 'lucide:loader-2',
191
+ pending: 'lucide:clock',
192
+ cancelled: 'lucide:ban',
193
+ skipped: 'lucide:skip-forward',
194
+ }
195
+ return icons[props.status] ?? 'lucide:clock'
196
+ })
197
+
198
+ const statusText = computed(() => {
199
+ const texts: Record<string, string> = {
200
+ done: '成功',
201
+ error: '错误',
202
+ running: '运行中',
203
+ pending: '等待中',
204
+ cancelled: '已取消',
205
+ skipped: '已跳过',
206
+ }
207
+ return texts[props.status] ?? ''
208
+ })
209
+
210
+ const title = computed(() => {
211
+ const suffixes: Record<string, string> = {
212
+ pending: ' - 等待确认',
213
+ running: ' - 执行中...',
214
+ error: ' - 执行失败',
215
+ done: ' - 执行完成',
216
+ cancelled: ' - 已取消',
217
+ skipped: ' - 已跳过',
218
+ }
219
+ return props.name + (suffixes[props.status] ?? '')
220
+ })
221
+
222
+ /** 处理模式变化 */
223
+ async function handleModeChange(value: string) {
224
+ if (!props.onSaveConfig || !props.autoRunConfig) return
225
+
226
+ try {
227
+ // 直接修改引用类型的属性
228
+ props.autoRunConfig.mode = value as AutoRunMode
229
+ // 保存到数据库
230
+ await props.onSaveConfig(props.autoRunConfig)
231
+ } catch (error) {
232
+ console.error('[ToolCallPart] 保存配置失败:', error)
233
+ }
234
+ }
235
+
236
+ /** 跳过执行:只拒绝当前工具,AI 继续思考 */
237
+ async function handleSkip() {
238
+ if (!props.adapter?.respondToolApproval) return
239
+
240
+ try {
241
+ // 只拒绝工具执行,不取消整个请求
242
+ // AI 会收到"用户跳过了此工具"的反馈,然后可能:
243
+ // 1. 尝试其他工具/方法
244
+ // 2. 直接回复用户
245
+ // 3. 询问用户想要什么
246
+ await props.adapter.respondToolApproval(props.id, false)
247
+ } catch (error) {
248
+ console.error('[ToolCallPart] 跳过失败:', error)
249
+ }
250
+ }
251
+
252
+ /** 运行执行 */
253
+ async function handleRun() {
254
+ if (!props.adapter?.respondToolApproval) return
255
+
256
+ try {
257
+ await props.adapter.respondToolApproval(props.id, true)
258
+ } catch (error) {
259
+ console.error('[ToolCallPart] 运行失败:', error)
260
+ }
261
+ }
262
+
263
+ /** 取消执行 */
264
+ function handleCancel() {
265
+ props.adapter?.cancel?.()
266
+ }
267
+
268
+ // 监听模式变化:从 manual 切换到 run-everything 时,自动执行 pending 的工具
269
+ watch(currentMode, (newMode, oldMode) => {
270
+ if (oldMode === 'manual' && newMode === 'run-everything' && props.status === 'pending') {
271
+ handleRun()
272
+ }
273
+ })
274
+
275
+ </script>
276
+
277
+ <style scoped>
278
+ .tool-call-content {
279
+ display: flex;
280
+ flex-direction: column;
281
+ }
282
+
283
+ .tool-call-command {
284
+ margin-bottom: 12px;
285
+ }
286
+
287
+ .command-header,
288
+ .result-header {
289
+ display: block;
290
+ margin-bottom: 8px;
291
+ padding: 0;
292
+ cursor: default;
293
+ user-select: none;
294
+ font-size: 12px;
295
+ line-height: 1.5;
296
+ }
297
+
298
+ .command-header:hover,
299
+ .result-header:hover,
300
+ .command-label:hover,
301
+ .result-label:hover,
302
+ .command-name:hover,
303
+ .result-name:hover {
304
+ background: transparent !important;
305
+ }
306
+
307
+ .command-code {
308
+ display: block;
309
+ padding: 10px;
310
+ background: rgba(0, 0, 0, 0.3);
311
+ border: 1px solid var(--chat-border, #333);
312
+ border-radius: 6px;
313
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
314
+ font-size: 13px;
315
+ color: var(--chat-text, #ccc);
316
+ white-space: pre-wrap;
317
+ word-break: break-all;
318
+ overflow-x: auto;
319
+ }
320
+
321
+ .tool-call-result {
322
+ margin-bottom: 12px;
323
+ }
324
+
325
+ .result-label,
326
+ .command-label {
327
+ color: var(--chat-text-muted, #888);
328
+ font-weight: 500;
329
+ }
330
+
331
+ .result-name,
332
+ .command-name {
333
+ color: var(--chat-text, #ccc);
334
+ }
335
+
336
+ .result-content {
337
+ display: block;
338
+ padding: 10px;
339
+ background: rgba(0, 0, 0, 0.3);
340
+ border: 1px solid var(--chat-border, #333);
341
+ border-radius: 6px;
342
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
343
+ font-size: 13px;
344
+ color: var(--chat-text, #ccc);
345
+ white-space: pre-wrap;
346
+ word-break: break-all;
347
+ overflow-x: auto;
348
+ }
349
+
350
+ .tool-call-footer {
351
+ display: flex;
352
+ align-items: center;
353
+ justify-content: space-between;
354
+ gap: 12px;
355
+ padding-top: 12px;
356
+ border-top: 1px solid var(--chat-border, #333);
357
+ }
358
+
359
+ .footer-left {
360
+ flex-shrink: 0;
361
+ position: relative;
362
+ z-index: 10;
363
+ }
364
+
365
+ .footer-right {
366
+ display: flex;
367
+ align-items: center;
368
+ gap: 12px;
369
+ }
370
+
371
+ .execution-status {
372
+ display: flex;
373
+ align-items: center;
374
+ gap: 6px;
375
+ font-size: 12px;
376
+ font-weight: 500;
377
+ }
378
+
379
+ .execution-status.done {
380
+ color: var(--chat-success, #22c55e);
381
+ }
382
+
383
+ .execution-status.error {
384
+ color: #ef4444;
385
+ }
386
+
387
+ .execution-status.running {
388
+ color: var(--chat-accent, #3b82f6);
389
+ }
390
+
391
+ .execution-status.pending {
392
+ color: var(--chat-text-muted, #888);
393
+ }
394
+
395
+ .execution-status.cancelled {
396
+ color: var(--chat-warning, #f59e0b);
397
+ }
398
+
399
+ .execution-status.skipped {
400
+ color: var(--chat-text-muted, #888);
401
+ }
402
+
403
+ .spinning {
404
+ animation: spin 1s linear infinite;
405
+ }
406
+
407
+ @keyframes spin {
408
+ from { transform: rotate(0deg); }
409
+ to { transform: rotate(360deg); }
410
+ }
411
+
412
+ .action-buttons {
413
+ display: flex;
414
+ gap: 8px;
415
+ }
416
+
417
+ .btn {
418
+ padding: 6px 12px;
419
+ border-radius: 6px;
420
+ font-size: 12px;
421
+ font-weight: 500;
422
+ cursor: pointer;
423
+ border: none;
424
+ display: flex;
425
+ align-items: center;
426
+ gap: 6px;
427
+ transition: all 0.2s;
428
+ }
429
+
430
+ .btn-skip {
431
+ background: var(--chat-muted, #2a2a2a);
432
+ color: var(--chat-text, #ccc);
433
+ border: 1px solid var(--chat-border, #333);
434
+ }
435
+
436
+ .btn-skip:hover {
437
+ background: var(--chat-muted-hover, #333);
438
+ }
439
+
440
+ .btn-run {
441
+ background: var(--chat-accent, #3b82f6);
442
+ color: #fff;
443
+ }
444
+
445
+ .btn-run:hover {
446
+ background: var(--chat-accent-hover, #2563eb);
447
+ }
448
+
449
+ .btn-cancel {
450
+ background: transparent;
451
+ color: var(--chat-text-muted, #888);
452
+ border: 1px solid var(--chat-border, #333);
453
+ }
454
+
455
+ .btn-cancel:hover {
456
+ background: rgba(239, 68, 68, 0.1);
457
+ color: #ef4444;
458
+ border-color: #ef4444;
459
+ }
460
+ </style>
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <div class="tool-result-part">
3
+ <!-- 自定义渲染器 -->
4
+ <component
5
+ v-if="customRenderer"
6
+ :is="customRenderer"
7
+ :tool-name="name"
8
+ :tool-args="args || {}"
9
+ :tool-result="result"
10
+ :status="status"
11
+ />
12
+ <!-- 默认渲染:可折叠的 JSON -->
13
+ <CollapsibleCard
14
+ v-else
15
+ v-model="expanded"
16
+ :icon="status === 'error' ? 'lucide:x-circle' : 'lucide:check-circle'"
17
+ :icon-color="status === 'error' ? 'var(--chat-error, #ef4444)' : 'var(--chat-success, #22c55e)'"
18
+ :title="name"
19
+ title-color="var(--chat-text, #ccc)"
20
+ >
21
+ <pre class="result-content">{{ formattedResult }}</pre>
22
+ </CollapsibleCard>
23
+ </div>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import { ref, computed, inject, type Component } from 'vue'
28
+ import CollapsibleCard from './CollapsibleCard.vue'
29
+ import type { StepsExpandedType } from '../../../types'
30
+
31
+ const props = withDefaults(defineProps<{
32
+ id: string
33
+ name: string
34
+ args?: Record<string, unknown>
35
+ result: unknown
36
+ status: 'done' | 'error' | 'cancelled' | 'skipped'
37
+ expandedType?: StepsExpandedType
38
+ }>(), {
39
+ expandedType: 'auto'
40
+ })
41
+
42
+ // 注入自定义渲染器配置
43
+ const toolRenderers = inject<Record<string, Component>>('toolRenderers', {})
44
+
45
+ // 获取自定义渲染器
46
+ const customRenderer = computed(() => toolRenderers[props.name])
47
+
48
+ // 根据模式计算初始状态(工具结果完成后默认折叠,除非 mode 是 open)
49
+ function getInitialExpanded(): boolean {
50
+ if (props.expandedType === 'open') return true
51
+ return false
52
+ }
53
+
54
+ const expanded = ref(getInitialExpanded())
55
+
56
+ // 格式化结果
57
+ const formattedResult = computed(() => {
58
+ if (typeof props.result === 'string') {
59
+ return props.result
60
+ }
61
+ return JSON.stringify(props.result, null, 2)
62
+ })
63
+ </script>
64
+
65
+ <style scoped>
66
+ .tool-result-part {
67
+ margin: 8px 0;
68
+ }
69
+
70
+ .result-content {
71
+ margin: 0;
72
+ font-size: 12px;
73
+ font-family: "SF Mono", Monaco, "Cascadia Code", monospace;
74
+ color: var(--chat-text-muted, #999);
75
+ white-space: pre-wrap;
76
+ word-break: break-word;
77
+ }
78
+ </style>
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Part 渲染组件导出
3
+ */
4
+ export { default as TextPart } from './TextPart.vue'
5
+ export { default as ThinkingPart } from './ThinkingPart.vue'
6
+ export { default as SearchPart } from './SearchPart.vue'
7
+ export { default as ToolCallPart } from './ToolCallPart.vue'
8
+ export { default as ToolResultPart } from './ToolResultPart.vue'
9
+ export { default as ImagePart } from './ImagePart.vue'
10
+ export { default as ErrorPart } from './ErrorPart.vue'
11
+
12
+ // 公共组件
13
+ export { default as CollapsibleCard } from './CollapsibleCard.vue'
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <div class="default-tool-result">
3
+ <pre class="result-content">{{ formattedResult }}</pre>
4
+ </div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { computed } from 'vue'
9
+ import type { ToolRendererProps } from '@huyooo/ai-chat-shared'
10
+
11
+ const props = defineProps<ToolRendererProps>()
12
+
13
+ const formattedResult = computed(() => {
14
+ if (typeof props.toolResult === 'string') {
15
+ return props.toolResult
16
+ }
17
+ try {
18
+ return JSON.stringify(props.toolResult, null, 2)
19
+ } catch {
20
+ return String(props.toolResult)
21
+ }
22
+ })
23
+ </script>
24
+
25
+ <style scoped>
26
+ .default-tool-result {
27
+ background: var(--chat-bg, #1e1e1e);
28
+ border-radius: 6px;
29
+ overflow: hidden;
30
+ }
31
+
32
+ .result-content {
33
+ margin: 0;
34
+ padding: 8px;
35
+ font-size: 13px;
36
+ line-height: 1.5;
37
+ color: var(--chat-text-muted, #999);
38
+ font-family: 'SF Mono', Monaco, monospace;
39
+ white-space: pre-wrap;
40
+ word-break: break-word;
41
+ overflow-x: auto;
42
+ }
43
+ </style>