@vibe-forge/client 0.2.0-alpha.9 → 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 (166) hide show
  1. package/cli.cjs +1 -1
  2. package/dist/assets/{arc-CybT1Fs2.js → arc-DgIxeTMg.js} +1 -1
  3. package/dist/assets/{blockDiagram-c4efeb88-BY5Aoa-D.js → blockDiagram-c4efeb88-CEAob3X9.js} +1 -1
  4. package/dist/assets/{c4Diagram-c83219d4-F42hTbzS.js → c4Diagram-c83219d4-DwIxpDKd.js} +1 -1
  5. package/dist/assets/channel-DhtnrNJ6.js +1 -0
  6. package/dist/assets/{classDiagram-beda092f-D-tIPp-3.js → classDiagram-beda092f-Cz1q8u_0.js} +1 -1
  7. package/dist/assets/{classDiagram-v2-2358418a-J57aCe6u.js → classDiagram-v2-2358418a-CImgTuwd.js} +1 -1
  8. package/dist/assets/clone-7bHB6YkC.js +1 -0
  9. package/dist/assets/{createText-1719965b-ByfEqOF-.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-CMEArkOa.js → edges-96097737-BU8qStzd.js} +1 -1
  15. package/dist/assets/{erDiagram-0228fc6a-Cf8mX2aj.js → erDiagram-0228fc6a-DNA1Fz2L.js} +1 -1
  16. package/dist/assets/{flowDb-c6c81e3f-DG6WKyo7.js → flowDb-c6c81e3f-DjiCStMN.js} +1 -1
  17. package/dist/assets/{flowDiagram-50d868cf-CstUxz-w.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--4CRoQ-H.js → flowchart-elk-definition-6af322e1-DrhIMas7.js} +1 -1
  20. package/dist/assets/{ganttDiagram-a2739b55-DYgHcKd-.js → ganttDiagram-a2739b55-CTZnUP5z.js} +1 -1
  21. package/dist/assets/{gitGraphDiagram-82fe8481-DDSVpfsd.js → gitGraphDiagram-82fe8481-COOW7jTi.js} +1 -1
  22. package/dist/assets/{graph-CRWF39gX.js → graph-CIkpD4Kx.js} +1 -1
  23. package/dist/assets/{index-5325376f-W1hft795.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-D4SHcix6.js → infoDiagram-8eee0895-DQpZ1LVD.js} +1 -1
  27. package/dist/assets/{journeyDiagram-c64418c1-MWgCkVoE.js → journeyDiagram-c64418c1-DoKguIuk.js} +1 -1
  28. package/dist/assets/{layout-C88ObkCf.js → layout-Tnmha8Nh.js} +1 -1
  29. package/dist/assets/{line-C7WAYMt5.js → line-BQR2SOyl.js} +1 -1
  30. package/dist/assets/{linear-C4msxfcU.js → linear-DlG0eemV.js} +1 -1
  31. package/dist/assets/{mermaid.core-Cabag9SZ.js → mermaid.core-BnwYO0He.js} +6 -6
  32. package/dist/assets/{mindmap-definition-8da855dc-CeS8ETXx.js → mindmap-definition-8da855dc-BllYwDID.js} +1 -1
  33. package/dist/assets/{pieDiagram-a8764435-BvjyKnq5.js → pieDiagram-a8764435-DwCkhPVc.js} +1 -1
  34. package/dist/assets/{quadrantDiagram-1e28029f-DzYvpbNM.js → quadrantDiagram-1e28029f-c40GKTU0.js} +1 -1
  35. package/dist/assets/{requirementDiagram-08caed73-DHIoDbyo.js → requirementDiagram-08caed73-DnQp2Tk6.js} +1 -1
  36. package/dist/assets/{sankeyDiagram-a04cb91d-BFSGnQGs.js → sankeyDiagram-a04cb91d-CnJrs13b.js} +1 -1
  37. package/dist/assets/{sequenceDiagram-c5b8d532-_LM3BJ5-.js → sequenceDiagram-c5b8d532-1YBwnpKu.js} +1 -1
  38. package/dist/assets/{stateDiagram-1ecb1508-DwORjOzl.js → stateDiagram-1ecb1508-BFBxQ6Fh.js} +1 -1
  39. package/dist/assets/{stateDiagram-v2-c2b004d7-B4cAWWz1.js → stateDiagram-v2-c2b004d7-Dmechvv2.js} +1 -1
  40. package/dist/assets/{styles-b4e223ce-D_rmV3B_.js → styles-b4e223ce-DWWfWX8O.js} +1 -1
  41. package/dist/assets/{styles-ca3715f6-BFx4VuFc.js → styles-ca3715f6-CKKvZxaU.js} +1 -1
  42. package/dist/assets/{styles-d45a18b0-BE3106vL.js → styles-d45a18b0-dKMOUh9p.js} +1 -1
  43. package/dist/assets/{svgDrawCommon-b86b1483-DwDTO1op.js → svgDrawCommon-b86b1483-CBgjChPM.js} +1 -1
  44. package/dist/assets/{timeline-definition-faaaa080-C4b8qUQZ.js → timeline-definition-faaaa080-NCt-HHmb.js} +1 -1
  45. package/dist/assets/{xychartDiagram-f5964ef8-BRJ9Z4u-.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 -241
  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 +13 -1
  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/NewSessionGuide.scss +35 -13
  86. package/src/components/chat/NewSessionGuide.tsx +20 -10
  87. package/src/components/chat/{Sender.scss → Sender/Sender.scss} +80 -0
  88. package/src/components/chat/{Sender.tsx → Sender/Sender.tsx} +161 -5
  89. package/src/components/chat/tools/DefaultTool.tsx +184 -21
  90. package/src/components/chat/tools/adapter-claude/BashTool.scss +67 -51
  91. package/src/components/chat/tools/adapter-claude/BashTool.tsx +83 -49
  92. package/src/components/chat/tools/adapter-claude/GlobTool.scss +0 -79
  93. package/src/components/chat/tools/adapter-claude/GlobTool.tsx +16 -36
  94. package/src/components/chat/tools/adapter-claude/GrepTool.scss +0 -87
  95. package/src/components/chat/tools/adapter-claude/GrepTool.tsx +22 -41
  96. package/src/components/chat/tools/adapter-claude/LSTool.scss +0 -79
  97. package/src/components/chat/tools/adapter-claude/LSTool.tsx +15 -15
  98. package/src/components/chat/tools/adapter-claude/ReadTool.scss +0 -55
  99. package/src/components/chat/tools/adapter-claude/ReadTool.tsx +20 -42
  100. package/src/components/chat/tools/adapter-claude/TodoTool.scss +8 -23
  101. package/src/components/chat/tools/adapter-claude/TodoTool.tsx +24 -11
  102. package/src/components/chat/tools/adapter-claude/WriteTool.scss +21 -69
  103. package/src/components/chat/tools/adapter-claude/WriteTool.tsx +22 -58
  104. package/src/components/chat/tools/adapter-claude/index.ts +4 -10
  105. package/src/components/chat/tools/adapter-claude/utils.ts +54 -0
  106. package/src/components/chat/tools/core/ToolCallBox.scss +356 -0
  107. package/src/components/chat/{ToolGroup.tsx → tools/core/ToolGroup.tsx} +26 -7
  108. package/src/components/chat/{ToolRenderer.tsx → tools/core/ToolRenderer.tsx} +6 -4
  109. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.scss +11 -0
  110. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +75 -0
  111. package/src/components/chat/tools/plugin-chrome-devtools/index.ts +45 -0
  112. package/src/components/chat/tools/task/GetTaskInfoTool.scss +2 -27
  113. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +48 -38
  114. package/src/components/chat/tools/task/ListTasksTool.scss +3 -28
  115. package/src/components/chat/tools/task/ListTasksTool.tsx +11 -8
  116. package/src/components/chat/tools/task/StartTasksTool.scss +3 -28
  117. package/src/components/chat/tools/task/StartTasksTool.tsx +14 -17
  118. package/src/components/chat/tools/task/components/TaskRow.scss +105 -0
  119. package/src/components/chat/tools/task/components/TaskRow.tsx +163 -0
  120. package/src/components/chat/tools/task/components/TaskToolCard.scss +15 -15
  121. package/src/components/chat/tools/task/components/TaskToolCard.tsx +8 -6
  122. package/src/components/config/AppSettingsPanel.tsx +33 -0
  123. package/src/components/config/ConfigSectionForm.tsx +12 -1
  124. package/src/components/config/channelDefinitions.ts +6 -0
  125. package/src/components/config/configSchema.ts +10 -1
  126. package/src/components/config/recordEditors/ChannelRecordEditor.scss +1 -0
  127. package/src/components/config/recordEditors/ChannelRecordEditor.tsx +397 -0
  128. package/src/components/config/recordEditors/index.tsx +1 -0
  129. package/src/components/knowledge-base/KnowledgeBaseView.tsx +51 -3
  130. package/src/components/knowledge-base/components/RuleItem.tsx +79 -0
  131. package/src/components/knowledge-base/components/RuleList.scss +5 -0
  132. package/src/components/knowledge-base/components/RuleList.tsx +70 -0
  133. package/src/components/knowledge-base/components/RulesTab.tsx +32 -7
  134. package/src/components/knowledge-base/components/SpecItem.tsx +1 -1
  135. package/src/hooks/chat/use-chat-interaction.ts +26 -0
  136. package/src/{components/chat/useChatModels.tsx → hooks/chat/use-chat-models.tsx} +65 -16
  137. package/src/hooks/chat/use-chat-permission-mode.ts +47 -0
  138. package/src/hooks/chat/use-chat-scroll.ts +51 -0
  139. package/src/hooks/chat/use-chat-session-actions.ts +147 -0
  140. package/src/hooks/chat/use-chat-session-messages.ts +250 -0
  141. package/src/hooks/chat/use-chat-session.ts +57 -0
  142. package/src/hooks/chat/use-chat-view.ts +39 -0
  143. package/src/main.tsx +10 -13
  144. package/src/resources/locales/en.json +73 -0
  145. package/src/resources/locales/zh.json +73 -0
  146. package/src/runtime-config.ts +52 -0
  147. package/src/store/index.ts +2 -0
  148. package/src/vite-env.d.ts +11 -0
  149. package/src/ws.ts +5 -3
  150. package/vite.config.ts +12 -4
  151. package/dist/assets/channel-DrWdSpqV.js +0 -1
  152. package/dist/assets/clone-D0cC8LLB.js +0 -1
  153. package/dist/assets/flowDiagram-v2-4f6560a1-Bf_DH7dp.js +0 -1
  154. package/dist/assets/index-CNMzWvKV.js +0 -497
  155. package/dist/assets/index-PEmISxiy.css +0 -1
  156. package/src/components/chat/ToolCallBox.scss +0 -137
  157. package/src/components/chat/useChatSession.ts +0 -370
  158. /package/src/components/{chat/CodeBlock.scss → CodeBlock.scss} +0 -0
  159. /package/src/components/chat/{MessageFooter.tsx → Messages/MessageFooter.tsx} +0 -0
  160. /package/src/components/chat/{CompletionMenu.scss → Sender/CompletionMenu.scss} +0 -0
  161. /package/src/components/chat/{CompletionMenu.tsx → Sender/CompletionMenu.tsx} +0 -0
  162. /package/src/components/chat/{ThinkingStatus.scss → Sender/ThinkingStatus.scss} +0 -0
  163. /package/src/components/chat/{ThinkingStatus.tsx → Sender/ThinkingStatus.tsx} +0 -0
  164. /package/src/components/chat/{ToolCallBox.tsx → tools/core/ToolCallBox.tsx} +0 -0
  165. /package/src/components/chat/{ToolGroup.scss → tools/core/ToolGroup.scss} +0 -0
  166. /package/src/{components/chat/safeSerialize.ts → utils/safe-serialize.ts} +0 -0
@@ -1,57 +1,2 @@
1
1
  .read-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
- .file-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
- }
31
-
32
- .file-path {
33
- font-size: 12px;
34
- color: var(--sub-text-color);
35
- white-space: nowrap;
36
- overflow: hidden;
37
- text-overflow: ellipsis;
38
- display: flex;
39
- align-items: center;
40
- min-width: 0;
41
- flex: 1;
42
- line-height: 20px;
43
-
44
- &::before {
45
- content: '—';
46
- margin: 0 8px;
47
- color: var(--border-color);
48
- font-weight: 300;
49
- }
50
- }
51
- .reading-placeholder {
52
- padding: 8px;
53
- color: var(--sub-text-color);
54
- font-size: 12px;
55
- }
56
- }
57
2
  }
@@ -1,43 +1,20 @@
1
- import React from 'react'
2
1
  import './ReadTool.scss'
2
+ import React from 'react'
3
3
  import { useTranslation } from 'react-i18next'
4
-
4
+ import type { ToolInputs } from '@vibe-forge/core'
5
+ import { CodeBlock } from '#~/components/CodeBlock'
6
+ import { ToolCallBox } from '../core/ToolCallBox'
7
+ import { safeJsonStringify } from '#~/utils/safe-serialize'
5
8
  import { defineToolRender } from '../defineToolRender'
6
- import { CodeBlock } from '../../CodeBlock'
7
- import { ToolCallBox } from '../../ToolCallBox'
8
- import { safeJsonStringify } from '../../safeSerialize'
9
+ import { getFileInfo, getLanguageFromPath } from './utils'
9
10
 
10
11
  export const ReadTool = defineToolRender(({ item, resultItem }) => {
11
12
  const { t } = useTranslation()
12
- const input = (item.input != null ? item.input : {}) as { file_path?: string }
13
- const filePath = (input.file_path != null && input.file_path !== '') ? input.file_path : 'unknown file'
14
- const lastPart = filePath.split('/').pop()
15
- const fileName = (lastPart != null && lastPart !== '') ? lastPart : filePath
16
- const dirPath = filePath.includes('/') ? filePath.substring(0, filePath.lastIndexOf('/')) : ''
17
-
18
- const getLanguage = (path: string) => {
19
- const extPart = path.split('.').pop()
20
- const ext = (extPart != null && extPart !== '') ? extPart.toLowerCase() : ''
21
- const langMap: Record<string, string> = {
22
- 'js': 'javascript',
23
- 'jsx': 'jsx',
24
- 'ts': 'typescript',
25
- 'tsx': 'tsx',
26
- 'py': 'python',
27
- 'md': 'markdown',
28
- 'json': 'json',
29
- 'scss': 'scss',
30
- 'css': 'css',
31
- 'html': 'html',
32
- 'sh': 'bash',
33
- 'yml': 'yaml',
34
- 'yaml': 'yaml',
35
- 'sql': 'sql'
36
- }
37
- return langMap[ext] || 'text'
38
- }
39
-
40
- const language = getLanguage(filePath)
13
+ const input = (item.input != null ? item.input : {}) as ToolInputs['adapter:claude-code:Read']
14
+ const { filePath } = getFileInfo(input.file_path)
15
+ const language = getLanguageFromPath(filePath)
16
+ const offset = input.offset
17
+ const limit = input.limit
41
18
 
42
19
  const cleanContent = (content: any): string => {
43
20
  if (typeof content !== 'string') return safeJsonStringify(content, 2)
@@ -53,24 +30,25 @@ export const ReadTool = defineToolRender(({ item, resultItem }) => {
53
30
  <ToolCallBox
54
31
  header={
55
32
  <div className='tool-header-content'>
56
- <span className='material-symbols-rounded'>description</span>
57
- <span className='command-name'>{t('chat.tools.read')}</span>
58
- <span className='file-name'>{fileName}</span>
33
+ <span className='material-symbols-rounded tool-header-icon'>visibility</span>
34
+ <span className='tool-header-primary tool-header-mono tool-header-row-text'>{filePath}</span>
35
+ {(typeof offset === 'number' && Number.isFinite(offset)) && (
36
+ <span className='tool-header-chip'>{t('chat.tools.offset')}:{offset}</span>
37
+ )}
38
+ {(typeof limit === 'number' && Number.isFinite(limit)) && (
39
+ <span className='tool-header-chip'>{t('chat.tools.limit')}:{limit}</span>
40
+ )}
59
41
  </div>
60
42
  }
61
43
  content={
62
44
  <div className='tool-content'>
63
- {dirPath && (
64
- <div className='file-path'>
65
- {dirPath}
66
- </div>
67
- )}
68
45
  {resultItem
69
46
  ? (
70
47
  <div className='result-content'>
71
48
  <CodeBlock
72
49
  code={cleanContent(resultItem.content)}
73
50
  lang={language}
51
+ hideHeader={true}
74
52
  />
75
53
  </div>
76
54
  )
@@ -1,34 +1,15 @@
1
1
  .todo-tool {
2
- .todo-header {
3
- display: flex;
4
- align-items: center;
5
- gap: 8px;
6
-
7
- .status-icon {
8
- font-size: 18px;
9
- }
10
-
11
- .todo-title {
12
- font-size: 13px;
13
- font-weight: 500;
14
- }
15
- }
16
-
17
- .tool-content {
18
- padding: 4px 0;
19
- }
20
-
21
2
  .todo-item {
22
3
  display: flex;
23
4
  align-items: flex-start;
24
5
  gap: 8px;
25
6
  margin-bottom: 2px;
26
- padding: 6px 12px;
7
+ padding: 6px 4px;
27
8
  border-radius: 0;
28
9
  transition: background-color .2s;
29
10
 
30
11
  &:hover {
31
- background-color: rgba(0, 0, 0, .04);
12
+ background-color: var(--bg-color-hover);
32
13
  }
33
14
 
34
15
  &:last-child {
@@ -43,13 +24,13 @@
43
24
  }
44
25
 
45
26
  .status-icon {
46
- color: #10b981;
27
+ color: var(--success-color);
47
28
  }
48
29
  }
49
30
 
50
31
  &.in_progress {
51
32
  .status-icon {
52
- color: #3b82f6;
33
+ color: var(--primary-color);
53
34
  }
54
35
  }
55
36
 
@@ -59,6 +40,10 @@
59
40
  }
60
41
  }
61
42
 
43
+ .status-icon {
44
+ font-size: 16px;
45
+ }
46
+
62
47
  .todo-info {
63
48
  display: flex;
64
49
  flex-direction: column;
@@ -1,29 +1,42 @@
1
1
  import './TodoTool.scss'
2
2
  import React from 'react'
3
3
  import { useTranslation } from 'react-i18next'
4
+ import type { ToolInputs } from '@vibe-forge/core'
4
5
 
6
+ import { ToolCallBox } from '../core/ToolCallBox'
5
7
  import { defineToolRender } from '../defineToolRender'
6
- import { ToolCallBox } from '../../ToolCallBox'
7
8
 
8
- interface TodoItem {
9
- content: string
10
- status: 'pending' | 'in_progress' | 'completed'
11
- activeForm?: string
12
- }
9
+ type TodoItem = ToolInputs['adapter:claude-code:TodoWrite']['todos'][number]
13
10
 
14
11
  export const TodoTool = defineToolRender(({ item }) => {
15
12
  const { t } = useTranslation()
16
- const input = (item.input != null ? item.input : {}) as { todos?: TodoItem[] }
17
- const todos = input.todos ?? []
13
+ const input = (item.input != null ? item.input : {}) as Partial<ToolInputs['adapter:claude-code:TodoWrite']>
14
+ const todos = (input.todos ?? []) as TodoItem[]
15
+ const totalCount = todos.length
16
+ const completedCount = todos.filter(todo => todo.status === 'completed').length
17
+ const inProgressCount = todos.filter(todo => todo.status === 'in_progress').length
18
+ const pendingCount = totalCount - completedCount - inProgressCount
18
19
 
19
20
  return (
20
21
  <div className='tool-group todo-tool'>
21
22
  <ToolCallBox
22
23
  defaultExpanded={true}
23
24
  header={
24
- <div className='todo-header'>
25
- <span className='material-symbols-rounded status-icon'>task_alt</span>
26
- <span className='todo-title'>{t('chat.tools.todo')}</span>
25
+ <div className='tool-header-content'>
26
+ <span className='material-symbols-rounded tool-header-icon'>task_alt</span>
27
+ <span className='tool-header-title'>{t('chat.tools.todo')}</span>
28
+ {totalCount > 0 && (
29
+ <span className='tool-header-chip'>{totalCount} total</span>
30
+ )}
31
+ {inProgressCount > 0 && (
32
+ <span className='tool-header-chip'>{inProgressCount} doing</span>
33
+ )}
34
+ {pendingCount > 0 && (
35
+ <span className='tool-header-chip'>{pendingCount} todo</span>
36
+ )}
37
+ {completedCount > 0 && (
38
+ <span className='tool-header-chip'>{completedCount} done</span>
39
+ )}
27
40
  </div>
28
41
  }
29
42
  content={
@@ -1,81 +1,39 @@
1
1
  .write-tool {
2
- .tool-header-content {
2
+ .write-tool__header {
3
+ width: 100%;
4
+ min-width: 0;
3
5
  display: flex;
4
6
  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
- .file-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
- }
31
-
32
- .file-path {
33
- font-size: 12px;
34
- color: var(--sub-text-color);
35
- white-space: nowrap;
36
- overflow: hidden;
37
- text-overflow: ellipsis;
38
- display: flex;
39
- align-items: center;
40
- min-width: 0;
41
- flex: 1;
42
- line-height: 20px;
43
-
44
- &::before {
45
- content: '—';
46
- margin: 0 8px;
47
- color: var(--border-color);
48
- font-weight: 300;
49
- }
50
- }
7
+ justify-content: space-between;
8
+ gap: 8px;
51
9
  }
52
10
 
53
- .write-result-status {
11
+ .write-tool__header-left {
54
12
  display: flex;
55
13
  align-items: center;
56
- gap: 4px;
14
+ gap: 6px;
15
+ min-width: 0;
16
+ flex: 1;
17
+ }
57
18
 
58
- .status-icon {
59
- font-size: 14px;
60
- }
19
+ .write-tool__status-icon {
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ width: 16px;
24
+ height: 16px;
25
+ flex-shrink: 0;
61
26
 
62
- .status-text {
63
- font-size: 12px;
27
+ .material-symbols-rounded {
28
+ font-size: 16px;
64
29
  }
65
30
 
66
31
  &.error {
67
- .status-icon, .status-text {
68
- color: #ef4444;
69
- }
32
+ color: var(--danger-color);
70
33
  }
71
34
 
72
35
  &.success {
73
- .status-icon {
74
- color: #10b981;
75
- }
76
- .status-text {
77
- color: #059669;
78
- }
36
+ color: var(--success-color);
79
37
  }
80
38
  }
81
39
 
@@ -83,10 +41,4 @@
83
41
  display: flex;
84
42
  flex-direction: column;
85
43
  }
86
-
87
- .write-result-status {
88
- display: flex;
89
- align-items: center;
90
- gap: 4px;
91
- }
92
44
  }
@@ -1,59 +1,40 @@
1
- import React from 'react'
2
1
  import './WriteTool.scss'
3
- import { useTranslation } from 'react-i18next'
4
-
2
+ import React from 'react'
3
+ import type { ToolInputs } from '@vibe-forge/core'
4
+ import { CodeBlock } from '#~/components/CodeBlock'
5
+ import { ToolCallBox } from '../core/ToolCallBox'
5
6
  import { defineToolRender } from '../defineToolRender'
6
- import { CodeBlock } from '../../CodeBlock'
7
- import { ToolCallBox } from '../../ToolCallBox'
7
+ import { getFileInfo, getLanguageFromPath } from './utils'
8
8
 
9
9
  export const WriteTool = defineToolRender(({ item, resultItem }) => {
10
- const { t } = useTranslation()
11
- const input = (item.input != null ? item.input : {}) as { file_path?: string; content?: string }
12
- const filePath = (input.file_path != null && input.file_path !== '') ? input.file_path : 'unknown file'
13
- const lastPart = filePath.split('/').pop()
14
- const fileName = (lastPart != null && lastPart !== '') ? lastPart : filePath
15
- const dirPath = filePath.includes('/') ? filePath.substring(0, filePath.lastIndexOf('/')) : ''
10
+ const input = (item.input != null ? item.input : {}) as ToolInputs['adapter:claude-code:Write']
11
+ const { filePath } = getFileInfo(input.file_path)
16
12
  const content = input.content ?? ''
17
-
18
- const getLanguage = (path: string) => {
19
- const extPart = path.split('.').pop()
20
- const ext = (extPart != null && extPart !== '') ? extPart.toLowerCase() : ''
21
- const langMap: Record<string, string> = {
22
- 'js': 'javascript',
23
- 'jsx': 'jsx',
24
- 'ts': 'typescript',
25
- 'tsx': 'tsx',
26
- 'py': 'python',
27
- 'md': 'markdown',
28
- 'json': 'json',
29
- 'scss': 'scss',
30
- 'css': 'css',
31
- 'html': 'html',
32
- 'sh': 'bash',
33
- 'yml': 'yaml',
34
- 'yaml': 'yaml',
35
- 'sql': 'sql'
36
- }
37
- return langMap[ext] || 'text'
38
- }
39
-
40
- const language = getLanguage(filePath)
13
+ const language = getLanguageFromPath(filePath)
41
14
 
42
15
  return (
43
16
  <div className='tool-group write-tool'>
44
17
  <ToolCallBox
45
18
  defaultExpanded={false}
46
19
  header={
47
- <div className='tool-header-content'>
48
- <span className='material-symbols-rounded'>edit_note</span>
49
- <span className='file-name'>{fileName}</span>
50
- {(dirPath != null && dirPath !== '') && <span className='file-path'>{dirPath}</span>}
20
+ <div className='write-tool__header'>
21
+ <div className='write-tool__header-left'>
22
+ <span className='material-symbols-rounded tool-header-icon'>edit_note</span>
23
+ <span className='tool-header-primary tool-header-mono tool-header-row-text'>{filePath}</span>
24
+ </div>
25
+ {resultItem != null && (
26
+ <span className={`write-tool__status-icon ${resultItem.is_error ? 'error' : 'success'}`}>
27
+ <span className='material-symbols-rounded'>
28
+ {resultItem.is_error === true ? 'error' : 'check_circle'}
29
+ </span>
30
+ </span>
31
+ )}
51
32
  </div>
52
33
  }
53
34
  content={
54
35
  <div className='tool-content'>
55
- <div className='bash-content-scroll'>
56
- <div className='bash-code-wrapper'>
36
+ <div className='tool-scroll'>
37
+ <div className='tool-code-wrapper'>
57
38
  <CodeBlock
58
39
  code={content}
59
40
  lang={language}
@@ -64,23 +45,6 @@ export const WriteTool = defineToolRender(({ item, resultItem }) => {
64
45
  </div>
65
46
  }
66
47
  />
67
- {resultItem != null && (
68
- <ToolCallBox
69
- type='result'
70
- isError={resultItem.is_error}
71
- header={
72
- <div className={`write-result-status ${resultItem.is_error ? 'error' : 'success'}`}>
73
- <span className='material-symbols-rounded status-icon'>
74
- {resultItem.is_error === true ? 'error' : 'check_circle'}
75
- </span>
76
- <span className='status-text'>
77
- {resultItem.is_error === true ? t('chat.tools.writeFailed') : t('chat.tools.writeSuccess')}
78
- </span>
79
- </div>
80
- }
81
- content={null}
82
- />
83
- )}
84
48
  </div>
85
49
  )
86
50
  })
@@ -1,3 +1,5 @@
1
+ import type {} from '@vibe-forge/adapter-claude-code/schema'
2
+
1
3
  import { defineToolRenders } from '../defineToolRender'
2
4
  import { BashTool } from './BashTool'
3
5
  import { GlobTool } from './GlobTool'
@@ -15,14 +17,6 @@ export const adapterClaudeToolRenders = defineToolRenders({
15
17
  Read: ReadTool,
16
18
  Write: WriteTool,
17
19
  TodoWrite: TodoTool
18
- })
20
+ }, { namespace: 'adapter:claude-code:' })
19
21
 
20
- export {
21
- BashTool,
22
- GlobTool,
23
- GrepTool,
24
- LsTool,
25
- ReadTool,
26
- TodoTool,
27
- WriteTool
28
- }
22
+ export { BashTool, GlobTool, GrepTool, LsTool, ReadTool, TodoTool, WriteTool }
@@ -0,0 +1,54 @@
1
+ export function getFileInfo(filePath?: string) {
2
+ const resolvedPath = (filePath != null && filePath !== '') ? filePath : 'unknown file'
3
+ const lastSlashIndex = resolvedPath.lastIndexOf('/')
4
+ const fileName = lastSlashIndex >= 0 ? resolvedPath.slice(lastSlashIndex + 1) : resolvedPath
5
+ const dirPath = lastSlashIndex >= 0 ? resolvedPath.slice(0, lastSlashIndex) : ''
6
+
7
+ return { filePath: resolvedPath, fileName, dirPath }
8
+ }
9
+
10
+ export function getLanguageFromPath(path: string) {
11
+ const extPart = path.split('.').pop()
12
+ const ext = (extPart != null && extPart !== '') ? extPart.toLowerCase() : ''
13
+ const langMap: Record<string, string> = {
14
+ 'js': 'javascript',
15
+ 'jsx': 'jsx',
16
+ 'ts': 'typescript',
17
+ 'tsx': 'tsx',
18
+ 'py': 'python',
19
+ 'md': 'markdown',
20
+ 'json': 'json',
21
+ 'scss': 'scss',
22
+ 'css': 'css',
23
+ 'html': 'html',
24
+ 'sh': 'bash',
25
+ 'yml': 'yaml',
26
+ 'yaml': 'yaml',
27
+ 'sql': 'sql'
28
+ }
29
+ return langMap[ext] || 'text'
30
+ }
31
+
32
+ export function normalizeResultLines(content: unknown): string[] {
33
+ if (Array.isArray(content)) {
34
+ return content.map(String)
35
+ }
36
+
37
+ if (typeof content !== 'string') {
38
+ return []
39
+ }
40
+
41
+ const trimmed = content.trim()
42
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
43
+ try {
44
+ const parsed = JSON.parse(trimmed)
45
+ if (Array.isArray(parsed)) {
46
+ return parsed.map(String)
47
+ }
48
+ } catch {
49
+ return content.split('\n')
50
+ }
51
+ }
52
+
53
+ return content.split('\n')
54
+ }