@vibe-forge/client 0.10.0 → 0.11.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 (111) hide show
  1. package/dist/assets/{arc-CCXV7u3V.js → arc-M4HYfcHs.js} +1 -1
  2. package/dist/assets/{blockDiagram-c4efeb88-Bm52FmvT.js → blockDiagram-c4efeb88-CUrDjrxj.js} +1 -1
  3. package/dist/assets/{c4Diagram-c83219d4-C8tTEpcK.js → c4Diagram-c83219d4-BMEtqlFp.js} +1 -1
  4. package/dist/assets/channel-Cj3Cp2OJ.js +1 -0
  5. package/dist/assets/{classDiagram-beda092f-CNAIBAH1.js → classDiagram-beda092f-BOmDJ0Ml.js} +1 -1
  6. package/dist/assets/{classDiagram-v2-2358418a-BHeZAVdc.js → classDiagram-v2-2358418a-BODzX2MB.js} +1 -1
  7. package/dist/assets/clone-B7Q9B1dS.js +1 -0
  8. package/dist/assets/{createText-1719965b-BS2hLG8t.js → createText-1719965b-B9Dd8zcR.js} +1 -1
  9. package/dist/assets/{cssMode-WHcTFAOU.js → cssMode-DLxG92Ot.js} +1 -1
  10. package/dist/assets/{edges-96097737-C07f4iWA.js → edges-96097737-CuZFd43m.js} +1 -1
  11. package/dist/assets/{erDiagram-0228fc6a-BytsAWUs.js → erDiagram-0228fc6a-8g9lu2-Z.js} +1 -1
  12. package/dist/assets/{flowDb-c6c81e3f-CQJkOpAs.js → flowDb-c6c81e3f-BlBS1tdN.js} +1 -1
  13. package/dist/assets/{flowDiagram-50d868cf-CD5Tng2S.js → flowDiagram-50d868cf-u6mWflpF.js} +1 -1
  14. package/dist/assets/flowDiagram-v2-4f6560a1-G3v545eF.js +1 -0
  15. package/dist/assets/{flowchart-elk-definition-6af322e1-ylso-GWH.js → flowchart-elk-definition-6af322e1-BDqI2NFr.js} +1 -1
  16. package/dist/assets/{freemarker2-U_9Jyyr3.js → freemarker2-tVtpTMPu.js} +1 -1
  17. package/dist/assets/{ganttDiagram-a2739b55-Cg98bJx5.js → ganttDiagram-a2739b55-CDQjx9Wu.js} +1 -1
  18. package/dist/assets/{gitGraphDiagram-82fe8481-7Yp4hz0N.js → gitGraphDiagram-82fe8481-DUHFKRVA.js} +1 -1
  19. package/dist/assets/{graph-Ig3nvzvL.js → graph-2HKPi5B_.js} +1 -1
  20. package/dist/assets/{handlebars-DQyCBwHe.js → handlebars-D00tgNd8.js} +1 -1
  21. package/dist/assets/{html-CNC2AT5k.js → html-B-TDzBiR.js} +1 -1
  22. package/dist/assets/{htmlMode-DlATk4xW.js → htmlMode-ClycqSTM.js} +1 -1
  23. package/dist/assets/{index-5325376f-C4zed9sb.js → index-5325376f-DPrJpRQ-.js} +1 -1
  24. package/dist/assets/{index-Dbx0JG0p.js → index-CAHZZEoo.js} +319 -323
  25. package/dist/assets/{index-DRLsOoqb.css → index-Di7lePfb.css} +1 -1
  26. package/dist/assets/{infoDiagram-8eee0895-C8oSBaFs.js → infoDiagram-8eee0895-Co5tS1I5.js} +1 -1
  27. package/dist/assets/{javascript-9wv9uKW4.js → javascript-zbkwarmb.js} +1 -1
  28. package/dist/assets/{journeyDiagram-c64418c1-D5kJldvy.js → journeyDiagram-c64418c1-k_qioHgy.js} +1 -1
  29. package/dist/assets/{jsonMode-45dv39mU.js → jsonMode-C3CSpzBF.js} +1 -1
  30. package/dist/assets/{layout-DYNFLnIl.js → layout-CjOXKxvs.js} +1 -1
  31. package/dist/assets/{line-1gvOYQYZ.js → line-C-XnQrKR.js} +1 -1
  32. package/dist/assets/{linear-DHGm6Zdw.js → linear-C7MMERzS.js} +1 -1
  33. package/dist/assets/{liquid-BGoxrdXO.js → liquid-5G37EU6K.js} +1 -1
  34. package/dist/assets/{lspLanguageFeatures-CpCCXhrd.js → lspLanguageFeatures-zaDMuhCE.js} +1 -1
  35. package/dist/assets/{mdx-Dc2iMbEw.js → mdx-Bc-LY0gi.js} +1 -1
  36. package/dist/assets/{mermaid.core-Cq2bBFF1.js → mermaid.core-CechbHof.js} +4 -4
  37. package/dist/assets/{mindmap-definition-8da855dc-y5l6GRVh.js → mindmap-definition-8da855dc-ejftCDGb.js} +1 -1
  38. package/dist/assets/{pieDiagram-a8764435-PNROcv9y.js → pieDiagram-a8764435-DY__X3Qj.js} +1 -1
  39. package/dist/assets/{python-DjYAge7h.js → python-vK2Ff2J5.js} +1 -1
  40. package/dist/assets/{quadrantDiagram-1e28029f-CFJ3VPpp.js → quadrantDiagram-1e28029f-azIZCv_2.js} +1 -1
  41. package/dist/assets/{razor-OIY8fx_i.js → razor-BipjBJKu.js} +1 -1
  42. package/dist/assets/{requirementDiagram-08caed73-BpzDIINS.js → requirementDiagram-08caed73-C4EB0Xs2.js} +1 -1
  43. package/dist/assets/{sankeyDiagram-a04cb91d-CH69-iIn.js → sankeyDiagram-a04cb91d-PNhR6YWu.js} +1 -1
  44. package/dist/assets/{sequenceDiagram-c5b8d532-yBBEeVFU.js → sequenceDiagram-c5b8d532-4c-qV-Ri.js} +1 -1
  45. package/dist/assets/{stateDiagram-1ecb1508-BvF4sign.js → stateDiagram-1ecb1508-CnURumPE.js} +1 -1
  46. package/dist/assets/{stateDiagram-v2-c2b004d7-BeyT7Ghx.js → stateDiagram-v2-c2b004d7-DR2qHTPg.js} +1 -1
  47. package/dist/assets/{styles-b4e223ce-C58zxmK6.js → styles-b4e223ce-B2PWXT_i.js} +1 -1
  48. package/dist/assets/{styles-ca3715f6-DCE5sFi5.js → styles-ca3715f6-DEhgVF5H.js} +1 -1
  49. package/dist/assets/{styles-d45a18b0-CG-C1aM8.js → styles-d45a18b0-DyzccA5F.js} +1 -1
  50. package/dist/assets/{svgDrawCommon-b86b1483-F-K8GeDd.js → svgDrawCommon-b86b1483-C_1tMhxp.js} +1 -1
  51. package/dist/assets/{timeline-definition-faaaa080-DPv4uqVX.js → timeline-definition-faaaa080-FdaC0dQH.js} +1 -1
  52. package/dist/assets/{tsMode-BtU8ZELV.js → tsMode-CrMC5T3_.js} +1 -1
  53. package/dist/assets/{typescript-CJHgISWo.js → typescript-CRfPu8v7.js} +1 -1
  54. package/dist/assets/{xml-C_TJw4Bi.js → xml-jlRvQfFI.js} +1 -1
  55. package/dist/assets/{xychartDiagram-f5964ef8-BmXlhBzX.js → xychartDiagram-f5964ef8-sxjv75h9.js} +1 -1
  56. package/dist/assets/{yaml-BujeJOJ6.js → yaml-B47_IHOH.js} +1 -1
  57. package/dist/index.html +2 -2
  58. package/package.json +10 -10
  59. package/src/components/chat/ChatHistoryView.tsx +92 -14
  60. package/src/components/chat/messages/MessageItem.scss +10 -0
  61. package/src/components/chat/messages/MessageItem.tsx +5 -1
  62. package/src/components/chat/messages/MessageStatusNotice.scss +163 -0
  63. package/src/components/chat/messages/MessageStatusNotice.tsx +48 -0
  64. package/src/components/chat/messages/build-chat-history-status-notices.ts +138 -0
  65. package/src/components/chat/sender/@components/sender-body/SenderBody.tsx +0 -24
  66. package/src/components/chat/sender/@core/build-sender-controller-result.ts +0 -6
  67. package/src/components/chat/sender/@hooks/use-sender-controller.ts +0 -2
  68. package/src/components/chat/sender/@types/sender-props.ts +0 -3
  69. package/src/components/chat/sender/Sender.scss +0 -58
  70. package/src/components/chat/sender/Sender.tsx +0 -2
  71. package/src/components/chat/tools/DefaultTool.tsx +74 -207
  72. package/src/components/chat/tools/adapter-claude/ClaudeEditDiff.tsx +30 -0
  73. package/src/components/chat/tools/adapter-claude/GenericClaudeTool.scss +128 -0
  74. package/src/components/chat/tools/adapter-claude/GenericClaudeTool.tsx +133 -0
  75. package/src/components/chat/tools/adapter-claude/claude-tool-edit-builders.ts +102 -0
  76. package/src/components/chat/tools/adapter-claude/claude-tool-field-sections.tsx +168 -0
  77. package/src/components/chat/tools/adapter-claude/claude-tool-operation-builders.ts +135 -0
  78. package/src/components/chat/tools/adapter-claude/claude-tool-presentation.ts +61 -0
  79. package/src/components/chat/tools/adapter-claude/claude-tool-shared.ts +185 -0
  80. package/src/components/chat/tools/adapter-claude/claude-tool-summary.ts +76 -0
  81. package/src/components/chat/tools/adapter-claude/claude-tool-system-builders.ts +125 -0
  82. package/src/components/chat/tools/adapter-claude/claude-tool-task-builders.ts +148 -0
  83. package/src/components/chat/tools/adapter-claude/index.ts +24 -15
  84. package/src/components/chat/tools/core/ToolCallBox.scss +344 -36
  85. package/src/components/chat/tools/core/ToolCallBox.tsx +35 -13
  86. package/src/components/chat/tools/core/ToolDiffViewer.scss +138 -0
  87. package/src/components/chat/tools/core/ToolDiffViewer.tsx +180 -0
  88. package/src/components/chat/tools/core/ToolGroup.scss +52 -74
  89. package/src/components/chat/tools/core/ToolGroup.tsx +20 -26
  90. package/src/components/chat/tools/core/ToolRenderer.tsx +3 -3
  91. package/src/components/chat/tools/core/ToolResultContent.tsx +66 -0
  92. package/src/components/chat/tools/core/ToolSummaryHeader.tsx +67 -0
  93. package/src/components/chat/tools/core/tool-content-presence.ts +57 -0
  94. package/src/components/chat/tools/core/tool-display.ts +192 -0
  95. package/src/components/chat/tools/core/tool-result-content-utils.ts +171 -0
  96. package/src/components/chat/tools/core/tool-summary.ts +194 -0
  97. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +66 -53
  98. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +26 -9
  99. package/src/components/chat/tools/task/ListTasksTool.tsx +22 -9
  100. package/src/components/chat/tools/task/StartTasksTool.tsx +22 -9
  101. package/src/hooks/chat/interaction-state.ts +29 -9
  102. package/src/hooks/chat/use-chat-scroll.ts +2 -2
  103. package/src/hooks/chat/use-chat-session-messages.ts +24 -18
  104. package/src/hooks/chat/use-chat-session.ts +2 -2
  105. package/src/resources/locales/en.json +81 -0
  106. package/src/resources/locales/zh.json +81 -0
  107. package/src/routes/ChatRoute.tsx +40 -15
  108. package/src/utils/strip-ansi.ts +26 -0
  109. package/dist/assets/channel-gq_WMRvv.js +0 -1
  110. package/dist/assets/clone-XxGY7A5N.js +0 -1
  111. package/dist/assets/flowDiagram-v2-4f6560a1-DIBOANLV.js +0 -1
@@ -1,172 +1,17 @@
1
- import { CodeBlock } from '#~/components/CodeBlock'
2
- import { MarkdownContent } from '#~/components/MarkdownContent'
3
- import { safeJsonStringify, toSerializable } from '#~/utils/safe-serialize'
4
- import type { ChatMessageContent } from '@vibe-forge/core'
1
+ import { Tooltip } from 'antd'
5
2
  import { useTranslation } from 'react-i18next'
6
- import { ToolCallBox } from './core/ToolCallBox'
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
3
 
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
- }
4
+ import type { ChatMessageContent } from '@vibe-forge/core'
119
5
 
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
- }
6
+ import { CodeBlock } from '#~/components/CodeBlock'
7
+ import { safeJsonStringify } from '#~/utils/safe-serialize'
138
8
 
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
- }
9
+ import { ToolCallBox } from './core/ToolCallBox'
10
+ import { ToolResultContent } from './core/ToolResultContent'
11
+ import { ToolSummaryHeader } from './core/ToolSummaryHeader'
12
+ import { hasMeaningfulToolValue } from './core/tool-content-presence'
13
+ import { TOOL_TOOLTIP_PROPS, getToolSectionIcon, getToolTargetPresentation } from './core/tool-display'
14
+ import { getToolPrimaryText, getToolTitleText } from './core/tool-summary'
170
15
 
171
16
  export function DefaultTool({
172
17
  item,
@@ -176,51 +21,73 @@ export function DefaultTool({
176
21
  resultItem?: Extract<ChatMessageContent, { type: 'tool_result' }>
177
22
  }) {
178
23
  const { t } = useTranslation()
179
- const structuredBlocks = resultItem != null ? getStructuredBlocks(resultItem.content) : null
24
+ const hasCallDetails = hasMeaningfulToolValue(item.input)
25
+ const hasResultDetails = resultItem != null && hasMeaningfulToolValue(resultItem.content)
26
+ const hasDetails = hasCallDetails || hasResultDetails
27
+ const titleText = getToolTitleText(item, t)
28
+ const targetPresentation = getToolTargetPresentation(getToolPrimaryText(item))
29
+ const errorMeta = resultItem?.is_error === true
30
+ ? (
31
+ <span className='tool-status tool-status--error'>
32
+ <span className='material-symbols-rounded'>error</span>
33
+ </span>
34
+ )
35
+ : undefined
36
+
180
37
  return (
181
- <div className='tool-group'>
38
+ <div className='tool-group tool-group--compact'>
182
39
  <ToolCallBox
183
- header={
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>
189
- }
190
- content={
191
- <div className='tool-content'>
192
- <CodeBlock
193
- code={safeJsonStringify(item.input != null ? item.input : {}, 2)}
194
- lang='json'
195
- />
196
- </div>
197
- }
198
- />
199
- {resultItem != null && (
200
- <ToolCallBox
201
- type='result'
202
- isError={resultItem.is_error}
203
- header={
204
- <div className='tool-header-content'>
205
- <span className='material-symbols-rounded tool-header-icon'>
206
- {resultItem.is_error === true ? 'error' : 'check_circle'}
207
- </span>
208
- <span className='tool-header-title'>{t('chat.result')}</span>
40
+ variant='inline'
41
+ defaultExpanded={false}
42
+ collapsible={hasDetails}
43
+ header={({ isExpanded, isCollapsible }) => (
44
+ <ToolSummaryHeader
45
+ icon={<span className='material-symbols-rounded'>build</span>}
46
+ title={titleText}
47
+ target={targetPresentation.text}
48
+ targetTitle={targetPresentation.title}
49
+ targetMonospace={targetPresentation.monospace}
50
+ expanded={isExpanded}
51
+ collapsible={isCollapsible}
52
+ meta={errorMeta}
53
+ metaTitle={errorMeta == null ? undefined : t('chat.result')}
54
+ />
55
+ )}
56
+ content={hasDetails
57
+ ? (
58
+ <div className='tool-detail-sections'>
59
+ {hasCallDetails && (
60
+ <div className='tool-detail-section'>
61
+ <div className='tool-detail-section__header'>
62
+ <Tooltip title={t('chat.tools.call')} {...TOOL_TOOLTIP_PROPS}>
63
+ <span className='tool-detail-section__icon material-symbols-rounded'>
64
+ {getToolSectionIcon('call')}
65
+ </span>
66
+ </Tooltip>
67
+ </div>
68
+ <CodeBlock
69
+ code={safeJsonStringify(item.input != null ? item.input : {}, 2)}
70
+ lang='json'
71
+ hideHeader={true}
72
+ />
73
+ </div>
74
+ )}
75
+ {hasResultDetails && resultItem != null && (
76
+ <div className='tool-detail-section'>
77
+ <div className='tool-detail-section__header'>
78
+ <Tooltip title={t('chat.result')} {...TOOL_TOOLTIP_PROPS}>
79
+ <span className='tool-detail-section__icon material-symbols-rounded'>
80
+ {getToolSectionIcon('result')}
81
+ </span>
82
+ </Tooltip>
83
+ </div>
84
+ <ToolResultContent content={resultItem.content} />
85
+ </div>
86
+ )}
209
87
  </div>
210
- }
211
- content={
212
- <div className='tool-content'>
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' />)}
220
- </div>
221
- }
222
- />
223
- )}
88
+ )
89
+ : null}
90
+ />
224
91
  </div>
225
92
  )
226
93
  }
@@ -0,0 +1,30 @@
1
+ import React from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+
4
+ import { ToolDiffViewer } from '../core/ToolDiffViewer'
5
+ import type { ToolDiffMetaItem } from '../core/ToolDiffViewer'
6
+
7
+ export function ClaudeEditDiff({
8
+ oldValue,
9
+ newValue,
10
+ lang,
11
+ metaItems = []
12
+ }: {
13
+ oldValue?: string
14
+ newValue?: string
15
+ lang?: string
16
+ metaItems?: ToolDiffMetaItem[]
17
+ }) {
18
+ const { t } = useTranslation()
19
+
20
+ return (
21
+ <ToolDiffViewer
22
+ original={oldValue ?? ''}
23
+ modified={newValue ?? ''}
24
+ language={lang}
25
+ metaItems={metaItems}
26
+ splitLabel={t('chat.tools.diffSplit')}
27
+ inlineLabel={t('chat.tools.diffInline')}
28
+ />
29
+ )
30
+ }
@@ -0,0 +1,128 @@
1
+ .tool-group.claude-generic-tool {
2
+ .claude-generic-tool__content {
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 8px;
6
+ }
7
+
8
+ .claude-generic-tool__section {
9
+ display: flex;
10
+ flex-direction: column;
11
+ gap: 6px;
12
+ min-width: 0;
13
+ }
14
+
15
+ .claude-generic-tool__status {
16
+ display: inline-flex;
17
+ align-items: center;
18
+
19
+ .material-symbols-rounded {
20
+ font-size: 16px;
21
+ }
22
+
23
+ &.is-success {
24
+ color: var(--success-color);
25
+ }
26
+
27
+ &.is-error {
28
+ color: var(--danger-color);
29
+ }
30
+ }
31
+
32
+ .claude-generic-tool__list {
33
+ display: flex;
34
+ flex-direction: column;
35
+ gap: 4px;
36
+ }
37
+
38
+ .claude-generic-tool__list-item {
39
+ padding: 4px 0 4px 10px;
40
+ border: none;
41
+ border-left: 1px solid var(--border-color);
42
+ border-radius: 0;
43
+ background: transparent;
44
+ color: var(--text-color);
45
+ font-family: var(--font-mono);
46
+ font-size: 12px;
47
+ overflow-wrap: anywhere;
48
+ }
49
+
50
+ .claude-generic-tool__questions {
51
+ display: flex;
52
+ flex-direction: column;
53
+ gap: 8px;
54
+ }
55
+
56
+ .claude-generic-tool__question {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: 6px;
60
+ padding-left: 10px;
61
+ border-left: 1px solid var(--border-color);
62
+ }
63
+
64
+ .claude-generic-tool__question-header {
65
+ display: flex;
66
+ align-items: center;
67
+ justify-content: space-between;
68
+ gap: 8px;
69
+ }
70
+
71
+ .claude-generic-tool__question-title {
72
+ font-size: 12px;
73
+ font-weight: 500;
74
+ color: var(--text-color);
75
+ min-width: 0;
76
+ overflow: hidden;
77
+ text-overflow: ellipsis;
78
+ white-space: nowrap;
79
+ }
80
+
81
+ .claude-generic-tool__question-mode {
82
+ flex-shrink: 0;
83
+ color: var(--sub-text-color);
84
+ font-size: 15px;
85
+ cursor: help;
86
+ }
87
+
88
+ .claude-generic-tool__question-text {
89
+ color: var(--text-color);
90
+ font-size: 12px;
91
+ line-height: 1.55;
92
+ }
93
+
94
+ .claude-generic-tool__question-options {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 4px;
98
+ }
99
+
100
+ .claude-generic-tool__question-option {
101
+ padding: 4px 0 4px 10px;
102
+ border-radius: 0;
103
+ background: transparent;
104
+ border: none;
105
+ border-left: 1px solid var(--border-color);
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: 2px;
109
+ }
110
+
111
+ .claude-generic-tool__question-option-label {
112
+ color: var(--text-color);
113
+ font-size: 12px;
114
+ font-weight: 500;
115
+ }
116
+
117
+ .claude-generic-tool__question-option-description {
118
+ color: var(--sub-text-color);
119
+ font-size: 11px;
120
+ line-height: 1.45;
121
+ }
122
+
123
+ .claude-generic-tool__placeholder {
124
+ color: var(--sub-text-color);
125
+ font-size: 12px;
126
+ font-style: italic;
127
+ }
128
+ }
@@ -0,0 +1,133 @@
1
+ import './GenericClaudeTool.scss'
2
+
3
+ import { Tooltip } from 'antd'
4
+ import React, { useMemo } from 'react'
5
+ import { useTranslation } from 'react-i18next'
6
+
7
+ import { ToolCallBox } from '../core/ToolCallBox'
8
+ import { ToolResultContent } from '../core/ToolResultContent'
9
+ import { ToolSummaryHeader } from '../core/ToolSummaryHeader'
10
+ import { hasMeaningfulToolValue } from '../core/tool-content-presence'
11
+ import {
12
+ TOOL_TOOLTIP_PROPS,
13
+ getToolFieldIcon,
14
+ getToolInlineValueText,
15
+ getToolSectionIcon,
16
+ getToolTargetPresentation
17
+ } from '../core/tool-display'
18
+ import { defineToolRender } from '../defineToolRender'
19
+ import { ClaudeEditDiff } from './ClaudeEditDiff'
20
+ import { ClaudeToolInlineFields, renderClaudeBlockField } from './claude-tool-field-sections'
21
+ import { buildClaudeToolPresentation, getClaudeToolBaseName } from './claude-tool-presentation'
22
+ import { getClaudeToolSummaryText } from './claude-tool-summary'
23
+
24
+ export const GenericClaudeTool = defineToolRender(({ item, resultItem }) => {
25
+ const { t } = useTranslation()
26
+ const view = useMemo(() => buildClaudeToolPresentation(item.name, item.input), [item.input, item.name])
27
+ const titleText = useMemo(() => t(view.titleKey, { defaultValue: view.fallbackTitle }), [
28
+ t,
29
+ view.fallbackTitle,
30
+ view.titleKey
31
+ ])
32
+ const inlineFields = view.fields.filter(field => field.format === 'inline')
33
+ const isEditTool = view.baseName === 'Edit'
34
+ const blockFields = view.fields.filter((field) => {
35
+ if (field.format === 'inline') return false
36
+ if (!isEditTool) return true
37
+ return field.labelKey !== 'chat.tools.fields.oldString' && field.labelKey !== 'chat.tools.fields.newString'
38
+ })
39
+ const hasFields = view.fields.length > 0
40
+ const hasResultDetails = resultItem != null && hasMeaningfulToolValue(resultItem.content)
41
+ const showResultDetails = hasResultDetails && (!isEditTool || resultItem?.is_error === true)
42
+ const hasDetails = hasFields || hasResultDetails
43
+ const preferMarkdown = ['WebFetch', 'WebSearch'].includes(getClaudeToolBaseName(item.name))
44
+ const editInput = isEditTool && item.input != null && typeof item.input === 'object' && !Array.isArray(item.input)
45
+ ? item.input as Record<string, unknown>
46
+ : null
47
+ const editLanguage = blockFields.find(field => field.lang != null)?.lang
48
+ const editOldValue = typeof editInput?.old_string === 'string' ? editInput.old_string : undefined
49
+ const editNewValue = typeof editInput?.new_string === 'string' ? editInput.new_string : undefined
50
+ const hasEditDiff = isEditTool && (editOldValue != null || editNewValue != null)
51
+ const editMetaItems = hasEditDiff
52
+ ? inlineFields.map((field) => {
53
+ const label = t(field.labelKey, { defaultValue: field.fallbackLabel })
54
+ const isBooleanTrue = field.value === true || field.value === 'true'
55
+ const isBooleanFalse = field.value === false || field.value === 'false'
56
+
57
+ return {
58
+ icon: getToolFieldIcon(field.labelKey, field.format),
59
+ label,
60
+ value: isBooleanTrue
61
+ ? t('chat.tools.booleanOn')
62
+ : isBooleanFalse
63
+ ? t('chat.tools.booleanOff')
64
+ : getToolInlineValueText(field.value),
65
+ tone: isBooleanTrue ? 'success' : isBooleanFalse ? 'muted' : 'default'
66
+ } as const
67
+ })
68
+ : []
69
+ const standaloneInlineFields = hasEditDiff ? [] : inlineFields
70
+ const summaryText = useMemo(() => getClaudeToolSummaryText(item.name, item.input, t), [item.input, item.name, t])
71
+ const rawTargetText = view.primary ?? (summaryText !== titleText ? summaryText : undefined)
72
+ const targetPresentation = getToolTargetPresentation(rawTargetText)
73
+ const errorMeta = resultItem?.is_error === true
74
+ ? (
75
+ <span className='claude-generic-tool__status is-error'>
76
+ <span className='material-symbols-rounded'>error</span>
77
+ </span>
78
+ )
79
+ : undefined
80
+
81
+ return (
82
+ <div className='tool-group tool-group--compact claude-generic-tool'>
83
+ <ToolCallBox
84
+ variant='inline'
85
+ defaultExpanded={false}
86
+ collapsible={hasDetails}
87
+ header={({ isExpanded, isCollapsible }) => (
88
+ <ToolSummaryHeader
89
+ icon={<span className='material-symbols-rounded'>{view.icon}</span>}
90
+ title={titleText}
91
+ target={targetPresentation.text}
92
+ targetTitle={targetPresentation.title}
93
+ targetMonospace={targetPresentation.monospace}
94
+ expanded={isExpanded}
95
+ collapsible={isCollapsible}
96
+ meta={errorMeta}
97
+ metaTitle={errorMeta == null ? undefined : t('chat.result')}
98
+ />
99
+ )}
100
+ content={hasDetails
101
+ ? (
102
+ <div className='tool-detail-sections claude-generic-tool__content'>
103
+ <ClaudeToolInlineFields fields={standaloneInlineFields} t={t} />
104
+ {hasEditDiff && (
105
+ <div className='tool-detail-section claude-generic-tool__section'>
106
+ <ClaudeEditDiff
107
+ oldValue={editOldValue}
108
+ newValue={editNewValue}
109
+ lang={editLanguage}
110
+ metaItems={editMetaItems}
111
+ />
112
+ </div>
113
+ )}
114
+ {blockFields.map((field, index) => renderClaudeBlockField(field, index, t))}
115
+ {showResultDetails && resultItem != null && (
116
+ <div className='tool-detail-section'>
117
+ <div className='tool-detail-section__header'>
118
+ <Tooltip title={t('chat.result')} {...TOOL_TOOLTIP_PROPS}>
119
+ <span className='tool-detail-section__icon material-symbols-rounded'>
120
+ {getToolSectionIcon('result')}
121
+ </span>
122
+ </Tooltip>
123
+ </div>
124
+ <ToolResultContent content={resultItem.content} preferMarkdown={preferMarkdown} />
125
+ </div>
126
+ )}
127
+ </div>
128
+ )
129
+ : null}
130
+ />
131
+ </div>
132
+ )
133
+ })
@@ -0,0 +1,102 @@
1
+ import { asBoolean, asString, pushField } from './claude-tool-shared'
2
+ import type { ClaudeToolField } from './claude-tool-shared'
3
+ import { getFileInfo, getLanguageFromPath } from './utils'
4
+
5
+ interface BuilderParams {
6
+ baseName: string
7
+ record: Record<string, unknown> | null
8
+ fields: ClaudeToolField[]
9
+ usedKeys: Set<string>
10
+ }
11
+
12
+ export function buildClaudeEditToolPresentation(params: BuilderParams) {
13
+ const { baseName, record, fields, usedKeys } = params
14
+
15
+ if (baseName === 'TodoWrite') {
16
+ const todos = Array.isArray(record?.todos)
17
+ ? record.todos.flatMap((todo) => {
18
+ if (todo == null || typeof todo !== 'object') {
19
+ return []
20
+ }
21
+ const data = todo as Record<string, unknown>
22
+ const content = asString(data.content)
23
+ const status = asString(data.status)
24
+ return content != null ? [`[${status ?? 'pending'}] ${content}`] : []
25
+ })
26
+ : undefined
27
+
28
+ pushField(fields, usedKeys, 'todos', {
29
+ labelKey: 'chat.tools.fields.todos',
30
+ fallbackLabel: 'Todos',
31
+ format: 'list',
32
+ value: todos
33
+ })
34
+ return { handled: true, primary: todos != null ? `${todos.length} todos` : undefined }
35
+ }
36
+
37
+ if (baseName === 'Edit') {
38
+ const filePath = asString(record?.file_path)
39
+ const primary = filePath != null ? getFileInfo(filePath).filePath : undefined
40
+ const language = getLanguageFromPath(primary ?? '')
41
+ const replaceAll = asBoolean(record?.replace_all)
42
+
43
+ pushField(fields, usedKeys, 'replace_all', {
44
+ labelKey: 'chat.tools.fields.replaceAll',
45
+ fallbackLabel: 'Replace All',
46
+ format: 'inline',
47
+ value: replaceAll != null ? String(replaceAll) : undefined
48
+ })
49
+ pushField(fields, usedKeys, 'old_string', {
50
+ labelKey: 'chat.tools.fields.oldString',
51
+ fallbackLabel: 'Old String',
52
+ format: 'code',
53
+ value: asString(record?.old_string),
54
+ lang: language
55
+ })
56
+ pushField(fields, usedKeys, 'new_string', {
57
+ labelKey: 'chat.tools.fields.newString',
58
+ fallbackLabel: 'New String',
59
+ format: 'code',
60
+ value: asString(record?.new_string),
61
+ lang: language
62
+ })
63
+ usedKeys.add('file_path')
64
+ return { handled: true, primary }
65
+ }
66
+
67
+ if (baseName !== 'NotebookEdit') {
68
+ return { handled: false }
69
+ }
70
+
71
+ const notebookPath = asString(record?.notebook_path)
72
+ const primary = notebookPath != null ? getFileInfo(notebookPath).filePath : undefined
73
+ const cellType = asString(record?.cell_type)
74
+
75
+ pushField(fields, usedKeys, 'cell_id', {
76
+ labelKey: 'chat.tools.fields.cellId',
77
+ fallbackLabel: 'Cell ID',
78
+ format: 'inline',
79
+ value: asString(record?.cell_id)
80
+ })
81
+ pushField(fields, usedKeys, 'cell_type', {
82
+ labelKey: 'chat.tools.fields.cellType',
83
+ fallbackLabel: 'Cell Type',
84
+ format: 'inline',
85
+ value: cellType
86
+ })
87
+ pushField(fields, usedKeys, 'edit_mode', {
88
+ labelKey: 'chat.tools.fields.editMode',
89
+ fallbackLabel: 'Edit Mode',
90
+ format: 'inline',
91
+ value: asString(record?.edit_mode)
92
+ })
93
+ pushField(fields, usedKeys, 'new_source', {
94
+ labelKey: 'chat.tools.fields.newSource',
95
+ fallbackLabel: 'New Source',
96
+ format: 'code',
97
+ value: asString(record?.new_source),
98
+ lang: cellType === 'markdown' ? 'markdown' : 'text'
99
+ })
100
+ usedKeys.add('notebook_path')
101
+ return { handled: true, primary }
102
+ }