@huyooo/ai-chat-frontend-vue 0.2.13 → 0.2.14

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 (142) hide show
  1. package/README.md +85 -77
  2. package/dist/_baseUniq-Berj5otS.js +477 -0
  3. package/dist/arc-D4b-De5i.js +83 -0
  4. package/dist/architecture-U656AL7Q-B1R4o7wt.js +5 -0
  5. package/dist/architectureDiagram-VXUJARFQ-0H1LzSrf.js +4668 -0
  6. package/dist/blockDiagram-VD42YOAC-Dd6DMjLf.js +2265 -0
  7. package/dist/c4Diagram-YG6GDRKO-c6mpIonb.js +1581 -0
  8. package/dist/channel-CQSBI1fo.js +5 -0
  9. package/dist/chunk-4BX2VUAB-CfWC7lTG.js +9 -0
  10. package/dist/chunk-55IACEB6-joM8m2Zv.js +8 -0
  11. package/dist/chunk-B4BG7PRW-DN94Y3mS.js +1376 -0
  12. package/dist/chunk-DI55MBZ5-CPRGoFPG.js +1382 -0
  13. package/dist/chunk-FMBD7UC4-dUKl2TcN.js +19 -0
  14. package/dist/chunk-QN33PNHL-o3WmqquO.js +20 -0
  15. package/dist/chunk-QZHKN3VN-Btf62lRF.js +15 -0
  16. package/dist/chunk-TZMSLE5B-QCPpnm8Y.js +64 -0
  17. package/dist/classDiagram-2ON5EDUG-ERF9RE9K.js +16 -0
  18. package/dist/classDiagram-v2-WZHVMYZB-ERF9RE9K.js +16 -0
  19. package/dist/clone-B_B1yyxj.js +8 -0
  20. package/dist/components/ChatPanel.vue.d.ts +11 -7
  21. package/dist/components/ChatPanel.vue.d.ts.map +1 -1
  22. package/dist/components/common/SettingsPanel.vue.d.ts +10 -1
  23. package/dist/components/common/SettingsPanel.vue.d.ts.map +1 -1
  24. package/dist/components/common/ToggleSwitch.vue.d.ts.map +1 -1
  25. package/dist/components/input/ChatInput.vue.d.ts +4 -1
  26. package/dist/components/input/ChatInput.vue.d.ts.map +1 -1
  27. package/dist/components/input/DropdownSelector.vue.d.ts +10 -0
  28. package/dist/components/input/DropdownSelector.vue.d.ts.map +1 -1
  29. package/dist/components/message/MessageBubble.vue.d.ts +2 -2
  30. package/dist/components/message/MessageBubble.vue.d.ts.map +1 -1
  31. package/dist/components/message/PartsRenderer.vue.d.ts +6 -0
  32. package/dist/components/message/PartsRenderer.vue.d.ts.map +1 -1
  33. package/dist/components/message/parts/CollapsibleCard.vue.d.ts.map +1 -1
  34. package/dist/components/message/parts/ImagePart.vue.d.ts.map +1 -1
  35. package/dist/components/message/parts/SearchPart.vue.d.ts.map +1 -1
  36. package/dist/components/message/parts/TextPart.vue.d.ts +3 -1
  37. package/dist/components/message/parts/TextPart.vue.d.ts.map +1 -1
  38. package/dist/components/message/parts/ToolCallPart.vue.d.ts +9 -4
  39. package/dist/components/message/parts/ToolCallPart.vue.d.ts.map +1 -1
  40. package/dist/components/message/parts/index.d.ts +5 -1
  41. package/dist/components/message/parts/index.d.ts.map +1 -1
  42. package/dist/components/message/parts/visual-predicate.d.ts +7 -0
  43. package/dist/components/message/parts/visual-predicate.d.ts.map +1 -0
  44. package/dist/components/message/parts/visual-render.d.ts +2 -0
  45. package/dist/components/message/parts/visual-render.d.ts.map +1 -0
  46. package/dist/components/message/parts/visual.d.ts +3 -0
  47. package/dist/components/message/parts/visual.d.ts.map +1 -0
  48. package/dist/composables/useChat.d.ts +34 -10
  49. package/dist/composables/useChat.d.ts.map +1 -1
  50. package/dist/composables/useImageUpload.d.ts +7 -0
  51. package/dist/composables/useImageUpload.d.ts.map +1 -1
  52. package/dist/cose-bilkent-S5V4N54A-C-CkEnIH.js +2608 -0
  53. package/dist/cytoscape.esm-CjI2IsL8.js +18735 -0
  54. package/dist/dagre-6UL2VRFP-BbmZysl9.js +446 -0
  55. package/dist/defaultLocale-D7EN2tov.js +171 -0
  56. package/dist/diagram-PSM6KHXK-CMnope5e.js +533 -0
  57. package/dist/diagram-QEK2KX5R-Bc87skpo.js +221 -0
  58. package/dist/diagram-S2PKOQOG-BtkzC5-o.js +143 -0
  59. package/dist/erDiagram-Q2GNP2WA-CwQLdjh9.js +842 -0
  60. package/dist/flowDiagram-NV44I4VS-8ehMD0M4.js +1627 -0
  61. package/dist/ganttDiagram-JELNMOA3-dLX8Pbvy.js +2670 -0
  62. package/dist/gitGraph-F6HP7TQM-BBk5qfnx.js +5 -0
  63. package/dist/gitGraphDiagram-NY62KEGX-DG_fvWrq.js +712 -0
  64. package/dist/graph-B7BnGhuR.js +381 -0
  65. package/dist/index-Cr3tTH72.js +83883 -0
  66. package/dist/index.d.ts +7 -7
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +19 -56433
  69. package/dist/info-NVLQJR56-eZr6nVr1.js +5 -0
  70. package/dist/infoDiagram-WHAUD3N6-BcVYTft_.js +24 -0
  71. package/dist/init-DjUOC4st.js +16 -0
  72. package/dist/journeyDiagram-XKPGCS4Q-D4C72gYB.js +834 -0
  73. package/dist/kanban-definition-3W4ZIXB7-BuSjAlm6.js +721 -0
  74. package/dist/layout-LjYblgUu.js +1441 -0
  75. package/dist/linear-DtPqj295.js +259 -0
  76. package/dist/mermaid-parser.core-DdOZxjrw.js +15189 -0
  77. package/dist/min-DYbgfxu0.js +38 -0
  78. package/dist/mindmap-definition-VGOIOE7T-BZxsr4M_.js +787 -0
  79. package/dist/ordinal-DfAQgscy.js +61 -0
  80. package/dist/packet-BFZMPI3H-XfNBjRzf.js +5 -0
  81. package/dist/pie-7BOR55EZ-pJDdOv19.js +5 -0
  82. package/dist/pieDiagram-ADFJNKIX-DDwfWBNn.js +161 -0
  83. package/dist/quadrantDiagram-AYHSOK5B-Ddpxuto0.js +1024 -0
  84. package/dist/radar-NHE76QYJ-jE07d-4Z.js +5 -0
  85. package/dist/requirementDiagram-UZGBJVZJ-B_TLWQ-N.js +852 -0
  86. package/dist/sankeyDiagram-TZEHDZUN-C1TWrUpG.js +810 -0
  87. package/dist/sequenceDiagram-WL72ISMW-B8pqb5YX.js +2518 -0
  88. package/dist/stateDiagram-FKZM4ZOC-DwNT1ej6.js +263 -0
  89. package/dist/stateDiagram-v2-4FDKWEC3-CZQUitzd.js +16 -0
  90. package/dist/style.css +1 -1
  91. package/dist/timeline-definition-IT6M3QCI-BGdS1wvf.js +799 -0
  92. package/dist/treemap-KMMF4GRG-B_eEU0Q4.js +5 -0
  93. package/dist/types/index.d.ts +36 -37
  94. package/dist/types/index.d.ts.map +1 -1
  95. package/dist/xychartDiagram-PRI3JC2R-DtwXQqN4.js +1340 -0
  96. package/package.json +3 -3
  97. package/src/components/ChatPanel.vue +75 -13
  98. package/src/components/common/SettingsPanel.vue +169 -0
  99. package/src/components/input/ChatInput.vue +49 -34
  100. package/src/components/input/DropdownSelector.vue +175 -18
  101. package/src/components/message/MessageBubble.vue +61 -40
  102. package/src/components/message/PartsRenderer.vue +106 -27
  103. package/src/components/message/parts/CollapsibleCard.vue +8 -3
  104. package/src/components/message/parts/ImagePart.vue +0 -1
  105. package/src/components/message/parts/SearchPart.vue +2 -0
  106. package/src/components/message/parts/TextPart.vue +753 -11
  107. package/src/components/message/parts/ToolCallPart.vue +315 -246
  108. package/src/components/message/parts/index.ts +5 -1
  109. package/src/components/message/parts/visual-predicate.ts +43 -0
  110. package/src/components/message/parts/visual-render.ts +19 -0
  111. package/src/components/message/parts/visual.ts +12 -0
  112. package/src/composables/useChat.ts +560 -92
  113. package/src/composables/useImageUpload.ts +115 -11
  114. package/src/index.ts +6 -8
  115. package/src/styles.css +124 -0
  116. package/src/types/index.ts +52 -44
  117. package/dist/components/message/blocks/CodeBlock.vue.d.ts +0 -11
  118. package/dist/components/message/blocks/CodeBlock.vue.d.ts.map +0 -1
  119. package/dist/components/message/blocks/TextBlock.vue.d.ts +0 -7
  120. package/dist/components/message/blocks/TextBlock.vue.d.ts.map +0 -1
  121. package/dist/components/message/blocks/index.d.ts +0 -6
  122. package/dist/components/message/blocks/index.d.ts.map +0 -1
  123. package/dist/components/message/parts/ToolResultPart.vue.d.ts +0 -14
  124. package/dist/components/message/parts/ToolResultPart.vue.d.ts.map +0 -1
  125. package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts +0 -4
  126. package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts.map +0 -1
  127. package/dist/components/message/tool-results/SearchResults.vue.d.ts +0 -4
  128. package/dist/components/message/tool-results/SearchResults.vue.d.ts.map +0 -1
  129. package/dist/components/message/tool-results/WeatherCard.vue.d.ts +0 -4
  130. package/dist/components/message/tool-results/WeatherCard.vue.d.ts.map +0 -1
  131. package/dist/components/message/tool-results/index.d.ts +0 -7
  132. package/dist/components/message/tool-results/index.d.ts.map +0 -1
  133. package/src/components/message/ContentRenderer.vue +0 -61
  134. package/src/components/message/ToolResultRenderer.vue +0 -27
  135. package/src/components/message/blocks/CodeBlock.vue +0 -113
  136. package/src/components/message/blocks/TextBlock.vue +0 -21
  137. package/src/components/message/blocks/index.ts +0 -6
  138. package/src/components/message/parts/ToolResultPart.vue +0 -78
  139. package/src/components/message/tool-results/DefaultToolResult.vue +0 -45
  140. package/src/components/message/tool-results/SearchResults.vue +0 -133
  141. package/src/components/message/tool-results/WeatherCard.vue +0 -139
  142. package/src/components/message/tool-results/index.ts +0 -7
@@ -7,65 +7,83 @@
7
7
  :spinning="status === 'running'"
8
8
  >
9
9
  <template #header-actions>
10
- <CopyButton :text="commandText" title="复制" />
10
+ <CopyButton :text="argsText" title="复制" />
11
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>
12
+
13
+ <!-- 参数内容 -->
14
+ <!-- ⚠️ 注意:<pre> 会原样渲染模板里的换行/缩进;因此 command/text 直接用 v-text,避免顶部出现“大空白” -->
15
+ <pre v-if="argsType === 'json'" class="tool-args json"><code v-html="highlightedJson"></code></pre>
16
+ <pre v-else class="tool-args" :class="argsType" v-text="argsText"></pre>
17
+
18
+ <!-- 输出面板(stdout/stderr) -->
19
+ <div v-if="hasOutput" class="output-panel">
20
+ <div class="output-header">
21
+ <div class="output-tabs">
22
+ <button
23
+ class="tab"
24
+ :class="{ active: activeStream === 'stdout' }"
25
+ type="button"
26
+ @click="activeStream = 'stdout'"
27
+ >
28
+ 正常输出
29
+ </button>
30
+ <button
31
+ class="tab"
32
+ :class="{ active: activeStream === 'stderr' }"
33
+ type="button"
34
+ @click="activeStream = 'stderr'"
35
+ >
36
+ 异常/告警输出
37
+ </button>
18
38
  </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>
39
+ <div class="output-actions">
40
+ <button class="btn btn-save" type="button" @click="saveLog">
41
+ <Icon icon="lucide:download" width="14" />
42
+ 保存日志
43
+ </button>
44
+ <CopyButton :text="activeOutputText" title="复制输出" />
27
45
  </div>
28
- <code class="result-content">{{ formattedResult }}</code>
29
46
  </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>
47
+ <pre class="output-body chat-scrollbar"><code>{{ activeOutputText || '(无输出)' }}</code></pre>
48
+ </div>
49
+
50
+ <!-- 底部操作区 -->
51
+ <div class="tool-footer">
52
+ <!-- 左侧:模式选择 -->
53
+ <DropdownSelector
54
+ :value="currentMode"
55
+ :options="modeOptions"
56
+ @select="handleModeChange"
57
+ />
58
+
59
+ <!-- 右侧:状态或按钮 -->
60
+ <div class="tool-actions">
61
+ <!-- pending:跳过/运行按钮 -->
62
+ <template v-if="status === 'pending'">
63
+ <button class="btn btn-skip" @click="handleSkip">跳过</button>
64
+ <button class="btn btn-run" @click="handleRun">
65
+ 运行
66
+ <Icon icon="lucide:arrow-right" width="14" />
67
+ </button>
68
+ </template>
69
+
70
+ <!-- running:取消按钮 -->
71
+ <template v-else-if="status === 'running'">
72
+ <span class="status-text running">
73
+ <Icon icon="lucide:loader-2" width="14" class="spinning" />
74
+ 运行中
75
+ </span>
76
+ <button class="btn btn-cancel" @click="handleCancel">
77
+ <Icon icon="lucide:x" width="14" />
78
+ 取消
79
+ </button>
80
+ </template>
81
+
82
+ <!-- 其他状态:只显示状态 -->
83
+ <span v-else :class="['status-text', status]">
84
+ <Icon :icon="statusDisplayIcon" width="14" />
85
+ {{ statusText }}
86
+ </span>
69
87
  </div>
70
88
  </div>
71
89
  </CollapsibleCard>
@@ -77,24 +95,27 @@ import { Icon } from '@iconify/vue'
77
95
  import DropdownSelector from '../../input/DropdownSelector.vue'
78
96
  import CollapsibleCard from './CollapsibleCard.vue'
79
97
  import CopyButton from '../../common/CopyButton.vue'
98
+ import { highlightCode } from '@huyooo/ai-chat-shared'
80
99
  import type { ChatAdapter } from '../../../adapter'
81
100
  import type { AutoRunConfig, AutoRunMode } from '@huyooo/ai-chat-bridge-electron/renderer'
82
101
  import type { StepsExpandedType } from '../../../types'
83
102
 
84
- interface Props {
103
+ const props = withDefaults(defineProps<{
85
104
  id: string
86
105
  name: string
87
106
  args?: Record<string, unknown>
88
107
  status: 'pending' | 'running' | 'done' | 'error' | 'cancelled' | 'skipped'
89
- result: unknown | null
108
+ output?: {
109
+ stdout?: string
110
+ stderr?: string
111
+ }
90
112
  expandedType?: StepsExpandedType
91
- // 通过 props 传递,不使用 inject
92
113
  adapter?: ChatAdapter
114
+ /** 取消工具调用(优先使用上层传入,确保能同步更新 UI 状态) */
115
+ onCancelToolCall?: (toolCallId: string) => void
93
116
  autoRunConfig?: AutoRunConfig
94
117
  onSaveConfig?: (config: AutoRunConfig) => Promise<void>
95
- }
96
-
97
- const props = withDefaults(defineProps<Props>(), {
118
+ }>(), {
98
119
  expandedType: 'auto',
99
120
  })
100
121
 
@@ -104,25 +125,27 @@ const modeOptions = [
104
125
  { value: 'run-everything', label: '自动执行' },
105
126
  ]
106
127
 
107
- // 当前模式:直接从配置读取
108
128
  const currentMode = computed(() => props.autoRunConfig?.mode ?? 'run-everything')
109
129
 
110
- // 命令显示
111
- const commandDisplay = computed(() => {
130
+ // 参数类型:'json' | 'command' | 'text'
131
+ const argsType = computed(() => {
112
132
  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 ? '...' : '')
133
+ return 'command'
116
134
  }
117
- return props.name
135
+ if (props.args && Object.keys(props.args).length > 0) {
136
+ return 'json'
137
+ }
138
+ return 'text'
118
139
  })
119
140
 
120
- const commandText = computed(() => {
141
+ // 参数文本(格式化后的)
142
+ const argsText = computed(() => {
121
143
  if (props.name === 'execute_command' && props.args?.command) {
122
144
  return String(props.args.command)
123
145
  }
124
146
  if (props.args && Object.keys(props.args).length > 0) {
125
147
  try {
148
+ // JSON 格式化:2 空格缩进
126
149
  return JSON.stringify(props.args, null, 2)
127
150
  } catch {
128
151
  return String(props.args)
@@ -131,64 +154,62 @@ const commandText = computed(() => {
131
154
  return props.name
132
155
  })
133
156
 
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)
157
+ // JSON 高亮后的 HTML
158
+ const highlightedJson = computed(() => {
159
+ if (argsType.value === 'json') {
160
+ return highlightCode(argsText.value, 'json')
142
161
  }
162
+ return ''
143
163
  })
144
164
 
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
- }
165
+ const hasOutput = computed(() => {
166
+ return !!(props.output?.stdout || props.output?.stderr)
167
+ })
168
+
169
+ const activeStream = ref<'stdout' | 'stderr'>('stdout')
170
+
171
+ const activeOutputText = computed(() => {
172
+ return activeStream.value === 'stdout'
173
+ ? (props.output?.stdout ?? '')
174
+ : (props.output?.stderr ?? '')
175
+ })
151
176
 
152
- const expanded = ref(getInitialExpanded())
177
+ // 折叠状态
178
+ const expanded = ref(
179
+ props.expandedType === 'open' ? true :
180
+ props.expandedType === 'close' ? false :
181
+ props.status === 'pending' || props.status === 'running'
182
+ )
153
183
 
154
- // auto 模式下:状态变化时自动折叠/展开
155
184
  watch(() => props.status, (newStatus) => {
156
185
  if (props.expandedType === 'auto') {
157
186
  expanded.value = newStatus === 'pending' || newStatus === 'running'
158
187
  }
159
188
  })
160
189
 
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
- })
190
+ // 状态相关
191
+ const statusIcon = computed(() => ({
192
+ error: 'lucide:x-circle',
193
+ done: 'lucide:check-circle',
194
+ running: 'lucide:loader-2',
195
+ pending: 'lucide:clock',
196
+ cancelled: 'lucide:ban',
197
+ skipped: 'lucide:skip-forward',
198
+ }[props.status] ?? 'lucide:clock'))
199
+
200
+ const statusIconColor = computed(() => ({
201
+ error: 'var(--chat-error, #ef4444)',
202
+ done: 'var(--chat-success, #22c55e)',
203
+ running: 'var(--chat-accent, #3b82f6)',
204
+ pending: 'var(--chat-text-muted, #888)',
205
+ cancelled: 'var(--chat-warning, #f59e0b)',
206
+ skipped: 'var(--chat-text-muted, #888)',
207
+ }[props.status] ?? 'var(--chat-text-muted, #888)'))
185
208
 
186
209
  const statusDisplayIcon = computed(() => {
187
210
  const icons: Record<string, string> = {
188
211
  done: 'lucide:check-circle-2',
189
212
  error: 'lucide:x-circle',
190
- running: 'lucide:loader-2',
191
- pending: 'lucide:clock',
192
213
  cancelled: 'lucide:ban',
193
214
  skipped: 'lucide:skip-forward',
194
215
  }
@@ -199,8 +220,6 @@ const statusText = computed(() => {
199
220
  const texts: Record<string, string> = {
200
221
  done: '成功',
201
222
  error: '错误',
202
- running: '运行中',
203
- pending: '等待中',
204
223
  cancelled: '已取消',
205
224
  skipped: '已跳过',
206
225
  }
@@ -208,169 +227,169 @@ const statusText = computed(() => {
208
227
  })
209
228
 
210
229
  const title = computed(() => {
211
- const suffixes: Record<string, string> = {
230
+ const suffix = {
212
231
  pending: ' - 等待确认',
213
232
  running: ' - 执行中...',
214
233
  error: ' - 执行失败',
215
234
  done: ' - 执行完成',
216
235
  cancelled: ' - 已取消',
217
236
  skipped: ' - 已跳过',
218
- }
219
- return props.name + (suffixes[props.status] ?? '')
237
+ }[props.status] ?? ''
238
+ return props.name + suffix
220
239
  })
221
240
 
222
- /** 处理模式变化 */
241
+ // 操作
223
242
  async function handleModeChange(value: string) {
224
243
  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
- }
244
+ props.autoRunConfig.mode = value as AutoRunMode
245
+ await props.onSaveConfig(props.autoRunConfig)
234
246
  }
235
247
 
236
- /** 跳过执行:只拒绝当前工具,AI 继续思考 */
237
248
  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
- }
249
+ await props.adapter?.respondToolApproval?.(props.id, false)
250
250
  }
251
251
 
252
- /** 运行执行 */
253
252
  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
- }
253
+ await props.adapter?.respondToolApproval?.(props.id, true)
261
254
  }
262
255
 
263
- /** 取消执行 */
264
256
  function handleCancel() {
257
+ if (props.onCancelToolCall) {
258
+ props.onCancelToolCall(props.id)
259
+ return
260
+ }
265
261
  props.adapter?.cancel?.()
266
262
  }
267
263
 
268
- // 监听模式变化:从 manual 切换到 run-everything 时,自动执行 pending 的工具
264
+ function saveLog() {
265
+ const stdout = props.output?.stdout ?? ''
266
+ const stderr = props.output?.stderr ?? ''
267
+ const content =
268
+ `# tool: ${props.name}\n` +
269
+ `# id: ${props.id}\n` +
270
+ `# time: ${new Date().toISOString()}\n\n` +
271
+ `===== STDOUT =====\n${stdout}\n\n` +
272
+ `===== STDERR =====\n${stderr}\n`
273
+
274
+ const blob = new Blob([content], { type: 'text/plain;charset=utf-8' })
275
+ const url = URL.createObjectURL(blob)
276
+ const a = document.createElement('a')
277
+ a.href = url
278
+ a.download = `tool-${props.name}-${props.id}.log`
279
+ document.body.appendChild(a)
280
+ a.click()
281
+ document.body.removeChild(a)
282
+ URL.revokeObjectURL(url)
283
+ }
284
+
285
+ // 模式切换时自动执行
269
286
  watch(currentMode, (newMode, oldMode) => {
270
287
  if (oldMode === 'manual' && newMode === 'run-everything' && props.status === 'pending') {
271
288
  handleRun()
272
289
  }
273
290
  })
274
-
275
291
  </script>
276
292
 
277
293
  <style scoped>
278
- .tool-call-content {
279
- display: flex;
280
- flex-direction: column;
294
+ .tool-args {
295
+ margin: 0 0 12px 0;
296
+ padding: 0.75em 1em;
297
+ background: var(--chat-bg, #1e1e1e);
298
+ border: 1px solid var(--chat-border, rgba(255, 255, 255, 0.08));
299
+ border-radius: 8px;
300
+ font-size: 13px;
301
+ line-height: 1.6;
302
+ color: var(--chat-text-muted, #999);
303
+ white-space: pre-wrap;
304
+ word-break: break-word;
305
+ overflow-x: auto;
281
306
  }
282
307
 
283
- .tool-call-command {
284
- margin-bottom: 12px;
308
+ /* 让 <code> 继承父级字体(避免默认 monospace 影响 text 类型的展示) */
309
+ .tool-args code {
310
+ font-family: inherit;
311
+ font-size: inherit;
285
312
  }
286
313
 
287
- .command-header,
288
- .result-header {
289
- display: block;
290
- margin-bottom: 8px;
291
- padding: 0;
292
- cursor: default;
293
- user-select: none;
314
+ /* JSON 格式:使用等宽字体,更好的可读性,支持横向滚动 */
315
+ .tool-args.json {
316
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
294
317
  font-size: 12px;
295
- line-height: 1.5;
318
+ line-height: 1.8;
319
+ white-space: pre;
320
+ word-break: normal;
321
+ overflow-x: auto;
296
322
  }
297
323
 
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;
324
+ /* JSON 横向滚动条样式(与全局 chat-scrollbar 对齐) */
325
+ .tool-args.json::-webkit-scrollbar {
326
+ height: 6px;
305
327
  }
306
328
 
307
- .command-code {
308
- display: block;
309
- padding: 10px;
310
- /* 浅色主题下 rgba(0,0,0,0.3) 会显得“脏黑”,这里统一走 code token */
311
- background: var(--chat-code-bg, rgba(0, 0, 0, 0.3));
312
- border: 1px solid var(--chat-border, #333);
313
- border-radius: 6px;
314
- font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
315
- font-size: 13px;
316
- color: var(--chat-code-text, var(--chat-text, #ccc));
317
- white-space: pre-wrap;
318
- word-break: break-all;
319
- overflow-x: auto;
329
+ .tool-args.json::-webkit-scrollbar-track {
330
+ background: transparent;
320
331
  }
321
332
 
322
- .tool-call-result {
323
- margin-bottom: 12px;
333
+ .tool-args.json::-webkit-scrollbar-thumb {
334
+ background: var(--chat-scrollbar, rgba(255, 255, 255, 0.2));
335
+ border-radius: 3px;
324
336
  }
325
337
 
326
- .result-label,
327
- .command-label {
328
- color: var(--chat-text-muted, #888);
329
- font-weight: 500;
338
+ .tool-args.json::-webkit-scrollbar-thumb:hover {
339
+ background: var(--chat-scrollbar-hover, rgba(255, 255, 255, 0.3));
330
340
  }
331
341
 
332
- .result-name,
333
- .command-name {
334
- color: var(--chat-text, #ccc);
342
+ /* JSON 语法高亮样式(使用 CSS 变量,跟随主题) */
343
+ .tool-args.json :deep(.hljs-attr),
344
+ .tool-args.json :deep(.hljs-attribute) {
345
+ color: var(--chat-hljs-attr, #79c0ff); /* 键名 */
335
346
  }
336
347
 
337
- .result-content {
338
- display: block;
339
- padding: 10px;
340
- /* 浅色主题下 rgba(0,0,0,0.3) 会显得“脏黑”,这里统一走 code token */
341
- background: var(--chat-code-bg, rgba(0, 0, 0, 0.3));
342
- border: 1px solid var(--chat-border, #333);
343
- border-radius: 6px;
348
+ .tool-args.json :deep(.hljs-string),
349
+ .tool-args.json :deep(.hljs-template-variable) {
350
+ color: var(--chat-hljs-string, #a5d6ff); /* 字符串值 */
351
+ }
352
+
353
+ .tool-args.json :deep(.hljs-number) {
354
+ color: var(--chat-hljs-number, #79c0ff); /* 数字 */
355
+ }
356
+
357
+ .tool-args.json :deep(.hljs-literal),
358
+ .tool-args.json :deep(.hljs-null) {
359
+ color: var(--chat-hljs-literal, #79c0ff); /* true, false, null */
360
+ }
361
+
362
+ .tool-args.json :deep(.hljs-punctuation) {
363
+ color: var(--chat-hljs-punctuation, #e6edf3); /* 括号、逗号等 */
364
+ }
365
+
366
+ /* 命令格式:使用等宽字体 */
367
+ .tool-args.command {
344
368
  font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
369
+ font-size: 12px;
370
+ line-height: 1.6;
371
+ }
372
+
373
+ /* 普通文本:保持默认字体 */
374
+ .tool-args.text {
345
375
  font-size: 13px;
346
- color: var(--chat-code-text, var(--chat-text, #ccc));
347
- white-space: pre-wrap;
348
- word-break: break-all;
349
- overflow-x: auto;
376
+ line-height: 1.5;
350
377
  }
351
378
 
352
- .tool-call-footer {
379
+ .tool-footer {
353
380
  display: flex;
354
381
  align-items: center;
355
382
  justify-content: space-between;
356
383
  gap: 12px;
357
- padding-top: 12px;
358
- border-top: 1px solid var(--chat-border, #333);
359
- }
360
-
361
- .footer-left {
362
- flex-shrink: 0;
363
- position: relative;
364
- z-index: 10;
365
384
  }
366
385
 
367
- .footer-right {
386
+ .tool-actions {
368
387
  display: flex;
369
388
  align-items: center;
370
- gap: 12px;
389
+ gap: 8px;
371
390
  }
372
391
 
373
- .execution-status {
392
+ .status-text {
374
393
  display: flex;
375
394
  align-items: center;
376
395
  gap: 6px;
@@ -378,29 +397,11 @@ watch(currentMode, (newMode, oldMode) => {
378
397
  font-weight: 500;
379
398
  }
380
399
 
381
- .execution-status.done {
382
- color: var(--chat-success, #22c55e);
383
- }
384
-
385
- .execution-status.error {
386
- color: #ef4444;
387
- }
388
-
389
- .execution-status.running {
390
- color: var(--chat-accent, #3b82f6);
391
- }
392
-
393
- .execution-status.pending {
394
- color: var(--chat-text-muted, #888);
395
- }
396
-
397
- .execution-status.cancelled {
398
- color: var(--chat-warning, #f59e0b);
399
- }
400
-
401
- .execution-status.skipped {
402
- color: var(--chat-text-muted, #888);
403
- }
400
+ .status-text.done { color: var(--chat-success, #22c55e); }
401
+ .status-text.error { color: var(--chat-error, #ef4444); }
402
+ .status-text.running { color: var(--chat-accent, #3b82f6); }
403
+ .status-text.cancelled { color: var(--chat-warning, #f59e0b); }
404
+ .status-text.skipped { color: var(--chat-text-muted, #888); }
404
405
 
405
406
  .spinning {
406
407
  animation: spin 1s linear infinite;
@@ -411,11 +412,6 @@ watch(currentMode, (newMode, oldMode) => {
411
412
  to { transform: rotate(360deg); }
412
413
  }
413
414
 
414
- .action-buttons {
415
- display: flex;
416
- gap: 8px;
417
- }
418
-
419
415
  .btn {
420
416
  padding: 6px 12px;
421
417
  border-radius: 6px;
@@ -434,7 +430,6 @@ watch(currentMode, (newMode, oldMode) => {
434
430
  color: var(--chat-text, #ccc);
435
431
  border: 1px solid var(--chat-border, #333);
436
432
  }
437
-
438
433
  .btn-skip:hover {
439
434
  background: var(--chat-muted-hover, #333);
440
435
  }
@@ -443,7 +438,6 @@ watch(currentMode, (newMode, oldMode) => {
443
438
  background: var(--chat-accent, #3b82f6);
444
439
  color: #fff;
445
440
  }
446
-
447
441
  .btn-run:hover {
448
442
  background: var(--chat-accent-hover, #2563eb);
449
443
  }
@@ -453,10 +447,85 @@ watch(currentMode, (newMode, oldMode) => {
453
447
  color: var(--chat-text-muted, #888);
454
448
  border: 1px solid var(--chat-border, #333);
455
449
  }
456
-
457
450
  .btn-cancel:hover {
458
451
  background: rgba(239, 68, 68, 0.1);
459
452
  color: #ef4444;
460
453
  border-color: #ef4444;
461
454
  }
455
+
456
+ .output-panel {
457
+ margin: 0 0 12px 0;
458
+ border: 1px solid var(--chat-border, rgba(255, 255, 255, 0.08));
459
+ border-radius: 8px;
460
+ background: var(--chat-bg, #1e1e1e);
461
+ overflow: hidden;
462
+ }
463
+
464
+ .output-header {
465
+ display: flex;
466
+ align-items: center;
467
+ justify-content: space-between;
468
+ gap: 12px;
469
+ padding: 8px 10px;
470
+ border-bottom: 1px solid var(--chat-border, rgba(255, 255, 255, 0.08));
471
+ }
472
+
473
+ .output-tabs {
474
+ display: flex;
475
+ gap: 6px;
476
+ }
477
+
478
+ .tab {
479
+ padding: 4px 8px;
480
+ border-radius: 6px;
481
+ border: 1px solid var(--chat-border, #333);
482
+ background: var(--chat-muted, #2a2a2a);
483
+ color: var(--chat-text-muted, #999);
484
+ font-size: 11px;
485
+ cursor: pointer;
486
+ }
487
+
488
+ .tab.active {
489
+ background: rgba(59, 130, 246, 0.15);
490
+ border-color: rgba(59, 130, 246, 0.35);
491
+ color: var(--chat-text, #ccc);
492
+ }
493
+
494
+ .output-actions {
495
+ display: flex;
496
+ align-items: center;
497
+ gap: 8px;
498
+ }
499
+
500
+ .btn-save {
501
+ padding: 6px 10px;
502
+ border-radius: 6px;
503
+ font-size: 12px;
504
+ font-weight: 500;
505
+ cursor: pointer;
506
+ border: 1px solid var(--chat-border, #333);
507
+ background: transparent;
508
+ color: var(--chat-text-muted, #888);
509
+ display: flex;
510
+ align-items: center;
511
+ gap: 6px;
512
+ }
513
+
514
+ .btn-save:hover {
515
+ background: var(--chat-muted-hover, #333);
516
+ color: var(--chat-text, #ccc);
517
+ }
518
+
519
+ .output-body {
520
+ margin: 0;
521
+ padding: 10px 12px;
522
+ font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
523
+ font-size: 12px;
524
+ line-height: 1.6;
525
+ color: var(--chat-text-muted, #999);
526
+ white-space: pre-wrap;
527
+ word-break: break-word;
528
+ max-height: 260px;
529
+ overflow: auto;
530
+ }
462
531
  </style>