bingocode 1.0.3 → 1.0.4

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/package.json +1 -1
  2. package/desktop/README.md +0 -30
  3. package/desktop/bunfig.toml +0 -1
  4. package/desktop/index.html +0 -17
  5. package/desktop/package.json +0 -55
  6. package/desktop/pnpm-lock.yaml +0 -3832
  7. package/desktop/public/app-icon.jpg +0 -0
  8. package/desktop/public/fonts/inter-latin-ext.woff2 +0 -0
  9. package/desktop/public/fonts/inter-latin.woff2 +0 -0
  10. package/desktop/public/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  11. package/desktop/public/fonts/jetbrains-mono-latin.woff2 +0 -0
  12. package/desktop/public/fonts/manrope-latin-ext.woff2 +0 -0
  13. package/desktop/public/fonts/manrope-latin.woff2 +0 -0
  14. package/desktop/public/fonts/material-symbols-outlined.woff2 +0 -0
  15. package/desktop/public/icons/bilibili.svg +0 -1
  16. package/desktop/public/icons/douyin.svg +0 -1
  17. package/desktop/public/icons/github.svg +0 -3
  18. package/desktop/public/icons/xiaohongshu.svg +0 -1
  19. package/desktop/scripts/build-macos-arm64.sh +0 -270
  20. package/desktop/scripts/build-sidecars.ts +0 -183
  21. package/desktop/scripts/build-windows-x64.ps1 +0 -295
  22. package/desktop/scripts/scan-missing-imports.ts +0 -235
  23. package/desktop/sidecars/claude-sidecar.ts +0 -156
  24. package/desktop/src/App.tsx +0 -5
  25. package/desktop/src/__tests__/agentsSettings.test.tsx +0 -349
  26. package/desktop/src/__tests__/pages.test.tsx +0 -290
  27. package/desktop/src/__tests__/skillsSettings.test.tsx +0 -205
  28. package/desktop/src/api/adapters.ts +0 -12
  29. package/desktop/src/api/agents.ts +0 -36
  30. package/desktop/src/api/cliTasks.ts +0 -28
  31. package/desktop/src/api/client.ts +0 -63
  32. package/desktop/src/api/computerUse.ts +0 -76
  33. package/desktop/src/api/filesystem.ts +0 -30
  34. package/desktop/src/api/hahaOAuth.ts +0 -38
  35. package/desktop/src/api/models.ts +0 -28
  36. package/desktop/src/api/providers.ts +0 -63
  37. package/desktop/src/api/search.ts +0 -29
  38. package/desktop/src/api/sessions.ts +0 -56
  39. package/desktop/src/api/settings.ts +0 -20
  40. package/desktop/src/api/skills.ts +0 -19
  41. package/desktop/src/api/tasks.ts +0 -36
  42. package/desktop/src/api/teams.ts +0 -44
  43. package/desktop/src/api/websocket.ts +0 -164
  44. package/desktop/src/components/chat/AskUserQuestion.tsx +0 -268
  45. package/desktop/src/components/chat/AssistantMessage.tsx +0 -29
  46. package/desktop/src/components/chat/AttachmentGallery.tsx +0 -113
  47. package/desktop/src/components/chat/ChatInput.tsx +0 -622
  48. package/desktop/src/components/chat/CodeViewer.tsx +0 -161
  49. package/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx +0 -174
  50. package/desktop/src/components/chat/ComputerUsePermissionModal.tsx +0 -311
  51. package/desktop/src/components/chat/DiffViewer.tsx +0 -157
  52. package/desktop/src/components/chat/FileSearchMenu.tsx +0 -198
  53. package/desktop/src/components/chat/ImageGalleryModal.tsx +0 -91
  54. package/desktop/src/components/chat/InlineImageGallery.tsx +0 -106
  55. package/desktop/src/components/chat/InlineTaskSummary.tsx +0 -60
  56. package/desktop/src/components/chat/MermaidRenderer.test.tsx +0 -98
  57. package/desktop/src/components/chat/MermaidRenderer.tsx +0 -361
  58. package/desktop/src/components/chat/MessageActionBar.tsx +0 -27
  59. package/desktop/src/components/chat/MessageList.test.tsx +0 -313
  60. package/desktop/src/components/chat/MessageList.tsx +0 -249
  61. package/desktop/src/components/chat/PermissionDialog.tsx +0 -262
  62. package/desktop/src/components/chat/SessionTaskBar.test.tsx +0 -99
  63. package/desktop/src/components/chat/SessionTaskBar.tsx +0 -159
  64. package/desktop/src/components/chat/StreamingIndicator.tsx +0 -41
  65. package/desktop/src/components/chat/TerminalChrome.tsx +0 -35
  66. package/desktop/src/components/chat/ThinkingBlock.tsx +0 -87
  67. package/desktop/src/components/chat/ToolCallBlock.tsx +0 -247
  68. package/desktop/src/components/chat/ToolCallGroup.tsx +0 -617
  69. package/desktop/src/components/chat/ToolResultBlock.tsx +0 -107
  70. package/desktop/src/components/chat/UserMessage.tsx +0 -38
  71. package/desktop/src/components/chat/chatBlocks.test.tsx +0 -136
  72. package/desktop/src/components/chat/clipboard.ts +0 -25
  73. package/desktop/src/components/chat/composerUtils.test.ts +0 -55
  74. package/desktop/src/components/chat/composerUtils.ts +0 -149
  75. package/desktop/src/components/controls/ModelSelector.tsx +0 -156
  76. package/desktop/src/components/controls/PermissionModeSelector.tsx +0 -229
  77. package/desktop/src/components/layout/AppShell.tsx +0 -107
  78. package/desktop/src/components/layout/ContentRouter.tsx +0 -27
  79. package/desktop/src/components/layout/ProjectFilter.tsx +0 -126
  80. package/desktop/src/components/layout/Sidebar.test.tsx +0 -158
  81. package/desktop/src/components/layout/Sidebar.tsx +0 -384
  82. package/desktop/src/components/layout/StatusBar.tsx +0 -31
  83. package/desktop/src/components/layout/TabBar.test.tsx +0 -136
  84. package/desktop/src/components/layout/TabBar.tsx +0 -318
  85. package/desktop/src/components/layout/TitleBar.tsx +0 -96
  86. package/desktop/src/components/layout/WindowControls.test.tsx +0 -69
  87. package/desktop/src/components/layout/WindowControls.tsx +0 -89
  88. package/desktop/src/components/markdown/MarkdownRenderer.test.tsx +0 -100
  89. package/desktop/src/components/markdown/MarkdownRenderer.tsx +0 -229
  90. package/desktop/src/components/settings/ClaudeOfficialLogin.tsx +0 -107
  91. package/desktop/src/components/shared/Button.tsx +0 -63
  92. package/desktop/src/components/shared/CopyButton.tsx +0 -58
  93. package/desktop/src/components/shared/DirectoryPicker.tsx +0 -316
  94. package/desktop/src/components/shared/Dropdown.tsx +0 -91
  95. package/desktop/src/components/shared/Input.tsx +0 -38
  96. package/desktop/src/components/shared/Modal.tsx +0 -65
  97. package/desktop/src/components/shared/ProjectContextChip.tsx +0 -30
  98. package/desktop/src/components/shared/Spinner.tsx +0 -30
  99. package/desktop/src/components/shared/Textarea.tsx +0 -38
  100. package/desktop/src/components/shared/Toast.tsx +0 -47
  101. package/desktop/src/components/shared/UpdateChecker.tsx +0 -90
  102. package/desktop/src/components/skills/SkillDetail.test.tsx +0 -89
  103. package/desktop/src/components/skills/SkillDetail.tsx +0 -403
  104. package/desktop/src/components/skills/SkillList.tsx +0 -254
  105. package/desktop/src/components/tasks/DayOfWeekPicker.tsx +0 -57
  106. package/desktop/src/components/tasks/NewTaskModal.tsx +0 -407
  107. package/desktop/src/components/tasks/PromptEditor.tsx +0 -74
  108. package/desktop/src/components/tasks/TaskEmptyState.tsx +0 -30
  109. package/desktop/src/components/tasks/TaskList.tsx +0 -46
  110. package/desktop/src/components/tasks/TaskRow.tsx +0 -253
  111. package/desktop/src/components/tasks/TaskRunsPanel.tsx +0 -195
  112. package/desktop/src/components/teams/TeamStatusBar.tsx +0 -147
  113. package/desktop/src/config/providerPresets.ts +0 -78
  114. package/desktop/src/config/spinnerVerbs.ts +0 -193
  115. package/desktop/src/hooks/useKeyboardShortcuts.ts +0 -60
  116. package/desktop/src/i18n/index.ts +0 -54
  117. package/desktop/src/i18n/locales/en.ts +0 -670
  118. package/desktop/src/i18n/locales/zh.ts +0 -670
  119. package/desktop/src/lib/__tests__/cronDescribe.test.ts +0 -93
  120. package/desktop/src/lib/cronDescribe.ts +0 -188
  121. package/desktop/src/lib/desktopRuntime.ts +0 -54
  122. package/desktop/src/lib/parseRunOutput.ts +0 -79
  123. package/desktop/src/main.tsx +0 -13
  124. package/desktop/src/mocks/data.ts +0 -202
  125. package/desktop/src/pages/ActiveSession.test.tsx +0 -181
  126. package/desktop/src/pages/ActiveSession.tsx +0 -219
  127. package/desktop/src/pages/AdapterSettings.tsx +0 -375
  128. package/desktop/src/pages/AgentTeams.tsx +0 -200
  129. package/desktop/src/pages/ComputerUseSettings.tsx +0 -420
  130. package/desktop/src/pages/EmptySession.tsx +0 -518
  131. package/desktop/src/pages/NewTaskModal.tsx +0 -346
  132. package/desktop/src/pages/ScheduledTasks.tsx +0 -66
  133. package/desktop/src/pages/ScheduledTasksEmpty.tsx +0 -152
  134. package/desktop/src/pages/ScheduledTasksList.tsx +0 -416
  135. package/desktop/src/pages/SessionControls.tsx +0 -460
  136. package/desktop/src/pages/Settings.tsx +0 -1448
  137. package/desktop/src/pages/ToolInspection.tsx +0 -235
  138. package/desktop/src/stores/adapterStore.ts +0 -106
  139. package/desktop/src/stores/agentStore.ts +0 -34
  140. package/desktop/src/stores/chatStore.test.ts +0 -505
  141. package/desktop/src/stores/chatStore.ts +0 -850
  142. package/desktop/src/stores/cliTaskStore.ts +0 -152
  143. package/desktop/src/stores/hahaOAuthStore.test.ts +0 -77
  144. package/desktop/src/stores/hahaOAuthStore.ts +0 -97
  145. package/desktop/src/stores/providerStore.ts +0 -101
  146. package/desktop/src/stores/sessionStore.test.ts +0 -63
  147. package/desktop/src/stores/sessionStore.ts +0 -102
  148. package/desktop/src/stores/settingsStore.ts +0 -120
  149. package/desktop/src/stores/skillStore.ts +0 -51
  150. package/desktop/src/stores/tabStore.ts +0 -169
  151. package/desktop/src/stores/taskStore.ts +0 -68
  152. package/desktop/src/stores/teamStore.ts +0 -344
  153. package/desktop/src/stores/uiStore.ts +0 -100
  154. package/desktop/src/stores/updateStore.test.ts +0 -71
  155. package/desktop/src/stores/updateStore.ts +0 -221
  156. package/desktop/src/theme/globals.css +0 -465
  157. package/desktop/src/types/adapter.ts +0 -33
  158. package/desktop/src/types/chat.ts +0 -152
  159. package/desktop/src/types/cliTask.ts +0 -24
  160. package/desktop/src/types/provider.ts +0 -62
  161. package/desktop/src/types/session.ts +0 -27
  162. package/desktop/src/types/settings.ts +0 -22
  163. package/desktop/src/types/skill.ts +0 -38
  164. package/desktop/src/types/task.ts +0 -56
  165. package/desktop/src/types/team.ts +0 -38
  166. package/desktop/src-tauri/Cargo.lock +0 -5549
  167. package/desktop/src-tauri/Cargo.toml +0 -20
  168. package/desktop/src-tauri/app-icon.svg +0 -13
  169. package/desktop/src-tauri/build.rs +0 -3
  170. package/desktop/src-tauri/capabilities/default.json +0 -106
  171. package/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +0 -5
  172. package/desktop/src-tauri/icons/android/values/ic_launcher_background.xml +0 -4
  173. package/desktop/src-tauri/icons/icon.icns +0 -0
  174. package/desktop/src-tauri/icons/icon.ico +0 -0
  175. package/desktop/src-tauri/src/lib.rs +0 -408
  176. package/desktop/src-tauri/src/main.rs +0 -6
  177. package/desktop/src-tauri/tauri.conf.json +0 -78
  178. package/desktop/src-tauri/tauri.macos.conf.json +0 -18
  179. package/desktop/src-tauri/tauri.release-ci.json +0 -5
  180. package/desktop/src-tauri/tauri.windows.conf.json +0 -16
  181. package/desktop/src-tauri/windows-installer-hooks.nsh +0 -17
  182. package/desktop/tsconfig.json +0 -25
  183. package/desktop/vite.config.ts +0 -26
  184. package/desktop/vitest.config.ts +0 -18
@@ -1,313 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest'
2
- import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
3
- import { MessageList, buildRenderItems } from './MessageList'
4
- import { useChatStore } from '../../stores/chatStore'
5
- import { useTabStore } from '../../stores/tabStore'
6
- import type { UIMessage } from '../../types/chat'
7
- import type { PerSessionState } from '../../stores/chatStore'
8
-
9
- const ACTIVE_TAB = 'active-tab'
10
-
11
- function makeSessionState(overrides: Partial<PerSessionState> = {}): PerSessionState {
12
- return {
13
- messages: [],
14
- chatState: 'idle',
15
- connectionState: 'connected',
16
- streamingText: '',
17
- streamingToolInput: '',
18
- activeToolUseId: null,
19
- activeToolName: null,
20
- activeThinkingId: null,
21
- pendingPermission: null,
22
- pendingComputerUsePermission: null,
23
- tokenUsage: { input_tokens: 0, output_tokens: 0 },
24
- elapsedSeconds: 0,
25
- statusVerb: '',
26
- slashCommands: [],
27
- agentTaskNotifications: {},
28
- elapsedTimer: null,
29
- ...overrides,
30
- }
31
- }
32
-
33
- describe('MessageList nested tool calls', () => {
34
- beforeEach(() => {
35
- useTabStore.setState({ activeTabId: ACTIVE_TAB, tabs: [{ sessionId: ACTIVE_TAB, title: 'Test', type: 'session' as const, status: 'idle' }] })
36
- useChatStore.setState({ sessions: { [ACTIVE_TAB]: makeSessionState() } })
37
- })
38
-
39
- it('renders sub-agent tool calls inline beneath the parent agent tool call', () => {
40
- useChatStore.setState({
41
- sessions: {
42
- [ACTIVE_TAB]: makeSessionState({
43
- messages: [
44
- {
45
- id: 'tool-agent',
46
- type: 'tool_use',
47
- toolName: 'Agent',
48
- toolUseId: 'agent-1',
49
- input: { description: 'Inspect src/components' },
50
- timestamp: 1,
51
- },
52
- {
53
- id: 'tool-read',
54
- type: 'tool_use',
55
- toolName: 'Read',
56
- toolUseId: 'read-1',
57
- input: { file_path: '/tmp/example.ts' },
58
- timestamp: 2,
59
- parentToolUseId: 'agent-1',
60
- },
61
- {
62
- id: 'result-read',
63
- type: 'tool_result',
64
- toolUseId: 'read-1',
65
- content: 'const answer = 42',
66
- isError: false,
67
- timestamp: 3,
68
- parentToolUseId: 'agent-1',
69
- },
70
- ],
71
- }),
72
- },
73
- })
74
-
75
- const { container } = render(<MessageList />)
76
-
77
- expect(screen.getAllByText('Running').length).toBeGreaterThan(0)
78
- expect(screen.getByText(/Read .*example\.ts.*done/i)).toBeTruthy()
79
- expect(container.textContent).toContain('Agent')
80
- })
81
-
82
- it('keeps root tool runs split when nested child tool calls appear between them', () => {
83
- const messages: UIMessage[] = [
84
- {
85
- id: 'tool-agent',
86
- type: 'tool_use',
87
- toolName: 'Agent',
88
- toolUseId: 'agent-1',
89
- input: { description: 'Inspect src/components' },
90
- timestamp: 1,
91
- },
92
- {
93
- id: 'tool-read',
94
- type: 'tool_use',
95
- toolName: 'Read',
96
- toolUseId: 'read-1',
97
- input: { file_path: '/tmp/example.ts' },
98
- timestamp: 2,
99
- parentToolUseId: 'agent-1',
100
- },
101
- {
102
- id: 'result-read',
103
- type: 'tool_result',
104
- toolUseId: 'read-1',
105
- content: 'const answer = 42',
106
- isError: false,
107
- timestamp: 3,
108
- parentToolUseId: 'agent-1',
109
- },
110
- {
111
- id: 'tool-write',
112
- type: 'tool_use',
113
- toolName: 'Write',
114
- toolUseId: 'write-1',
115
- input: { file_path: '/tmp/out.ts', content: 'export const value = 1' },
116
- timestamp: 4,
117
- },
118
- ]
119
-
120
- const toolUseIds = new Set(messages.filter((message) => message.type === 'tool_use').map((message) => message.toolUseId))
121
- const renderItems = buildRenderItems(messages, toolUseIds)
122
- const toolGroups = renderItems.filter((item) => item.kind === 'tool_group')
123
-
124
- expect(toolGroups).toHaveLength(2)
125
- expect(toolGroups.map((item) => item.toolCalls[0]?.toolUseId)).toEqual(['agent-1', 'write-1'])
126
- })
127
-
128
- it('shows failed agent status and compact unavailable summary for Explore launch errors', () => {
129
- useChatStore.setState({
130
- sessions: {
131
- [ACTIVE_TAB]: makeSessionState({
132
- messages: [
133
- {
134
- id: 'tool-agent',
135
- type: 'tool_use',
136
- toolName: 'Agent',
137
- toolUseId: 'agent-1',
138
- input: { description: '探索整体架构', subagent_type: 'Explore' },
139
- timestamp: 1,
140
- },
141
- {
142
- id: 'result-agent',
143
- type: 'tool_result',
144
- toolUseId: 'agent-1',
145
- content: `Agent type 'Explore' not found. Available agents: general-purpose`,
146
- isError: true,
147
- timestamp: 2,
148
- },
149
- ],
150
- }),
151
- },
152
- })
153
-
154
- render(<MessageList />)
155
-
156
- expect(screen.getByText('Failed')).toBeTruthy()
157
- expect(screen.getByText('Explore agent unavailable in this session')).toBeTruthy()
158
- })
159
-
160
- it('shows completed agent output when no nested tool activity is available', () => {
161
- const longResult = '探索完成。让我将结果整合写入计划文件。第二段补充内容用于验证 dialog 展示的是完整结果而不是截断摘要。'
162
-
163
- useChatStore.setState({
164
- sessions: {
165
- [ACTIVE_TAB]: makeSessionState({
166
- messages: [
167
- {
168
- id: 'tool-agent',
169
- type: 'tool_use',
170
- toolName: 'Agent',
171
- toolUseId: 'agent-1',
172
- input: { description: '探索整体架构' },
173
- timestamp: 1,
174
- },
175
- {
176
- id: 'result-agent',
177
- type: 'tool_result',
178
- toolUseId: 'agent-1',
179
- content: {
180
- status: 'completed',
181
- content: [{ type: 'text', text: longResult }],
182
- },
183
- isError: false,
184
- timestamp: 2,
185
- },
186
- ],
187
- }),
188
- },
189
- })
190
-
191
- render(<MessageList />)
192
-
193
- expect(screen.getByText('Done')).toBeTruthy()
194
- expect(screen.getByRole('button', { name: 'View result' })).toBeTruthy()
195
-
196
- fireEvent.click(screen.getByRole('button', { name: 'View result' }))
197
-
198
- const dialog = screen.getByRole('dialog')
199
- expect(within(dialog).getByText(/第二段补充内容用于验证 dialog 展示的是完整结果而不是截断摘要。/)).toBeTruthy()
200
- expect(screen.getByRole('button', { name: 'Close dialog' })).toBeTruthy()
201
- })
202
-
203
- it('keeps async launched agents in running state until a terminal notification arrives', () => {
204
- useChatStore.setState({
205
- sessions: {
206
- [ACTIVE_TAB]: makeSessionState({
207
- messages: [
208
- {
209
- id: 'tool-agent',
210
- type: 'tool_use',
211
- toolName: 'Agent',
212
- toolUseId: 'agent-1',
213
- input: { description: '修复临时文件泄漏' },
214
- timestamp: 1,
215
- },
216
- {
217
- id: 'result-agent',
218
- type: 'tool_result',
219
- toolUseId: 'agent-1',
220
- content:
221
- "Async agent launched successfully.\nagentId: a29934b04b20ed564 (internal ID - do not mention to user. Use SendMessage with to: 'a29934b04b20ed564' to continue this agent.)\nThe agent is working in the background. You will be notified automatically when it completes.",
222
- isError: false,
223
- timestamp: 2,
224
- },
225
- ],
226
- }),
227
- },
228
- })
229
-
230
- render(<MessageList />)
231
-
232
- expect(screen.getAllByText('Running').length).toBeGreaterThan(0)
233
- expect(screen.queryByText('Done')).toBeNull()
234
- expect(screen.queryByRole('button', { name: 'View result' })).toBeNull()
235
- })
236
-
237
- it('renders copy controls for user messages and scopes assistant copy to a single reply', async () => {
238
- const writeText = vi.fn().mockResolvedValue(undefined)
239
- Object.assign(navigator, {
240
- clipboard: {
241
- writeText,
242
- },
243
- })
244
-
245
- useChatStore.setState({
246
- sessions: {
247
- [ACTIVE_TAB]: makeSessionState({
248
- messages: [
249
- {
250
- id: 'user-1',
251
- type: 'user_text',
252
- content: '请帮我探索整体架构',
253
- timestamp: 1,
254
- },
255
- {
256
- id: 'assistant-1',
257
- type: 'assistant_text',
258
- content: '先看 CLI 和服务端入口。',
259
- timestamp: 2,
260
- },
261
- {
262
- id: 'assistant-2',
263
- type: 'assistant_text',
264
- content: '再看 desktop 前后端边界。',
265
- timestamp: 3,
266
- },
267
- ],
268
- }),
269
- },
270
- })
271
-
272
- render(<MessageList />)
273
-
274
- expect(screen.getByRole('button', { name: 'Copy prompt' })).toBeTruthy()
275
-
276
- fireEvent.click(screen.getAllByRole('button', { name: 'Copy reply' })[1]!)
277
-
278
- await waitFor(() => {
279
- expect(writeText).toHaveBeenCalledWith('再看 desktop 前后端边界。')
280
- })
281
- expect(writeText).not.toHaveBeenCalledWith(
282
- '先看 CLI 和服务端入口。\n再看 desktop 前后端边界。'
283
- )
284
- })
285
-
286
- it('shows raw startup details under translated CLI startup errors', () => {
287
- useChatStore.setState({
288
- sessions: {
289
- [ACTIVE_TAB]: makeSessionState({
290
- messages: [
291
- {
292
- id: 'error-1',
293
- type: 'error',
294
- code: 'CLI_START_FAILED',
295
- message:
296
- 'CLI exited during startup (code 1): Claude Code on Windows requires git-bash (https://git-scm.com/downloads/win).',
297
- timestamp: 1,
298
- },
299
- ],
300
- }),
301
- },
302
- })
303
-
304
- render(<MessageList />)
305
-
306
- expect(screen.getByText('Failed to start CLI process.')).toBeTruthy()
307
- expect(
308
- screen.getByText(
309
- 'CLI exited during startup (code 1): Claude Code on Windows requires git-bash (https://git-scm.com/downloads/win).',
310
- ),
311
- ).toBeTruthy()
312
- })
313
- })
@@ -1,249 +0,0 @@
1
- import { useRef, useEffect, useMemo, memo } from 'react'
2
- import { useChatStore } from '../../stores/chatStore'
3
- import { useTabStore } from '../../stores/tabStore'
4
- import { useTranslation } from '../../i18n'
5
- import type { TranslationKey } from '../../i18n/locales/en'
6
- import { UserMessage } from './UserMessage'
7
- import { AssistantMessage } from './AssistantMessage'
8
- import { ThinkingBlock } from './ThinkingBlock'
9
- import { ToolCallBlock } from './ToolCallBlock'
10
- import { ToolCallGroup } from './ToolCallGroup'
11
- import { ToolResultBlock } from './ToolResultBlock'
12
- import { PermissionDialog } from './PermissionDialog'
13
- import { AskUserQuestion } from './AskUserQuestion'
14
- import { StreamingIndicator } from './StreamingIndicator'
15
- import { InlineTaskSummary } from './InlineTaskSummary'
16
- import type { AgentTaskNotification, UIMessage } from '../../types/chat'
17
-
18
- type ToolCall = Extract<UIMessage, { type: 'tool_use' }>
19
- type ToolResult = Extract<UIMessage, { type: 'tool_result' }>
20
-
21
- type RenderItem =
22
- | { kind: 'tool_group'; toolCalls: ToolCall[]; id: string }
23
- | { kind: 'message'; message: UIMessage }
24
-
25
- export function buildRenderItems(messages: UIMessage[], toolUseIds: Set<string>): RenderItem[] {
26
- const items: RenderItem[] = []
27
- let pendingToolCalls: ToolCall[] = []
28
-
29
- const flushGroup = () => {
30
- if (pendingToolCalls.length > 0) {
31
- items.push({
32
- kind: 'tool_group',
33
- toolCalls: [...pendingToolCalls],
34
- id: `group-${pendingToolCalls[0]!.id}`,
35
- })
36
- pendingToolCalls = []
37
- }
38
- }
39
-
40
- for (const msg of messages) {
41
- if (msg.type === 'tool_result' && toolUseIds.has(msg.toolUseId)) {
42
- continue
43
- }
44
-
45
- if (msg.type === 'tool_use') {
46
- if (msg.parentToolUseId) {
47
- flushGroup()
48
- continue
49
- }
50
- if (msg.toolName === 'AskUserQuestion') {
51
- flushGroup()
52
- items.push({ kind: 'message', message: msg })
53
- } else {
54
- pendingToolCalls.push(msg)
55
- }
56
- } else {
57
- flushGroup()
58
- items.push({ kind: 'message', message: msg })
59
- }
60
- }
61
-
62
- flushGroup()
63
- return items
64
- }
65
-
66
- export function MessageList() {
67
- const activeTabId = useTabStore((s) => s.activeTabId)
68
- const sessionState = useChatStore((s) => activeTabId ? s.sessions[activeTabId] : undefined)
69
- const messages = sessionState?.messages ?? []
70
- const chatState = sessionState?.chatState ?? 'idle'
71
- const streamingText = sessionState?.streamingText ?? ''
72
- const activeThinkingId = sessionState?.activeThinkingId ?? null
73
- const agentTaskNotifications = sessionState?.agentTaskNotifications ?? {}
74
- const bottomRef = useRef<HTMLDivElement>(null)
75
-
76
- useEffect(() => {
77
- bottomRef.current?.scrollIntoView?.({ behavior: 'smooth' })
78
- }, [messages.length, streamingText])
79
-
80
- const { toolResultMap, childToolCallsByParent, renderItems } = useMemo(() => {
81
- const toolUseIds = new Set<string>()
82
- const toolResultMap = new Map<string, ToolResult>()
83
- const childToolCallsByParent = new Map<string, ToolCall[]>()
84
-
85
- for (const msg of messages) {
86
- if (msg.type === 'tool_use') {
87
- toolUseIds.add(msg.toolUseId)
88
- if (msg.parentToolUseId) {
89
- const siblings = childToolCallsByParent.get(msg.parentToolUseId)
90
- if (siblings) {
91
- siblings.push(msg)
92
- } else {
93
- childToolCallsByParent.set(msg.parentToolUseId, [msg])
94
- }
95
- }
96
- }
97
- if (msg.type === 'tool_result' && msg.toolUseId) {
98
- toolResultMap.set(msg.toolUseId, msg)
99
- }
100
- }
101
-
102
- const renderItems = buildRenderItems(messages, toolUseIds)
103
- return { toolUseIds, toolResultMap, childToolCallsByParent, renderItems }
104
- }, [messages])
105
-
106
- return (
107
- <div className="flex-1 overflow-y-auto px-4 py-4">
108
- <div className="mx-auto max-w-[860px]">
109
- {renderItems.map((item) => {
110
- if (item.kind === 'tool_group') {
111
- return (
112
- <ToolCallGroup
113
- key={item.id}
114
- toolCalls={item.toolCalls}
115
- resultMap={toolResultMap}
116
- childToolCallsByParent={childToolCallsByParent}
117
- agentTaskNotifications={agentTaskNotifications}
118
- isStreaming={
119
- chatState === 'tool_executing' &&
120
- item.toolCalls.some((tc) => !toolResultMap.has(tc.toolUseId))
121
- }
122
- />
123
- )
124
- }
125
-
126
- const msg = item.message
127
- return (
128
- <MessageBlock
129
- key={msg.id}
130
- message={msg}
131
- activeThinkingId={activeThinkingId}
132
- agentTaskNotifications={agentTaskNotifications}
133
- toolResult={
134
- msg.type === 'tool_use'
135
- ? (() => {
136
- const r = toolResultMap.get(msg.toolUseId)
137
- return r ? { content: r.content, isError: r.isError } : null
138
- })()
139
- : null
140
- }
141
- />
142
- )
143
- })}
144
-
145
- {streamingText && (
146
- <AssistantMessage content={streamingText} isStreaming={chatState === 'streaming'} />
147
- )}
148
-
149
- {/* Show StreamingIndicator when:
150
- - tool_executing: tool is running
151
- - thinking but no active ThinkingBlock yet: the gap between
152
- sending a message and receiving the first thinking delta */}
153
- {(chatState === 'tool_executing' || (chatState === 'thinking' && !activeThinkingId)) && (
154
- <StreamingIndicator />
155
- )}
156
-
157
- <div ref={bottomRef} />
158
- </div>
159
- </div>
160
- )
161
- }
162
-
163
- export const MessageBlock = memo(function MessageBlock({
164
- message,
165
- activeThinkingId,
166
- agentTaskNotifications,
167
- toolResult,
168
- }: {
169
- message: UIMessage
170
- activeThinkingId: string | null
171
- agentTaskNotifications: Record<string, AgentTaskNotification>
172
- toolResult?: { content: unknown; isError: boolean } | null
173
- }) {
174
- const t = useTranslation()
175
-
176
- switch (message.type) {
177
- case 'user_text':
178
- return <UserMessage content={message.content} attachments={message.attachments} />
179
- case 'assistant_text':
180
- return <AssistantMessage content={message.content} />
181
- case 'thinking':
182
- return <ThinkingBlock content={message.content} isActive={message.id === activeThinkingId} />
183
- case 'tool_use':
184
- if (message.toolName === 'AskUserQuestion') {
185
- return (
186
- <AskUserQuestion
187
- toolUseId={message.toolUseId}
188
- input={message.input}
189
- />
190
- )
191
- }
192
- return (
193
- <ToolCallBlock
194
- toolName={message.toolName}
195
- input={message.input}
196
- result={toolResult}
197
- agentTaskNotification={
198
- message.toolName === 'Agent'
199
- ? agentTaskNotifications[message.toolUseId]
200
- : undefined
201
- }
202
- />
203
- )
204
- case 'tool_result':
205
- return (
206
- <ToolResultBlock
207
- content={message.content}
208
- isError={message.isError}
209
- standalone
210
- />
211
- )
212
- case 'permission_request':
213
- return (
214
- <PermissionDialog
215
- requestId={message.requestId}
216
- toolName={message.toolName}
217
- input={message.input}
218
- description={message.description}
219
- />
220
- )
221
- case 'error': {
222
- const errorKey = message.code ? `error.${message.code}` as TranslationKey : null
223
- const errorText = errorKey ? t(errorKey) : null
224
- const displayMessage = (errorText && errorText !== errorKey) ? errorText : message.message
225
- const showRawDetail =
226
- Boolean(message.message) &&
227
- message.message.trim() !== '' &&
228
- message.message !== displayMessage
229
- return (
230
- <div className="mb-3 px-4 py-2.5 rounded-lg border border-[var(--color-error)]/20 bg-[var(--color-error-container)]/28 text-sm text-[var(--color-error)]">
231
- <strong>Error:</strong> {displayMessage}
232
- {showRawDetail && (
233
- <div className="mt-1 whitespace-pre-wrap text-xs text-[var(--color-on-error-container)]/85">
234
- {message.message}
235
- </div>
236
- )}
237
- </div>
238
- )
239
- }
240
- case 'task_summary':
241
- return <InlineTaskSummary tasks={message.tasks} />
242
- case 'system':
243
- return (
244
- <div className="mb-3 text-center text-xs text-[var(--color-text-tertiary)]">
245
- {message.content}
246
- </div>
247
- )
248
- }
249
- })