@vibe-forge/client 0.2.0-alpha.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 (184) hide show
  1. package/LICENSE +21 -0
  2. package/cli.cjs +6 -0
  3. package/index.html +27 -0
  4. package/package.json +42 -0
  5. package/src/App.tsx +174 -0
  6. package/src/api.ts +241 -0
  7. package/src/components/ArchiveView.scss +168 -0
  8. package/src/components/ArchiveView.tsx +299 -0
  9. package/src/components/AutomationView/AutomationView.scss +26 -0
  10. package/src/components/AutomationView/RuleFormPanel.scss +129 -0
  11. package/src/components/AutomationView/RuleFormPanel.tsx +257 -0
  12. package/src/components/AutomationView/RuleSidebar.scss +219 -0
  13. package/src/components/AutomationView/RuleSidebar.tsx +258 -0
  14. package/src/components/AutomationView/RunHistoryPanel.scss +286 -0
  15. package/src/components/AutomationView/RunHistoryPanel.tsx +320 -0
  16. package/src/components/AutomationView/TaskList.scss +128 -0
  17. package/src/components/AutomationView/TaskList.tsx +79 -0
  18. package/src/components/AutomationView/TriggerList.scss +153 -0
  19. package/src/components/AutomationView/TriggerList.tsx +217 -0
  20. package/src/components/AutomationView/index.tsx +228 -0
  21. package/src/components/AutomationView/types.ts +21 -0
  22. package/src/components/Chat.scss +89 -0
  23. package/src/components/Chat.tsx +92 -0
  24. package/src/components/ConfigView.scss +185 -0
  25. package/src/components/ConfigView.tsx +258 -0
  26. package/src/components/NavRail.scss +71 -0
  27. package/src/components/NavRail.tsx +188 -0
  28. package/src/components/Sidebar.scss +112 -0
  29. package/src/components/Sidebar.tsx +291 -0
  30. package/src/components/chat/ChatHeader.scss +401 -0
  31. package/src/components/chat/ChatHeader.tsx +342 -0
  32. package/src/components/chat/ChatHistoryView.tsx +122 -0
  33. package/src/components/chat/ChatSettingsView.tsx +22 -0
  34. package/src/components/chat/ChatTimelineView.scss +53 -0
  35. package/src/components/chat/ChatTimelineView.tsx +158 -0
  36. package/src/components/chat/CodeBlock.scss +87 -0
  37. package/src/components/chat/CodeBlock.tsx +179 -0
  38. package/src/components/chat/CompletionMenu.scss +70 -0
  39. package/src/components/chat/CompletionMenu.tsx +58 -0
  40. package/src/components/chat/CurrentTodoList.scss +217 -0
  41. package/src/components/chat/CurrentTodoList.tsx +103 -0
  42. package/src/components/chat/MarkdownContent.tsx +43 -0
  43. package/src/components/chat/MessageFooter.tsx +48 -0
  44. package/src/components/chat/MessageItem.scss +251 -0
  45. package/src/components/chat/MessageItem.tsx +78 -0
  46. package/src/components/chat/NewSessionGuide.scss +186 -0
  47. package/src/components/chat/NewSessionGuide.tsx +167 -0
  48. package/src/components/chat/Sender.scss +367 -0
  49. package/src/components/chat/Sender.tsx +541 -0
  50. package/src/components/chat/SessionTimelinePanel/EventList.scss +58 -0
  51. package/src/components/chat/SessionTimelinePanel/EventList.tsx +212 -0
  52. package/src/components/chat/SessionTimelinePanel/gantt.ts +177 -0
  53. package/src/components/chat/SessionTimelinePanel/git-graph.ts +518 -0
  54. package/src/components/chat/SessionTimelinePanel/index.scss +28 -0
  55. package/src/components/chat/SessionTimelinePanel/index.tsx +121 -0
  56. package/src/components/chat/SessionTimelinePanel/mermaid.ts +4 -0
  57. package/src/components/chat/SessionTimelinePanel/types.ts +64 -0
  58. package/src/components/chat/SessionTimelinePanel/utils.ts +20 -0
  59. package/src/components/chat/ThinkingStatus.scss +70 -0
  60. package/src/components/chat/ThinkingStatus.tsx +13 -0
  61. package/src/components/chat/ToolCallBox.scss +137 -0
  62. package/src/components/chat/ToolCallBox.tsx +55 -0
  63. package/src/components/chat/ToolGroup.scss +154 -0
  64. package/src/components/chat/ToolGroup.tsx +102 -0
  65. package/src/components/chat/ToolRenderer.tsx +45 -0
  66. package/src/components/chat/messageUtils.ts +171 -0
  67. package/src/components/chat/safeSerialize.ts +84 -0
  68. package/src/components/chat/tools/DefaultTool.tsx +63 -0
  69. package/src/components/chat/tools/adapter-claude/BashTool.scss +71 -0
  70. package/src/components/chat/tools/adapter-claude/BashTool.tsx +82 -0
  71. package/src/components/chat/tools/adapter-claude/GlobTool.scss +88 -0
  72. package/src/components/chat/tools/adapter-claude/GlobTool.tsx +85 -0
  73. package/src/components/chat/tools/adapter-claude/GrepTool.scss +96 -0
  74. package/src/components/chat/tools/adapter-claude/GrepTool.tsx +114 -0
  75. package/src/components/chat/tools/adapter-claude/LSTool.scss +85 -0
  76. package/src/components/chat/tools/adapter-claude/LSTool.tsx +94 -0
  77. package/src/components/chat/tools/adapter-claude/ReadTool.scss +57 -0
  78. package/src/components/chat/tools/adapter-claude/ReadTool.tsx +87 -0
  79. package/src/components/chat/tools/adapter-claude/TodoTool.scss +78 -0
  80. package/src/components/chat/tools/adapter-claude/TodoTool.tsx +60 -0
  81. package/src/components/chat/tools/adapter-claude/WriteTool.scss +92 -0
  82. package/src/components/chat/tools/adapter-claude/WriteTool.tsx +86 -0
  83. package/src/components/chat/tools/adapter-claude/components/FileList.scss +65 -0
  84. package/src/components/chat/tools/adapter-claude/components/FileList.tsx +185 -0
  85. package/src/components/chat/tools/adapter-claude/index.ts +28 -0
  86. package/src/components/chat/tools/defineToolRender.ts +28 -0
  87. package/src/components/chat/tools/task/GetTaskInfoTool.scss +50 -0
  88. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +88 -0
  89. package/src/components/chat/tools/task/ListTasksTool.scss +56 -0
  90. package/src/components/chat/tools/task/ListTasksTool.tsx +83 -0
  91. package/src/components/chat/tools/task/StartTasksTool.scss +56 -0
  92. package/src/components/chat/tools/task/StartTasksTool.tsx +96 -0
  93. package/src/components/chat/tools/task/components/TaskToolCard.scss +127 -0
  94. package/src/components/chat/tools/task/components/TaskToolCard.tsx +177 -0
  95. package/src/components/chat/tools/task/index.ts +15 -0
  96. package/src/components/chat/useChatModels.tsx +206 -0
  97. package/src/components/chat/useChatSession.ts +370 -0
  98. package/src/components/config/ConfigAboutSection.scss +111 -0
  99. package/src/components/config/ConfigAboutSection.tsx +86 -0
  100. package/src/components/config/ConfigDisplayValue.scss +22 -0
  101. package/src/components/config/ConfigDisplayValue.tsx +62 -0
  102. package/src/components/config/ConfigEditors.scss +65 -0
  103. package/src/components/config/ConfigEditors.tsx +98 -0
  104. package/src/components/config/ConfigFieldRow.scss +97 -0
  105. package/src/components/config/ConfigFieldRow.tsx +36 -0
  106. package/src/components/config/ConfigSectionForm.scss +94 -0
  107. package/src/components/config/ConfigSectionForm.tsx +436 -0
  108. package/src/components/config/ConfigSectionPanel.tsx +67 -0
  109. package/src/components/config/ConfigShortcutInput.scss +11 -0
  110. package/src/components/config/ConfigShortcutInput.tsx +52 -0
  111. package/src/components/config/ConfigSourceSwitch.tsx +57 -0
  112. package/src/components/config/configSchema.ts +319 -0
  113. package/src/components/config/configUtils.ts +83 -0
  114. package/src/components/config/index.tsx +5 -0
  115. package/src/components/config/recordEditors/BooleanRecordEditor.scss +1 -0
  116. package/src/components/config/recordEditors/BooleanRecordEditor.tsx +75 -0
  117. package/src/components/config/recordEditors/KeyValueEditor.scss +1 -0
  118. package/src/components/config/recordEditors/KeyValueEditor.tsx +97 -0
  119. package/src/components/config/recordEditors/McpServersRecordEditor.scss +1 -0
  120. package/src/components/config/recordEditors/McpServersRecordEditor.tsx +258 -0
  121. package/src/components/config/recordEditors/ModelServicesRecordEditor.scss +1 -0
  122. package/src/components/config/recordEditors/ModelServicesRecordEditor.tsx +233 -0
  123. package/src/components/config/recordEditors/RecordEditors.scss +117 -0
  124. package/src/components/config/recordEditors/RecordJsonEditor.scss +1 -0
  125. package/src/components/config/recordEditors/RecordJsonEditor.tsx +113 -0
  126. package/src/components/config/recordEditors/index.tsx +5 -0
  127. package/src/components/knowledge-base/KnowledgeBaseView.scss +19 -0
  128. package/src/components/knowledge-base/KnowledgeBaseView.tsx +186 -0
  129. package/src/components/knowledge-base/components/ActionButton.scss +5 -0
  130. package/src/components/knowledge-base/components/ActionButton.tsx +9 -0
  131. package/src/components/knowledge-base/components/EmptyState.scss +19 -0
  132. package/src/components/knowledge-base/components/EmptyState.tsx +42 -0
  133. package/src/components/knowledge-base/components/EntitiesTab.scss +5 -0
  134. package/src/components/knowledge-base/components/EntitiesTab.tsx +80 -0
  135. package/src/components/knowledge-base/components/EntityItem.scss +82 -0
  136. package/src/components/knowledge-base/components/EntityItem.tsx +79 -0
  137. package/src/components/knowledge-base/components/EntityList.scss +5 -0
  138. package/src/components/knowledge-base/components/EntityList.tsx +70 -0
  139. package/src/components/knowledge-base/components/FilterBar.scss +21 -0
  140. package/src/components/knowledge-base/components/FilterBar.tsx +51 -0
  141. package/src/components/knowledge-base/components/FlowsTab.scss +5 -0
  142. package/src/components/knowledge-base/components/FlowsTab.tsx +80 -0
  143. package/src/components/knowledge-base/components/KnowledgeBaseHeader.scss +27 -0
  144. package/src/components/knowledge-base/components/KnowledgeBaseHeader.tsx +29 -0
  145. package/src/components/knowledge-base/components/KnowledgeList.scss +19 -0
  146. package/src/components/knowledge-base/components/KnowledgeList.tsx +19 -0
  147. package/src/components/knowledge-base/components/LoadingState.scss +5 -0
  148. package/src/components/knowledge-base/components/LoadingState.tsx +11 -0
  149. package/src/components/knowledge-base/components/MetaList.scss +19 -0
  150. package/src/components/knowledge-base/components/MetaList.tsx +18 -0
  151. package/src/components/knowledge-base/components/RulesTab.scss +5 -0
  152. package/src/components/knowledge-base/components/RulesTab.tsx +49 -0
  153. package/src/components/knowledge-base/components/SectionHeader.scss +22 -0
  154. package/src/components/knowledge-base/components/SectionHeader.tsx +21 -0
  155. package/src/components/knowledge-base/components/SkillsTab.scss +5 -0
  156. package/src/components/knowledge-base/components/SkillsTab.tsx +49 -0
  157. package/src/components/knowledge-base/components/SpecItem.scss +138 -0
  158. package/src/components/knowledge-base/components/SpecItem.tsx +131 -0
  159. package/src/components/knowledge-base/components/SpecList.scss +5 -0
  160. package/src/components/knowledge-base/components/SpecList.tsx +70 -0
  161. package/src/components/knowledge-base/components/TabContent.scss +8 -0
  162. package/src/components/knowledge-base/components/TabContent.tsx +17 -0
  163. package/src/components/knowledge-base/components/TabLabel.scss +10 -0
  164. package/src/components/knowledge-base/components/TabLabel.tsx +15 -0
  165. package/src/components/knowledge-base/index.tsx +1 -0
  166. package/src/components/sidebar/SessionItem.scss +256 -0
  167. package/src/components/sidebar/SessionItem.tsx +265 -0
  168. package/src/components/sidebar/SessionList.scss +92 -0
  169. package/src/components/sidebar/SessionList.tsx +166 -0
  170. package/src/components/sidebar/SidebarHeader.scss +79 -0
  171. package/src/components/sidebar/SidebarHeader.tsx +128 -0
  172. package/src/connectionManager.ts +172 -0
  173. package/src/hooks/useGlobalShortcut.ts +26 -0
  174. package/src/hooks/useQueryParams.ts +54 -0
  175. package/src/i18n.ts +22 -0
  176. package/src/main.tsx +41 -0
  177. package/src/resources/locales/en.json +765 -0
  178. package/src/resources/locales/zh.json +766 -0
  179. package/src/store/index.ts +23 -0
  180. package/src/styles/global.scss +100 -0
  181. package/src/utils/shortcutUtils.ts +88 -0
  182. package/src/vite-env.d.ts +12 -0
  183. package/src/ws.ts +33 -0
  184. package/vite.config.ts +26 -0
@@ -0,0 +1,96 @@
1
+ .grep-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-grid {
56
+ display: flex;
57
+ flex-wrap: wrap;
58
+ gap: 12px;
59
+ padding: 8px 12px;
60
+ border-bottom: 1px solid var(--border-color);
61
+
62
+ .input-detail-item {
63
+ display: flex;
64
+ align-items: center;
65
+ font-size: 12px;
66
+ color: var(--sub-text-color);
67
+
68
+ .label {
69
+ font-weight: 500;
70
+ margin-right: 6px;
71
+ }
72
+
73
+ .value {
74
+ font-family: var(--font-mono);
75
+ background: var(--bg-color-hover);
76
+ padding: 2px 4px;
77
+ border-radius: 4px;
78
+ }
79
+ }
80
+ }
81
+
82
+ .tool-placeholder {
83
+ padding: 8px 12px;
84
+ color: var(--sub-text-color);
85
+ font-size: 12px;
86
+ font-style: italic;
87
+ }
88
+
89
+ .file-list-container {
90
+ border-radius: 0;
91
+ border-left: none;
92
+ border-right: none;
93
+ border-bottom: none;
94
+ border: none;
95
+ }
96
+ }
@@ -0,0 +1,114 @@
1
+ import React, { useMemo } from 'react'
2
+ import './GrepTool.scss'
3
+ import { useTranslation } from 'react-i18next'
4
+
5
+ import { defineToolRender } from '../defineToolRender'
6
+ import { CodeBlock } from '../../CodeBlock'
7
+ import { ToolCallBox } from '../../ToolCallBox'
8
+ import { safeJsonStringify } from '../../safeSerialize'
9
+ import { FileList } from './components/FileList'
10
+
11
+ export const GrepTool = defineToolRender(({ item, resultItem }) => {
12
+ const { t } = useTranslation()
13
+ const input = (item.input != null ? item.input : {}) as {
14
+ pattern?: string
15
+ path?: string
16
+ output_mode?: string
17
+ glob?: string
18
+ }
19
+ const pattern = (input.pattern != null && input.pattern !== '') ? input.pattern : ''
20
+ const path = input.path
21
+ const outputMode = input.output_mode || 'files_with_matches'
22
+ const fileGlob = input.glob
23
+
24
+ const fileCount = useMemo(() => {
25
+ if (!resultItem) return null
26
+ const content = resultItem.content
27
+ let lines: string[] = []
28
+
29
+ if (typeof content === 'string') {
30
+ if (content.trim().startsWith('[') && content.trim().endsWith(']')) {
31
+ try {
32
+ const parsed = JSON.parse(content)
33
+ if (Array.isArray(parsed)) {
34
+ lines = parsed.map(String)
35
+ } else {
36
+ lines = content.split('\n')
37
+ }
38
+ } catch (e) {
39
+ lines = content.split('\n')
40
+ }
41
+ } else {
42
+ lines = content.split('\n')
43
+ }
44
+ } else if (Array.isArray(content)) {
45
+ lines = content.map(String)
46
+ }
47
+
48
+ const count = lines.filter(line => line.trim() !== '').length
49
+ return count
50
+ }, [resultItem])
51
+
52
+ return (
53
+ <div className='tool-group grep-tool'>
54
+ <ToolCallBox
55
+ header={
56
+ <div className='tool-header-content'>
57
+ <span className='material-symbols-rounded'>find_in_page</span>
58
+ <span className='command-name'>Grep</span>
59
+ <span className='pattern'>{pattern}</span>
60
+ {fileCount !== null && (
61
+ <span className='file-count'>({fileCount} matches)</span>
62
+ )}
63
+ </div>
64
+ }
65
+ content={
66
+ <div className='tool-content'>
67
+ {(path || fileGlob) && (
68
+ <div className='input-details-grid'>
69
+ {path && (
70
+ <div className='input-detail-item'>
71
+ <span className='label'>Path:</span>
72
+ <span className='value'>{path}</span>
73
+ </div>
74
+ )}
75
+ {fileGlob && (
76
+ <div className='input-detail-item'>
77
+ <span className='label'>Glob:</span>
78
+ <span className='value'>{fileGlob}</span>
79
+ </div>
80
+ )}
81
+ </div>
82
+ )}
83
+ {resultItem
84
+ ? (
85
+ <div className='result-content'>
86
+ {outputMode === 'files_with_matches'
87
+ ? (
88
+ <FileList
89
+ content={typeof resultItem.content === 'string'
90
+ ? resultItem.content
91
+ : safeJsonStringify(resultItem.content)}
92
+ />
93
+ )
94
+ : (
95
+ <CodeBlock
96
+ code={typeof resultItem.content === 'string'
97
+ ? resultItem.content
98
+ : safeJsonStringify(resultItem.content, 2)}
99
+ lang='text'
100
+ />
101
+ )}
102
+ </div>
103
+ )
104
+ : (
105
+ <div className='tool-placeholder'>
106
+ Searching...
107
+ </div>
108
+ )}
109
+ </div>
110
+ }
111
+ />
112
+ </div>
113
+ )
114
+ })
@@ -0,0 +1,85 @@
1
+ .ls-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
+ .path {
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
+ code {
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
+ .file-list-container {
82
+ border-radius: 0;
83
+ border: none;
84
+ }
85
+ }
@@ -0,0 +1,94 @@
1
+ import React, { useMemo } from 'react'
2
+ import './LsTool.scss'
3
+ import { useTranslation } from 'react-i18next'
4
+
5
+ import { defineToolRender } from '../defineToolRender'
6
+ import { CodeBlock } from '../../CodeBlock'
7
+ import { ToolCallBox } from '../../ToolCallBox'
8
+ import { safeJsonStringify } from '../../safeSerialize'
9
+ import { FileList } from './components/FileList'
10
+
11
+ export const LsTool = defineToolRender(({ item, resultItem }) => {
12
+ const { t } = useTranslation()
13
+ const input = (item.input != null ? item.input : {}) as { path?: string; ignore?: string[] }
14
+ const path = (input.path != null && input.path !== '') ? input.path : 'current directory'
15
+ const ignore = input.ignore
16
+
17
+ const shouldShowList = (content: any) => {
18
+ if (typeof content !== 'string') return false
19
+ const lines = content.split('\n').filter(l => l.trim())
20
+ if (lines.length > 0 && lines[0].includes('drwxr')) return false
21
+ return true
22
+ }
23
+
24
+ const processContent = (content: string) => {
25
+ const lines = content.split('\n').filter(line => line.trim() !== '')
26
+ if (lines.length > 0) {
27
+ lines.pop()
28
+ }
29
+ return lines
30
+ }
31
+
32
+ const fileCount = useMemo(() => {
33
+ if (!resultItem) return null
34
+ if (typeof resultItem.content !== 'string') return null
35
+ if (!shouldShowList(resultItem.content)) return null
36
+
37
+ const lines = processContent(resultItem.content)
38
+ return lines.length
39
+ }, [resultItem])
40
+
41
+ return (
42
+ <div className='tool-group ls-tool'>
43
+ <ToolCallBox
44
+ header={
45
+ <div className='tool-header-content'>
46
+ <span className='material-symbols-rounded'>folder_open</span>
47
+ <span className='command-name'>LS</span>
48
+ <span className='path'>{path}</span>
49
+ {fileCount !== null && (
50
+ <span className='file-count'>({fileCount} files)</span>
51
+ )}
52
+ </div>
53
+ }
54
+ content={
55
+ <div className='tool-content'>
56
+ {ignore && ignore.length > 0 && (
57
+ <div className='input-details'>
58
+ <span className='label'>Ignore:</span>
59
+ <code>{JSON.stringify(ignore)}</code>
60
+ </div>
61
+ )}
62
+
63
+ {resultItem
64
+ ? (
65
+ <div className='result-content'>
66
+ {shouldShowList(resultItem.content)
67
+ ? (
68
+ <FileList
69
+ content={processContent(resultItem.content as string)}
70
+ removeRoot={true}
71
+ defaultCollapsed={true}
72
+ />
73
+ )
74
+ : (
75
+ <CodeBlock
76
+ code={typeof resultItem.content === 'string'
77
+ ? resultItem.content
78
+ : safeJsonStringify(resultItem.content, 2)}
79
+ lang='text'
80
+ />
81
+ )}
82
+ </div>
83
+ )
84
+ : (
85
+ <div className='tool-placeholder'>
86
+ Listing files...
87
+ </div>
88
+ )}
89
+ </div>
90
+ }
91
+ />
92
+ </div>
93
+ )
94
+ })
@@ -0,0 +1,57 @@
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
+ }
@@ -0,0 +1,87 @@
1
+ import React from 'react'
2
+ import './ReadTool.scss'
3
+ import { useTranslation } from 'react-i18next'
4
+
5
+ import { defineToolRender } from '../defineToolRender'
6
+ import { CodeBlock } from '../../CodeBlock'
7
+ import { ToolCallBox } from '../../ToolCallBox'
8
+ import { safeJsonStringify } from '../../safeSerialize'
9
+
10
+ export const ReadTool = defineToolRender(({ item, resultItem }) => {
11
+ 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)
41
+
42
+ const cleanContent = (content: any): string => {
43
+ if (typeof content !== 'string') return safeJsonStringify(content, 2)
44
+
45
+ return content.split('\n')
46
+ .filter(line => /^\s*\d+→/.test(line))
47
+ .map(line => line.replace(/^\s*\d+→/, ''))
48
+ .join('\n')
49
+ }
50
+
51
+ return (
52
+ <div className='tool-group read-tool'>
53
+ <ToolCallBox
54
+ header={
55
+ <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>
59
+ </div>
60
+ }
61
+ content={
62
+ <div className='tool-content'>
63
+ {dirPath && (
64
+ <div className='file-path'>
65
+ {dirPath}
66
+ </div>
67
+ )}
68
+ {resultItem
69
+ ? (
70
+ <div className='result-content'>
71
+ <CodeBlock
72
+ code={cleanContent(resultItem.content)}
73
+ lang={language}
74
+ />
75
+ </div>
76
+ )
77
+ : (
78
+ <div className='tool-placeholder'>
79
+ {t('chat.tools.reading')}
80
+ </div>
81
+ )}
82
+ </div>
83
+ }
84
+ />
85
+ </div>
86
+ )
87
+ })
@@ -0,0 +1,78 @@
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
+ .todo-item {
22
+ display: flex;
23
+ align-items: flex-start;
24
+ gap: 8px;
25
+ margin-bottom: 2px;
26
+ padding: 6px 12px;
27
+ border-radius: 0;
28
+ transition: background-color .2s;
29
+
30
+ &:hover {
31
+ background-color: rgba(0, 0, 0, .04);
32
+ }
33
+
34
+ &:last-child {
35
+ margin-bottom: 0;
36
+ }
37
+
38
+ &.completed {
39
+ opacity: .6;
40
+
41
+ .todo-text {
42
+ text-decoration: line-through;
43
+ }
44
+
45
+ .status-icon {
46
+ color: #10b981;
47
+ }
48
+ }
49
+
50
+ &.in_progress {
51
+ .status-icon {
52
+ color: #3b82f6;
53
+ }
54
+ }
55
+
56
+ &.pending {
57
+ .status-icon {
58
+ color: var(--sub-text-color);
59
+ }
60
+ }
61
+
62
+ .todo-info {
63
+ display: flex;
64
+ flex-direction: column;
65
+ }
66
+
67
+ .todo-text {
68
+ font-size: 13px;
69
+ color: var(--text-color);
70
+ }
71
+
72
+ .active-form {
73
+ font-size: 11px;
74
+ color: var(--sub-text-color);
75
+ font-style: italic;
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,60 @@
1
+ import './TodoTool.scss'
2
+ import React from 'react'
3
+ import { useTranslation } from 'react-i18next'
4
+
5
+ import { defineToolRender } from '../defineToolRender'
6
+ import { ToolCallBox } from '../../ToolCallBox'
7
+
8
+ interface TodoItem {
9
+ content: string
10
+ status: 'pending' | 'in_progress' | 'completed'
11
+ activeForm?: string
12
+ }
13
+
14
+ export const TodoTool = defineToolRender(({ item }) => {
15
+ const { t } = useTranslation()
16
+ const input = (item.input != null ? item.input : {}) as { todos?: TodoItem[] }
17
+ const todos = input.todos ?? []
18
+
19
+ return (
20
+ <div className='tool-group todo-tool'>
21
+ <ToolCallBox
22
+ defaultExpanded={true}
23
+ 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>
27
+ </div>
28
+ }
29
+ content={
30
+ <div className='tool-content'>
31
+ {todos.map((todo, idx) => (
32
+ <div
33
+ key={idx}
34
+ className={`todo-item ${todo.status}`}
35
+ >
36
+ <span className='material-symbols-rounded status-icon'>
37
+ {todo.status === 'completed'
38
+ ? 'check_circle'
39
+ : todo.status === 'in_progress'
40
+ ? 'clock_loader_40'
41
+ : 'radio_button_unchecked'}
42
+ </span>
43
+ <div className='todo-info'>
44
+ <span className='todo-text'>
45
+ {todo.content}
46
+ </span>
47
+ {(todo.activeForm != null && todo.activeForm !== '') && todo.status === 'in_progress' && (
48
+ <span className='active-form'>
49
+ {todo.activeForm}
50
+ </span>
51
+ )}
52
+ </div>
53
+ </div>
54
+ ))}
55
+ </div>
56
+ }
57
+ />
58
+ </div>
59
+ )
60
+ })