@vibe-forge/client 0.3.0 → 0.4.0

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 (158) hide show
  1. package/cli.cjs +1 -1
  2. package/dist/assets/{arc-CwMXUVsq.js → arc-DgIxeTMg.js} +1 -1
  3. package/dist/assets/{blockDiagram-c4efeb88-CGxJV7KJ.js → blockDiagram-c4efeb88-CEAob3X9.js} +1 -1
  4. package/dist/assets/{c4Diagram-c83219d4-BKhin7cY.js → c4Diagram-c83219d4-DwIxpDKd.js} +1 -1
  5. package/dist/assets/channel-DhtnrNJ6.js +1 -0
  6. package/dist/assets/{classDiagram-beda092f-BASmn22R.js → classDiagram-beda092f-Cz1q8u_0.js} +1 -1
  7. package/dist/assets/{classDiagram-v2-2358418a-BUk9rNBX.js → classDiagram-v2-2358418a-CImgTuwd.js} +1 -1
  8. package/dist/assets/clone-7bHB6YkC.js +1 -0
  9. package/dist/assets/{createText-1719965b-2XqnWjQY.js → createText-1719965b-C1_HJcCc.js} +1 -1
  10. package/dist/assets/devicon-BWlTeAUU.woff +0 -0
  11. package/dist/assets/devicon-CirD-cQx.ttf +0 -0
  12. package/dist/assets/devicon-Dg8iWy0i.svg +1211 -0
  13. package/dist/assets/devicon-TqfHp33-.eot +0 -0
  14. package/dist/assets/{edges-96097737-B7e32Jeg.js → edges-96097737-BU8qStzd.js} +1 -1
  15. package/dist/assets/{erDiagram-0228fc6a-CCR2or72.js → erDiagram-0228fc6a-DNA1Fz2L.js} +1 -1
  16. package/dist/assets/{flowDb-c6c81e3f-B72HWT9x.js → flowDb-c6c81e3f-DjiCStMN.js} +1 -1
  17. package/dist/assets/{flowDiagram-50d868cf-WOi0KARY.js → flowDiagram-50d868cf-CSDi0-RD.js} +1 -1
  18. package/dist/assets/flowDiagram-v2-4f6560a1-_13Sz5Wh.js +1 -0
  19. package/dist/assets/{flowchart-elk-definition-6af322e1-i_Yd0LCE.js → flowchart-elk-definition-6af322e1-DrhIMas7.js} +1 -1
  20. package/dist/assets/{ganttDiagram-a2739b55-CFH9zF14.js → ganttDiagram-a2739b55-CTZnUP5z.js} +1 -1
  21. package/dist/assets/{gitGraphDiagram-82fe8481-DglKfMze.js → gitGraphDiagram-82fe8481-COOW7jTi.js} +1 -1
  22. package/dist/assets/{graph-BKbBNGPf.js → graph-CIkpD4Kx.js} +1 -1
  23. package/dist/assets/{index-5325376f-BK7F9nSl.js → index-5325376f-aVVRRTIu.js} +1 -1
  24. package/dist/assets/index-D1giUI7r.css +1 -0
  25. package/dist/assets/index-DRSI_ZIL.js +514 -0
  26. package/dist/assets/{infoDiagram-8eee0895-BLFL77_D.js → infoDiagram-8eee0895-DQpZ1LVD.js} +1 -1
  27. package/dist/assets/{journeyDiagram-c64418c1-CS9XctDL.js → journeyDiagram-c64418c1-DoKguIuk.js} +1 -1
  28. package/dist/assets/{layout-By3JZZGt.js → layout-Tnmha8Nh.js} +1 -1
  29. package/dist/assets/{line-9GUsXbwv.js → line-BQR2SOyl.js} +1 -1
  30. package/dist/assets/{linear-DzGV4E9N.js → linear-DlG0eemV.js} +1 -1
  31. package/dist/assets/{mermaid.core-CG3Ib42Q.js → mermaid.core-BnwYO0He.js} +6 -6
  32. package/dist/assets/{mindmap-definition-8da855dc-WQ3LPKJU.js → mindmap-definition-8da855dc-BllYwDID.js} +1 -1
  33. package/dist/assets/{pieDiagram-a8764435-DHVIUZiN.js → pieDiagram-a8764435-DwCkhPVc.js} +1 -1
  34. package/dist/assets/{quadrantDiagram-1e28029f-C3G9Ye8-.js → quadrantDiagram-1e28029f-c40GKTU0.js} +1 -1
  35. package/dist/assets/{requirementDiagram-08caed73-C9ES1D5G.js → requirementDiagram-08caed73-DnQp2Tk6.js} +1 -1
  36. package/dist/assets/{sankeyDiagram-a04cb91d-B4BKXclQ.js → sankeyDiagram-a04cb91d-CnJrs13b.js} +1 -1
  37. package/dist/assets/{sequenceDiagram-c5b8d532-DrgEb25G.js → sequenceDiagram-c5b8d532-1YBwnpKu.js} +1 -1
  38. package/dist/assets/{stateDiagram-1ecb1508-CF1XWARJ.js → stateDiagram-1ecb1508-BFBxQ6Fh.js} +1 -1
  39. package/dist/assets/{stateDiagram-v2-c2b004d7-IO3i3yXv.js → stateDiagram-v2-c2b004d7-Dmechvv2.js} +1 -1
  40. package/dist/assets/{styles-b4e223ce-DACN9aSc.js → styles-b4e223ce-DWWfWX8O.js} +1 -1
  41. package/dist/assets/{styles-ca3715f6-bekm2WLP.js → styles-ca3715f6-CKKvZxaU.js} +1 -1
  42. package/dist/assets/{styles-d45a18b0-OzTDVBb8.js → styles-d45a18b0-dKMOUh9p.js} +1 -1
  43. package/dist/assets/{svgDrawCommon-b86b1483-BWroJerr.js → svgDrawCommon-b86b1483-CBgjChPM.js} +1 -1
  44. package/dist/assets/{timeline-definition-faaaa080-CCfRNigO.js → timeline-definition-faaaa080-NCt-HHmb.js} +1 -1
  45. package/dist/assets/{xychartDiagram-f5964ef8-C3cbfVqN.js → xychartDiagram-f5964ef8-BJhXS4dG.js} +1 -1
  46. package/dist/index.html +2 -7
  47. package/index.html +0 -5
  48. package/package.json +11 -6
  49. package/src/App.tsx +2 -0
  50. package/src/api/README.md +26 -0
  51. package/src/api/automation.ts +88 -0
  52. package/src/api/base.ts +54 -0
  53. package/src/api/benchmark.ts +45 -0
  54. package/src/api/config.ts +24 -0
  55. package/src/api/knowledge.ts +72 -0
  56. package/src/api/projects.ts +15 -0
  57. package/src/api/sessions.ts +82 -0
  58. package/src/api/types.ts +20 -0
  59. package/src/api.ts +44 -269
  60. package/src/components/AutomationView/AutomationView.scss +5 -1
  61. package/src/components/AutomationView/RuleFormPanel.tsx +3 -2
  62. package/src/components/AutomationView/TaskList.scss +4 -6
  63. package/src/components/AutomationView/TaskList.tsx +2 -1
  64. package/src/components/AutomationView/TriggerList.scss +4 -1
  65. package/src/components/BenchmarkView/BenchmarkCasePanel.scss +267 -0
  66. package/src/components/BenchmarkView/BenchmarkCasePanel.tsx +309 -0
  67. package/src/components/BenchmarkView/BenchmarkSidebar.scss +182 -0
  68. package/src/components/BenchmarkView/BenchmarkSidebar.tsx +262 -0
  69. package/src/components/BenchmarkView/BenchmarkView.scss +78 -0
  70. package/src/components/BenchmarkView/index.tsx +197 -0
  71. package/src/components/BenchmarkView/types.ts +10 -0
  72. package/src/components/BenchmarkView/utils.ts +21 -0
  73. package/src/components/Chat.tsx +37 -29
  74. package/src/components/{chat/CodeBlock.tsx → CodeBlock.tsx} +3 -1
  75. package/src/components/ConfigView.tsx +7 -0
  76. package/src/components/{chat/MarkdownContent.tsx → MarkdownContent.tsx} +1 -1
  77. package/src/components/NavRail.tsx +7 -0
  78. package/src/components/chat/ChatHeader.scss +37 -19
  79. package/src/components/chat/ChatHeader.tsx +6 -9
  80. package/src/components/chat/ChatHistoryView.tsx +89 -45
  81. package/src/components/chat/CurrentTodoList.tsx +10 -9
  82. package/src/components/chat/{MessageItem.scss → Messages/MessageItem.scss} +14 -0
  83. package/src/components/chat/{MessageItem.tsx → Messages/MessageItem.tsx} +30 -8
  84. package/src/components/chat/{messageUtils.ts → Messages/message-utils.ts} +1 -1
  85. package/src/components/chat/{Sender.scss → Sender/Sender.scss} +80 -0
  86. package/src/components/chat/{Sender.tsx → Sender/Sender.tsx} +161 -5
  87. package/src/components/chat/tools/DefaultTool.tsx +184 -21
  88. package/src/components/chat/tools/adapter-claude/BashTool.scss +67 -51
  89. package/src/components/chat/tools/adapter-claude/BashTool.tsx +83 -49
  90. package/src/components/chat/tools/adapter-claude/GlobTool.scss +0 -79
  91. package/src/components/chat/tools/adapter-claude/GlobTool.tsx +16 -36
  92. package/src/components/chat/tools/adapter-claude/GrepTool.scss +0 -87
  93. package/src/components/chat/tools/adapter-claude/GrepTool.tsx +22 -41
  94. package/src/components/chat/tools/adapter-claude/LSTool.scss +0 -79
  95. package/src/components/chat/tools/adapter-claude/LSTool.tsx +15 -15
  96. package/src/components/chat/tools/adapter-claude/ReadTool.scss +0 -55
  97. package/src/components/chat/tools/adapter-claude/ReadTool.tsx +20 -42
  98. package/src/components/chat/tools/adapter-claude/TodoTool.scss +8 -23
  99. package/src/components/chat/tools/adapter-claude/TodoTool.tsx +24 -11
  100. package/src/components/chat/tools/adapter-claude/WriteTool.scss +21 -69
  101. package/src/components/chat/tools/adapter-claude/WriteTool.tsx +22 -58
  102. package/src/components/chat/tools/adapter-claude/index.ts +4 -10
  103. package/src/components/chat/tools/adapter-claude/utils.ts +54 -0
  104. package/src/components/chat/tools/core/ToolCallBox.scss +356 -0
  105. package/src/components/chat/{ToolGroup.tsx → tools/core/ToolGroup.tsx} +26 -7
  106. package/src/components/chat/{ToolRenderer.tsx → tools/core/ToolRenderer.tsx} +6 -4
  107. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.scss +11 -0
  108. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +75 -0
  109. package/src/components/chat/tools/plugin-chrome-devtools/index.ts +45 -0
  110. package/src/components/chat/tools/task/GetTaskInfoTool.scss +2 -27
  111. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +48 -38
  112. package/src/components/chat/tools/task/ListTasksTool.scss +3 -28
  113. package/src/components/chat/tools/task/ListTasksTool.tsx +11 -8
  114. package/src/components/chat/tools/task/StartTasksTool.scss +3 -28
  115. package/src/components/chat/tools/task/StartTasksTool.tsx +14 -17
  116. package/src/components/chat/tools/task/components/TaskRow.scss +105 -0
  117. package/src/components/chat/tools/task/components/TaskRow.tsx +163 -0
  118. package/src/components/chat/tools/task/components/TaskToolCard.scss +15 -15
  119. package/src/components/chat/tools/task/components/TaskToolCard.tsx +8 -6
  120. package/src/components/config/ConfigSectionForm.tsx +12 -1
  121. package/src/components/config/channelDefinitions.ts +6 -0
  122. package/src/components/config/configSchema.ts +10 -1
  123. package/src/components/config/recordEditors/ChannelRecordEditor.scss +1 -0
  124. package/src/components/config/recordEditors/ChannelRecordEditor.tsx +397 -0
  125. package/src/components/config/recordEditors/index.tsx +1 -0
  126. package/src/components/knowledge-base/components/RuleItem.tsx +1 -1
  127. package/src/components/knowledge-base/components/SpecItem.tsx +1 -1
  128. package/src/hooks/chat/use-chat-interaction.ts +26 -0
  129. package/src/{components/chat/useChatModels.tsx → hooks/chat/use-chat-models.tsx} +46 -15
  130. package/src/hooks/chat/use-chat-permission-mode.ts +47 -0
  131. package/src/hooks/chat/use-chat-scroll.ts +51 -0
  132. package/src/hooks/chat/use-chat-session-actions.ts +147 -0
  133. package/src/hooks/chat/use-chat-session-messages.ts +250 -0
  134. package/src/hooks/chat/use-chat-session.ts +57 -0
  135. package/src/hooks/chat/use-chat-view.ts +39 -0
  136. package/src/main.tsx +10 -13
  137. package/src/resources/locales/en.json +66 -0
  138. package/src/resources/locales/zh.json +66 -0
  139. package/src/runtime-config.ts +52 -0
  140. package/src/vite-env.d.ts +11 -0
  141. package/src/ws.ts +5 -3
  142. package/vite.config.ts +12 -4
  143. package/dist/assets/channel-jbCEHqbG.js +0 -1
  144. package/dist/assets/clone-CCRKqS4L.js +0 -1
  145. package/dist/assets/flowDiagram-v2-4f6560a1-Baslbgn4.js +0 -1
  146. package/dist/assets/index-B0qfCb1G.css +0 -1
  147. package/dist/assets/index-CNo75dYr.js +0 -497
  148. package/src/components/chat/ToolCallBox.scss +0 -137
  149. package/src/components/chat/useChatSession.ts +0 -370
  150. /package/src/components/{chat/CodeBlock.scss → CodeBlock.scss} +0 -0
  151. /package/src/components/chat/{MessageFooter.tsx → Messages/MessageFooter.tsx} +0 -0
  152. /package/src/components/chat/{CompletionMenu.scss → Sender/CompletionMenu.scss} +0 -0
  153. /package/src/components/chat/{CompletionMenu.tsx → Sender/CompletionMenu.tsx} +0 -0
  154. /package/src/components/chat/{ThinkingStatus.scss → Sender/ThinkingStatus.scss} +0 -0
  155. /package/src/components/chat/{ThinkingStatus.tsx → Sender/ThinkingStatus.tsx} +0 -0
  156. /package/src/components/chat/{ToolCallBox.tsx → tools/core/ToolCallBox.tsx} +0 -0
  157. /package/src/components/chat/{ToolGroup.scss → tools/core/ToolGroup.scss} +0 -0
  158. /package/src/{components/chat/safeSerialize.ts → utils/safe-serialize.ts} +0 -0
@@ -1,10 +1,172 @@
1
1
  import type { ChatMessageContent } from '@vibe-forge/core'
2
- import React from 'react'
3
2
  import { useTranslation } from 'react-i18next'
4
- import { CodeBlock } from '../CodeBlock'
5
- import { MarkdownContent } from '../MarkdownContent'
6
- import { ToolCallBox } from '../ToolCallBox'
7
- import { safeJsonStringify } from '../safeSerialize'
3
+ import { CodeBlock } from '#~/components/CodeBlock'
4
+ import { MarkdownContent } from '#~/components/MarkdownContent'
5
+ import { ToolCallBox } from './core/ToolCallBox'
6
+ import { safeJsonStringify, toSerializable } from '#~/utils/safe-serialize'
7
+
8
+ interface StructuredTextBlock {
9
+ type: 'text'
10
+ text: string
11
+ format: 'text' | 'markdown'
12
+ }
13
+
14
+ interface StructuredImageBlock {
15
+ type: 'image'
16
+ src: string
17
+ alt?: string
18
+ title?: string
19
+ width?: number
20
+ height?: number
21
+ }
22
+
23
+ type StructuredBlock = StructuredTextBlock | StructuredImageBlock
24
+
25
+ function parseStructuredInput(value: unknown) {
26
+ if (typeof value !== 'string') {
27
+ return value
28
+ }
29
+ const trimmed = value.trim()
30
+ if (
31
+ (trimmed.startsWith('{') && trimmed.endsWith('}')) ||
32
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))
33
+ ) {
34
+ try {
35
+ return JSON.parse(trimmed)
36
+ } catch {
37
+ return value
38
+ }
39
+ }
40
+ return value
41
+ }
42
+
43
+ function resolveImageSource(value: Record<string, unknown>) {
44
+ const directUrl = typeof value.url === 'string'
45
+ ? value.url
46
+ : typeof value.src === 'string'
47
+ ? value.src
48
+ : typeof value.image_url === 'string'
49
+ ? value.image_url
50
+ : typeof value.imageUrl === 'string'
51
+ ? value.imageUrl
52
+ : typeof value.dataUrl === 'string'
53
+ ? value.dataUrl
54
+ : null
55
+ if (directUrl) {
56
+ return directUrl
57
+ }
58
+ const source = value.source != null && typeof value.source === 'object'
59
+ ? (value.source as Record<string, unknown>)
60
+ : null
61
+ const data = typeof value.data === 'string'
62
+ ? value.data
63
+ : typeof value.base64 === 'string'
64
+ ? value.base64
65
+ : source != null && typeof source.data === 'string'
66
+ ? source.data
67
+ : null
68
+ if (!data) {
69
+ return null
70
+ }
71
+ const mimeType = typeof value.mimeType === 'string'
72
+ ? value.mimeType
73
+ : typeof value.mime_type === 'string'
74
+ ? value.mime_type
75
+ : source != null && typeof source.media_type === 'string'
76
+ ? source.media_type
77
+ : source != null && typeof source.mimeType === 'string'
78
+ ? source.mimeType
79
+ : source != null && typeof source.mime_type === 'string'
80
+ ? source.mime_type
81
+ : 'image/png'
82
+ return `data:${mimeType};base64,${data}`
83
+ }
84
+
85
+ function parseBlock(value: unknown): StructuredBlock | null {
86
+ if (value == null || typeof value !== 'object') {
87
+ return null
88
+ }
89
+ const obj = value as Record<string, unknown>
90
+ const rawType = typeof obj.type === 'string' ? obj.type.toLowerCase() : ''
91
+ if (rawType === 'text' || rawType === 'markdown' || rawType === 'md') {
92
+ const text = typeof obj.text === 'string'
93
+ ? obj.text
94
+ : typeof obj.content === 'string'
95
+ ? obj.content
96
+ : null
97
+ if (text == null) {
98
+ return null
99
+ }
100
+ const rawFormat = typeof obj.format === 'string' ? obj.format.toLowerCase() : 'markdown'
101
+ const format = rawType === 'text'
102
+ ? (rawFormat === 'text' || rawFormat === 'plain' ? 'text' : 'markdown')
103
+ : 'markdown'
104
+ return { type: 'text', text, format }
105
+ }
106
+ if (rawType === 'image') {
107
+ const src = resolveImageSource(obj)
108
+ if (!src) {
109
+ return null
110
+ }
111
+ const alt = typeof obj.alt === 'string' ? obj.alt : undefined
112
+ const title = typeof obj.title === 'string' ? obj.title : undefined
113
+ const width = typeof obj.width === 'number' ? obj.width : undefined
114
+ const height = typeof obj.height === 'number' ? obj.height : undefined
115
+ return { type: 'image', src, alt, title, width, height }
116
+ }
117
+ return null
118
+ }
119
+
120
+ function getStructuredBlocks(value: unknown): StructuredBlock[] | null {
121
+ const serializable = toSerializable(value)
122
+ const parsed = parseStructuredInput(serializable)
123
+ if (Array.isArray(parsed)) {
124
+ const blocks = parsed.map(parseBlock)
125
+ return blocks.every(Boolean) ? (blocks as StructuredBlock[]) : null
126
+ }
127
+ if (parsed != null && typeof parsed === 'object') {
128
+ const container = parsed as Record<string, unknown>
129
+ const content = container.content ?? container.items ?? container.blocks
130
+ if (Array.isArray(content)) {
131
+ const blocks = content.map(parseBlock)
132
+ return blocks.every(Boolean) ? (blocks as StructuredBlock[]) : null
133
+ }
134
+ }
135
+ const single = parseBlock(parsed)
136
+ return single ? [single] : null
137
+ }
138
+
139
+ function StructuredToolResult({ blocks }: { blocks: StructuredBlock[] }) {
140
+ return (
141
+ <div className='tool-result-structured'>
142
+ {blocks.map((block, index) => {
143
+ if (block.type === 'text') {
144
+ return (
145
+ <div className='tool-result-text' key={`text-${index}`}>
146
+ {block.format === 'markdown'
147
+ ? <MarkdownContent content={block.text} />
148
+ : <div className='tool-result-text-content'>{block.text}</div>}
149
+ </div>
150
+ )
151
+ }
152
+ return (
153
+ <div className='tool-result-image-wrapper' key={`image-${index}`}>
154
+ <img
155
+ className='tool-result-image'
156
+ src={block.src}
157
+ alt={block.alt ?? ''}
158
+ width={block.width}
159
+ height={block.height}
160
+ />
161
+ {block.title != null && block.title.length > 0 && (
162
+ <div className='tool-result-image-caption'>{block.title}</div>
163
+ )}
164
+ </div>
165
+ )
166
+ })}
167
+ </div>
168
+ )
169
+ }
8
170
 
9
171
  export function DefaultTool({
10
172
  item,
@@ -14,17 +176,16 @@ export function DefaultTool({
14
176
  resultItem?: Extract<ChatMessageContent, { type: 'tool_result' }>
15
177
  }) {
16
178
  const { t } = useTranslation()
179
+ const structuredBlocks = resultItem != null ? getStructuredBlocks(resultItem.content) : null
17
180
  return (
18
181
  <div className='tool-group'>
19
182
  <ToolCallBox
20
183
  header={
21
- <>
22
- <span className='material-symbols-rounded' style={{ fontSize: 16 }}>build</span>
23
- <span>{item.name}</span>
24
- <span style={{ color: 'var(--sub-text-color)', fontSize: 11, fontWeight: 400 }}>
25
- {t('chat.tools.call')}
26
- </span>
27
- </>
184
+ <div className='tool-header-content'>
185
+ <span className='material-symbols-rounded tool-header-icon'>build</span>
186
+ <span className='tool-header-title'>{item.name}</span>
187
+ <span className='tool-header-hint'>{t('chat.tools.call')}</span>
188
+ </div>
28
189
  }
29
190
  content={
30
191
  <div className='tool-content'>
@@ -40,20 +201,22 @@ export function DefaultTool({
40
201
  type='result'
41
202
  isError={resultItem.is_error}
42
203
  header={
43
- <>
44
- <span className='material-symbols-rounded' style={{ fontSize: 16 }}>
204
+ <div className='tool-header-content'>
205
+ <span className='material-symbols-rounded tool-header-icon'>
45
206
  {resultItem.is_error === true ? 'error' : 'check_circle'}
46
207
  </span>
47
- <span>{t('chat.result')}</span>
48
- </>
208
+ <span className='tool-header-title'>{t('chat.result')}</span>
209
+ </div>
49
210
  }
50
211
  content={
51
212
  <div className='tool-content'>
52
- {typeof resultItem.content === 'string'
53
- ? (resultItem.content.startsWith('```')
54
- ? <MarkdownContent content={resultItem.content} />
55
- : <CodeBlock code={resultItem.content} lang='text' />)
56
- : <CodeBlock code={safeJsonStringify(resultItem.content, 2)} lang='json' />}
213
+ {structuredBlocks != null
214
+ ? <StructuredToolResult blocks={structuredBlocks} />
215
+ : (typeof resultItem.content === 'string'
216
+ ? (resultItem.content.startsWith('```')
217
+ ? <MarkdownContent content={resultItem.content} />
218
+ : <CodeBlock code={resultItem.content} lang='text' />)
219
+ : <CodeBlock code={safeJsonStringify(resultItem.content, 2)} lang='json' />)}
57
220
  </div>
58
221
  }
59
222
  />
@@ -1,71 +1,87 @@
1
1
  .tool-group.bash-tool {
2
- .bash-header, .result-header {
2
+ .tool-call-header {
3
+ height: auto;
4
+ min-height: 32px;
5
+ }
6
+
7
+ .bash-tool__header {
8
+ width: 100%;
9
+ min-width: 0;
3
10
  display: flex;
4
11
  align-items: center;
5
- gap: 6px;
12
+ gap: 8px;
13
+ }
6
14
 
7
- .status-icon {
8
- font-size: 16px;
9
- }
15
+ .bash-tool__icon {
16
+ font-size: 16px;
17
+ color: var(--sub-text-color);
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ width: 16px;
22
+ height: 16px;
23
+ flex-shrink: 0;
24
+ align-self: center;
25
+ }
10
26
 
11
- .bash-title, .result-title {
12
- font-weight: 600;
13
- }
27
+ .bash-tool__header-main {
28
+ flex: 1;
29
+ min-width: 0;
30
+ display: flex;
31
+ flex-direction: column;
32
+ gap: 2px;
14
33
  }
15
34
 
16
- .bash-command-preview {
17
- font-family:
18
- 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
19
- font-size: 11px;
20
- color: var(--text-color);
35
+ .bash-tool__command-row {
36
+ display: flex;
37
+ align-items: center;
38
+ gap: 6px;
39
+ min-width: 0;
40
+ }
41
+
42
+ .bash-tool__command-text {
43
+ flex: 1;
44
+ min-width: 0;
21
45
  overflow: hidden;
22
46
  text-overflow: ellipsis;
23
47
  white-space: nowrap;
24
- flex: 1;
25
- }
26
-
27
- .tool-reason {
28
- margin: 0 0 4px 0;
29
48
  font-size: 12px;
30
- color: var(--sub-text-color);
31
- line-height: 1.4;
49
+ color: var(--text-color);
50
+ }
32
51
 
33
- &.markdown-body {
34
- font-size: 12px;
35
- color: inherit;
36
- background-color: transparent;
37
- padding: 0;
52
+ .bash-tool__command-text--clickable {
53
+ cursor: pointer;
54
+ color: var(--text-color);
38
55
 
39
- p {
40
- margin: 0 !important;
41
- padding: 0 !important;
42
- }
56
+ &:hover {
57
+ text-shadow: 0 0 .5px currentColor;
43
58
  }
44
59
  }
45
60
 
46
- .bash-content-scroll {
47
- max-height: 400px;
48
- overflow-y: auto;
61
+ .bash-tool__reason-row {
62
+ display: flex;
63
+ min-width: 0;
64
+ }
49
65
 
50
- .bash-code-wrapper {
51
- background-color: transparent;
52
- border: none;
53
- border-radius: 0;
54
- padding: 0;
55
- }
66
+ .bash-tool__reason-text {
67
+ min-width: 0;
68
+ overflow: hidden;
69
+ text-overflow: ellipsis;
70
+ white-space: nowrap;
71
+ }
56
72
 
57
- &::-webkit-scrollbar {
58
- width: 4px;
59
- }
60
- &::-webkit-scrollbar-track {
61
- background: transparent;
62
- }
63
- &::-webkit-scrollbar-thumb {
64
- background: var(--border-color);
65
- border-radius: 4px;
66
- }
67
- &::-webkit-scrollbar-thumb:hover {
68
- background: var(--sub-text-color);
69
- }
73
+ .bash-tool__header-tags {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 6px;
77
+ flex-shrink: 0;
78
+ }
79
+
80
+ .bash-tool__command-detail {
81
+ margin-bottom: 6px;
82
+ }
83
+
84
+ .tool-scroll {
85
+ max-height: 270px;
70
86
  }
71
87
  }
@@ -1,22 +1,15 @@
1
1
  import './BashTool.scss'
2
- import React from 'react'
2
+ import type { ToolInputs } from '@vibe-forge/core'
3
+ import React, { useState } from 'react'
3
4
  import { useTranslation } from 'react-i18next'
4
- import ReactMarkdown from 'react-markdown'
5
- import remarkGfm from 'remark-gfm'
6
-
5
+ import { CodeBlock } from '#~/components/CodeBlock'
6
+ import { ToolCallBox } from '../core/ToolCallBox'
7
+ import { safeJsonStringify } from '#~/utils/safe-serialize'
7
8
  import { defineToolRender } from '../defineToolRender'
8
- import { CodeBlock } from '../../CodeBlock'
9
- import { ToolCallBox } from '../../ToolCallBox'
10
- import { safeJsonStringify } from '../../safeSerialize'
11
9
 
12
10
  export const BashTool = defineToolRender(({ item, resultItem }) => {
13
11
  const { t } = useTranslation()
14
- const input = (item.input != null ? item.input : {}) as {
15
- command?: string
16
- reason?: string
17
- thought?: string
18
- description?: string
19
- }
12
+ const input = (item.input != null ? item.input : {}) as Partial<ToolInputs['adapter:claude-code:Bash']>
20
13
  const command = input.command ?? ''
21
14
  const reason = (input.description != null && input.description !== '')
22
15
  ? input.description
@@ -25,58 +18,99 @@ export const BashTool = defineToolRender(({ item, resultItem }) => {
25
18
  : (input.thought != null && input.thought !== '')
26
19
  ? input.thought
27
20
  : ''
21
+ const timeout = input.timeout
22
+ const runInBackground = input.run_in_background
23
+ const dangerouslyDisableSandbox = input.dangerouslyDisableSandbox
24
+ const reasonLine = reason.replaceAll('\n', ' ').replace(/\s+/g, ' ').trim()
25
+ const commandLine = command.split('\n')[0] ?? ''
26
+ const hasMoreCommand = command.trim() !== commandLine.trim()
27
+ const showCommandExpand = hasMoreCommand || commandLine.length > 80
28
+ const [showCommandDetail, setShowCommandDetail] = useState(false)
28
29
 
29
30
  return (
30
31
  <div className='tool-group bash-tool'>
31
32
  <ToolCallBox
32
- collapsible={false}
33
+ defaultExpanded={true}
34
+ type={resultItem != null ? 'result' : 'call'}
35
+ isError={resultItem?.is_error ?? false}
33
36
  header={
34
- <div className='bash-header'>
35
- <span className='material-symbols-rounded status-icon'>terminal</span>
36
- <span className='bash-title'>{t('chat.tools.bash')}</span>
37
+ <div className='bash-tool__header'>
38
+ <span className='material-symbols-rounded bash-tool__icon'>terminal</span>
39
+ <div className='bash-tool__header-main'>
40
+ <div className='bash-tool__reason-row'>
41
+ <span className='bash-tool__reason-text tool-header-hint'>
42
+ {reasonLine !== '' ? reasonLine : commandLine}
43
+ </span>
44
+ </div>
45
+ {reasonLine !== '' && (
46
+ <div className='bash-tool__command-row'>
47
+ <span
48
+ className={`bash-tool__command-text tool-header-mono${
49
+ showCommandExpand ? ' bash-tool__command-text--clickable' : ''
50
+ }`}
51
+ title={showCommandExpand ? t('chat.tools.viewCommand') : undefined}
52
+ onClick={(e) => {
53
+ if (!showCommandExpand) return
54
+ e.stopPropagation()
55
+ setShowCommandDetail(prev => !prev)
56
+ }}
57
+ >
58
+ {commandLine}
59
+ {hasMoreCommand ? ' …' : ''}
60
+ </span>
61
+ </div>
62
+ )}
63
+ </div>
64
+ <div className='bash-tool__header-tags'>
65
+ {typeof timeout === 'number' && Number.isFinite(timeout) && (
66
+ <span
67
+ className='tool-icon-tag'
68
+ title={`${t('chat.tools.timeout')}: ${timeout}ms`}
69
+ >
70
+ <span className='material-symbols-rounded tool-icon-tag__icon'>timer</span>
71
+ <span className='tool-icon-tag__text'>
72
+ {timeout % 1000 === 0 ? `${timeout / 1000}s` : `${timeout}ms`}
73
+ </span>
74
+ </span>
75
+ )}
76
+ {runInBackground === true && (
77
+ <span className='tool-icon-tag' title={t('chat.tools.runInBackground')}>
78
+ <span className='material-symbols-rounded tool-icon-tag__icon'>schedule</span>
79
+ </span>
80
+ )}
81
+ {dangerouslyDisableSandbox === true && (
82
+ <span className='tool-icon-tag' title={t('chat.tools.dangerouslyDisableSandbox')}>
83
+ <span className='material-symbols-rounded tool-icon-tag__icon'>shield_lock</span>
84
+ </span>
85
+ )}
86
+ </div>
37
87
  </div>
38
88
  }
39
89
  content={
40
90
  <div className='tool-content'>
41
- {(reason != null && reason !== '') && (
42
- <div className='tool-reason markdown-body'>
43
- <ReactMarkdown remarkPlugins={[remarkGfm]}>{reason}</ReactMarkdown>
91
+ {showCommandDetail && (
92
+ <div className='bash-tool__command-detail'>
93
+ <div className='tool-code-wrapper'>
94
+ <CodeBlock code={command} lang='shell' hideHeader={true} />
95
+ </div>
44
96
  </div>
45
97
  )}
46
- <div className='bash-content-scroll'>
47
- <div className='bash-code-wrapper'>
48
- <CodeBlock code={command} lang='shell' />
98
+ <div className='tool-scroll'>
99
+ <div className='tool-code-wrapper'>
100
+ {resultItem
101
+ ? (typeof resultItem.content === 'string'
102
+ ? <CodeBlock code={resultItem.content} lang='text' hideHeader={true} />
103
+ : <CodeBlock code={safeJsonStringify(resultItem.content, 2)} lang='json' hideHeader={true} />)
104
+ : (
105
+ <div className='tool-placeholder'>
106
+ {t('chat.result')}
107
+ </div>
108
+ )}
49
109
  </div>
50
110
  </div>
51
111
  </div>
52
112
  }
53
113
  />
54
-
55
- {resultItem != null && (
56
- <ToolCallBox
57
- type='result'
58
- isError={resultItem.is_error}
59
- header={
60
- <div className='result-header'>
61
- <span className='material-symbols-rounded status-icon'>
62
- {resultItem.is_error === true ? 'error' : 'check_circle'}
63
- </span>
64
- <span className='result-title'>{t('chat.result')}</span>
65
- </div>
66
- }
67
- content={
68
- <div className='tool-content'>
69
- <div className='bash-content-scroll'>
70
- <div className='bash-code-wrapper'>
71
- {typeof resultItem.content === 'string'
72
- ? <CodeBlock code={resultItem.content} lang='text' />
73
- : <CodeBlock code={safeJsonStringify(resultItem.content, 2)} lang='json' />}
74
- </div>
75
- </div>
76
- </div>
77
- }
78
- />
79
- )}
80
114
  </div>
81
115
  )
82
116
  })
@@ -1,83 +1,4 @@
1
1
  .glob-tool {
2
- .tool-header-content {
3
- display: flex;
4
- align-items: center;
5
- width: 100%;
6
- height: 100%;
7
- overflow: hidden;
8
-
9
- .material-symbols-rounded {
10
- font-size: 18px;
11
- display: flex;
12
- align-items: center;
13
- justify-content: center;
14
- color: var(--sub-text-color);
15
- height: 20px;
16
- width: 18px;
17
- margin-right: 8px;
18
- position: relative;
19
- top: 1px;
20
- }
21
-
22
- .command-name {
23
- font-size: 13px;
24
- font-weight: 500;
25
- color: var(--text-color);
26
- white-space: nowrap;
27
- line-height: 20px;
28
- display: flex;
29
- align-items: center;
30
- margin-right: 8px;
31
- }
32
-
33
- .pattern {
34
- font-size: 12px;
35
- color: var(--sub-text-color);
36
- white-space: nowrap;
37
- overflow: hidden;
38
- text-overflow: ellipsis;
39
- display: flex;
40
- align-items: center;
41
- min-width: 0;
42
- flex: 1;
43
- line-height: 20px;
44
- }
45
-
46
- .file-count {
47
- font-size: 12px;
48
- color: var(--sub-text-color);
49
- white-space: nowrap;
50
- margin-left: 8px;
51
- flex-shrink: 0;
52
- }
53
- }
54
-
55
- .input-details {
56
- padding: 8px 12px;
57
- font-size: 12px;
58
- color: var(--sub-text-color);
59
- border-bottom: 1px solid var(--border-color);
60
-
61
- .label {
62
- font-weight: 500;
63
- margin-right: 8px;
64
- }
65
-
66
- .value {
67
- font-family: var(--font-mono);
68
- background: var(--bg-color-hover);
69
- padding: 2px 4px;
70
- border-radius: 4px;
71
- }
72
- }
73
-
74
- .tool-placeholder {
75
- padding: 8px 12px;
76
- color: var(--sub-text-color);
77
- font-size: 12px;
78
- font-style: italic;
79
- }
80
-
81
2
  .file-list-container {
82
3
  border-radius: 0;
83
4
  border-left: none;