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,262 +0,0 @@
1
- import { useState } 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'
6
- import { Button } from '../shared/Button'
7
- import { DiffViewer } from './DiffViewer'
8
-
9
- type Props = {
10
- requestId: string
11
- toolName: string
12
- input: unknown
13
- description?: string
14
- }
15
-
16
- /**
17
- * Icons for known tool types.
18
- * Uses Material Symbols Outlined names.
19
- */
20
- const TOOL_META: Record<string, { icon: string; label: string; color: string }> = {
21
- Bash: { icon: 'terminal', label: 'Bash', color: 'var(--color-warning)' },
22
- Edit: { icon: 'edit_note', label: 'Edit File', color: 'var(--color-brand)' },
23
- Write: { icon: 'edit_document', label: 'Write File', color: 'var(--color-success)' },
24
- Read: { icon: 'description', label: 'Read File', color: 'var(--color-secondary)' },
25
- Glob: { icon: 'search', label: 'Glob Search', color: 'var(--color-secondary)' },
26
- Grep: { icon: 'find_in_page', label: 'Grep Search', color: 'var(--color-secondary)' },
27
- Agent: { icon: 'smart_toy', label: 'Agent', color: 'var(--color-tertiary)' },
28
- WebSearch: { icon: 'travel_explore', label: 'Web Search', color: 'var(--color-secondary)' },
29
- WebFetch: { icon: 'cloud_download', label: 'Web Fetch', color: 'var(--color-secondary)' },
30
- NotebookEdit: { icon: 'note', label: 'Notebook Edit', color: 'var(--color-brand)' },
31
- Skill: { icon: 'auto_awesome', label: 'Skill', color: 'var(--color-tertiary)' },
32
- }
33
-
34
- /**
35
- * Extract human-readable detail lines from tool input.
36
- */
37
- function extractToolDetails(toolName: string, input: unknown, t: (key: TranslationKey, params?: Record<string, string | number>) => string): { primary: string; secondary?: string } {
38
- const obj = (input && typeof input === 'object') ? input as Record<string, unknown> : {}
39
-
40
- switch (toolName) {
41
- case 'Bash': {
42
- const cmd = typeof obj.command === 'string' ? obj.command : ''
43
- const desc = typeof obj.description === 'string' ? obj.description : undefined
44
- return { primary: cmd, secondary: desc }
45
- }
46
- case 'Edit': {
47
- const filePath = typeof obj.file_path === 'string' ? obj.file_path : ''
48
- return { primary: filePath, secondary: obj.old_string ? t('permission.replacingContent') : undefined }
49
- }
50
- case 'Write': {
51
- const filePath = typeof obj.file_path === 'string' ? obj.file_path : ''
52
- return { primary: filePath }
53
- }
54
- case 'Read': {
55
- const filePath = typeof obj.file_path === 'string' ? obj.file_path : ''
56
- return { primary: filePath }
57
- }
58
- case 'Glob':
59
- return { primary: typeof obj.pattern === 'string' ? obj.pattern : '' }
60
- case 'Grep':
61
- return { primary: typeof obj.pattern === 'string' ? obj.pattern : '' }
62
- case 'Agent':
63
- return { primary: typeof obj.description === 'string' ? obj.description : '' }
64
- case 'WebSearch':
65
- return { primary: typeof obj.query === 'string' ? obj.query : '' }
66
- case 'WebFetch':
67
- return { primary: typeof obj.url === 'string' ? obj.url : '' }
68
- default:
69
- return { primary: typeof input === 'string' ? input : JSON.stringify(input, null, 2) }
70
- }
71
- }
72
-
73
- function getPermissionTitle(toolName: string, input: unknown, t: (key: TranslationKey, params?: Record<string, string | number>) => string) {
74
- const obj = (input && typeof input === 'object') ? input as Record<string, unknown> : {}
75
- const filePath = typeof obj.file_path === 'string' ? obj.file_path : ''
76
- const fileName = filePath ? filePath.split('/').pop() || filePath : ''
77
-
78
- switch (toolName) {
79
- case 'Edit':
80
- case 'Write':
81
- return fileName ? t('permission.allowEditFile', { toolName, fileName }) : t('permission.allowEditFileGeneric', { toolName: toolName.toLowerCase() })
82
- case 'Bash':
83
- return t('permission.allowBash')
84
- default:
85
- return t('permission.allowTool', { toolName })
86
- }
87
- }
88
-
89
- function renderPermissionPreview(toolName: string, input: unknown) {
90
- const obj = (input && typeof input === 'object') ? input as Record<string, unknown> : {}
91
- const filePath = typeof obj.file_path === 'string' ? obj.file_path : 'file'
92
-
93
- if (toolName === 'Edit' && typeof obj.old_string === 'string' && typeof obj.new_string === 'string') {
94
- return <DiffViewer filePath={filePath} oldString={obj.old_string} newString={obj.new_string} />
95
- }
96
-
97
- if (toolName === 'Write' && typeof obj.content === 'string') {
98
- return <DiffViewer filePath={filePath} oldString="" newString={obj.content} />
99
- }
100
-
101
- if (toolName === 'Bash' && typeof obj.command === 'string') {
102
- return (
103
- <div className="overflow-x-auto rounded-[var(--radius-md)] bg-[var(--color-terminal-bg)] px-3 py-2.5">
104
- <pre className="font-[var(--font-mono)] text-[11px] leading-[1.3] text-[var(--color-terminal-fg)] whitespace-pre-wrap break-words">
105
- <span className="text-[var(--color-terminal-accent)] select-none">$ </span>{obj.command}
106
- </pre>
107
- </div>
108
- )
109
- }
110
-
111
- return null
112
- }
113
-
114
- export function PermissionDialog({ requestId, toolName, input, description }: Props) {
115
- const { respondToPermission } = useChatStore()
116
- const activeTabId = useTabStore((s) => s.activeTabId)
117
- const pendingPermission = useChatStore((s) => activeTabId ? s.sessions[activeTabId]?.pendingPermission : undefined)
118
- const t = useTranslation()
119
- const isPending = pendingPermission?.requestId === requestId
120
- const [showRaw, setShowRaw] = useState(false)
121
-
122
- const meta = TOOL_META[toolName] || { icon: 'shield', label: toolName, color: 'var(--color-text-tertiary)' }
123
- const details = extractToolDetails(toolName, input, t)
124
- const rawInput = typeof input === 'string' ? input : JSON.stringify(input, null, 2)
125
- const preview = renderPermissionPreview(toolName, input)
126
- const title = getPermissionTitle(toolName, input, t)
127
- const allowRawToggle = !preview
128
-
129
- return (
130
- <div className={`mb-4 ml-10 overflow-hidden rounded-[var(--radius-lg)] border ${
131
- isPending
132
- ? 'border-[var(--color-warning)] bg-[var(--color-surface-container-lowest)]'
133
- : 'border-[var(--color-outline-variant)]/40 bg-[var(--color-surface-container-low)] opacity-70'
134
- }`}>
135
- {/* Header */}
136
- <div className={`flex items-center gap-3 px-4 py-3 ${
137
- isPending
138
- ? 'bg-[var(--color-surface-container)]'
139
- : 'bg-[var(--color-surface-container-low)]'
140
- }`}>
141
- <div
142
- className="flex items-center justify-center w-8 h-8 rounded-[var(--radius-md)]"
143
- style={{ backgroundColor: `${meta.color}18` }}
144
- >
145
- <span
146
- className="material-symbols-outlined text-[18px]"
147
- style={{ color: meta.color }}
148
- >
149
- {meta.icon}
150
- </span>
151
- </div>
152
- <div className="flex-1 min-w-0">
153
- <div className="flex items-center gap-2">
154
- <span className="text-sm font-semibold text-[var(--color-text-primary)]">
155
- {title}
156
- </span>
157
- {isPending && (
158
- <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider bg-[var(--color-warning)]/15 text-[var(--color-warning)]">
159
- <span className="w-1.5 h-1.5 rounded-full bg-[var(--color-warning)] animate-pulse-dot" />
160
- {t('permission.awaitingApproval')}
161
- </span>
162
- )}
163
- {!isPending && (
164
- <span className="inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-bold uppercase tracking-wider bg-[var(--color-surface-container-high)] text-[var(--color-text-tertiary)]">
165
- {t('permission.responded')}
166
- </span>
167
- )}
168
- </div>
169
- {description && (
170
- <p className="mt-0.5 text-xs text-[var(--color-text-secondary)] truncate">{description}</p>
171
- )}
172
- </div>
173
- </div>
174
-
175
- {/* Tool details */}
176
- <div className="border-t border-[var(--color-outline-variant)]/20 px-4 py-3">
177
- {preview ? (
178
- <div className="space-y-2">
179
- {details.primary && toolName !== 'Bash' ? (
180
- <div className="flex items-center gap-2 rounded-[var(--radius-md)] bg-[var(--color-surface-container)] px-3 py-2 text-xs font-[var(--font-mono)] text-[var(--color-text-secondary)]">
181
- <span className="material-symbols-outlined text-[14px] text-[var(--color-outline)] flex-shrink-0">
182
- folder_open
183
- </span>
184
- <span className="truncate">{details.primary}</span>
185
- </div>
186
- ) : null}
187
- {preview}
188
- </div>
189
- ) : details.primary ? (
190
- <div className="mb-2">
191
- <div className="flex items-center gap-2 rounded-[var(--radius-md)] bg-[var(--color-surface-container)] px-3 py-2 text-xs font-[var(--font-mono)] text-[var(--color-text-secondary)]">
192
- <span className="material-symbols-outlined text-[14px] text-[var(--color-outline)] flex-shrink-0">
193
- {toolName === 'Glob' || toolName === 'Grep' ? 'search' : 'folder_open'}
194
- </span>
195
- <span className="truncate">{details.primary}</span>
196
- </div>
197
- </div>
198
- ) : null}
199
-
200
- {/* Secondary detail */}
201
- {details.secondary && (
202
- <p className="mt-2 text-xs text-[var(--color-text-tertiary)]">{details.secondary}</p>
203
- )}
204
-
205
- {allowRawToggle && (
206
- <button
207
- onClick={() => setShowRaw(!showRaw)}
208
- className="mt-2 flex cursor-pointer items-center gap-1 text-[11px] text-[var(--color-text-accent)] hover:underline"
209
- >
210
- <span className="material-symbols-outlined text-[14px]">
211
- {showRaw ? 'expand_less' : 'expand_more'}
212
- </span>
213
- {showRaw ? t('permission.hideDetails') : t('permission.showFullInput')}
214
- </button>
215
- )}
216
-
217
- {allowRawToggle && showRaw && (
218
- <pre className="mt-2 max-h-[220px] overflow-y-auto overflow-x-auto rounded-[var(--radius-md)] bg-[var(--color-terminal-bg)] px-3 py-2.5 font-[var(--font-mono)] text-[11px] leading-[1.3] text-[var(--color-terminal-fg)] whitespace-pre-wrap break-words">
219
- {rawInput}
220
- </pre>
221
- )}
222
- </div>
223
-
224
- {/* Action buttons */}
225
- {isPending && (
226
- <div className="flex items-center gap-2 border-t border-[var(--color-outline-variant)]/20 bg-[var(--color-surface-container-low)] px-4 py-3">
227
- <Button
228
- variant="primary"
229
- size="sm"
230
- onClick={() => activeTabId && respondToPermission(activeTabId, requestId, true)}
231
- icon={
232
- <span className="material-symbols-outlined text-[14px]">check</span>
233
- }
234
- >
235
- {t('permission.allow')}
236
- </Button>
237
- <Button
238
- variant="ghost"
239
- size="sm"
240
- onClick={() => activeTabId && respondToPermission(activeTabId, requestId, true, 'always')}
241
- icon={
242
- <span className="material-symbols-outlined text-[14px]">verified</span>
243
- }
244
- >
245
- {t('permission.allowForSession')}
246
- </Button>
247
- <div className="flex-1" />
248
- <Button
249
- variant="danger"
250
- size="sm"
251
- onClick={() => activeTabId && respondToPermission(activeTabId, requestId, false)}
252
- icon={
253
- <span className="material-symbols-outlined text-[14px]">close</span>
254
- }
255
- >
256
- {t('permission.deny')}
257
- </Button>
258
- </div>
259
- )}
260
- </div>
261
- )
262
- }
@@ -1,99 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
- import { act, fireEvent, render, screen } from '@testing-library/react'
3
- import '@testing-library/jest-dom'
4
- import { SessionTaskBar } from './SessionTaskBar'
5
- import { useCLITaskStore } from '../../stores/cliTaskStore'
6
-
7
- vi.mock('../../i18n', () => ({
8
- useTranslation: () => (key: string) => {
9
- const translations: Record<string, string> = {
10
- 'tasks.title': 'Tasks',
11
- 'tasks.dismissCompleted': 'Hide completed tasks',
12
- }
13
-
14
- return translations[key] ?? key
15
- },
16
- }))
17
-
18
- describe('SessionTaskBar', () => {
19
- beforeEach(() => {
20
- useCLITaskStore.setState({
21
- sessionId: 'session-1',
22
- tasks: [],
23
- expanded: false,
24
- completedAndDismissed: false,
25
- dismissedCompletionKey: null,
26
- })
27
- })
28
-
29
- afterEach(() => {
30
- useCLITaskStore.getState().clearTasks()
31
- })
32
-
33
- it('only shows the dismiss button once every task is completed', () => {
34
- act(() => {
35
- useCLITaskStore.getState().setTasksFromTodos([
36
- { content: 'first', status: 'completed' },
37
- { content: 'second', status: 'in_progress', activeForm: 'working' },
38
- ])
39
- })
40
-
41
- act(() => {
42
- render(<SessionTaskBar />)
43
- })
44
-
45
- expect(screen.getByText('Tasks')).toBeInTheDocument()
46
- expect(screen.queryByRole('button', { name: 'Hide completed tasks' })).toBeNull()
47
- })
48
-
49
- it('hides the bar after dismissing a completed task set', () => {
50
- act(() => {
51
- useCLITaskStore.getState().setTasksFromTodos([
52
- { content: 'first', status: 'completed' },
53
- { content: 'second', status: 'completed' },
54
- ])
55
- })
56
-
57
- act(() => {
58
- render(<SessionTaskBar />)
59
- })
60
-
61
- fireEvent.click(screen.getByRole('button', { name: 'Hide completed tasks' }))
62
-
63
- expect(screen.queryByText('Tasks')).toBeNull()
64
- expect(useCLITaskStore.getState().completedAndDismissed).toBe(true)
65
- })
66
-
67
- it('shows the bar again for a new task cycle after a previous completed set was dismissed', () => {
68
- act(() => {
69
- useCLITaskStore.getState().setTasksFromTodos([
70
- { content: 'first', status: 'completed' },
71
- ])
72
- })
73
-
74
- act(() => {
75
- render(<SessionTaskBar />)
76
- })
77
-
78
- fireEvent.click(screen.getByRole('button', { name: 'Hide completed tasks' }))
79
- expect(screen.queryByText('Tasks')).toBeNull()
80
-
81
- act(() => {
82
- useCLITaskStore.getState().setTasksFromTodos([
83
- { content: 'next task', status: 'in_progress', activeForm: 'running next task' },
84
- ])
85
- })
86
-
87
- expect(screen.getByText('Tasks')).toBeInTheDocument()
88
- expect(screen.queryByRole('button', { name: 'Hide completed tasks' })).toBeNull()
89
-
90
- act(() => {
91
- useCLITaskStore.getState().setTasksFromTodos([
92
- { content: 'next task', status: 'completed' },
93
- ])
94
- })
95
-
96
- expect(screen.getByText('Tasks')).toBeInTheDocument()
97
- expect(screen.getByRole('button', { name: 'Hide completed tasks' })).toBeInTheDocument()
98
- })
99
- })
@@ -1,159 +0,0 @@
1
- import { useCLITaskStore } from '../../stores/cliTaskStore'
2
- import { useTranslation } from '../../i18n'
3
- import type { CLITask } from '../../types/cliTask'
4
-
5
- const statusConfig = {
6
- pending: {
7
- icon: 'radio_button_unchecked',
8
- color: 'var(--color-text-tertiary)',
9
- label: 'pending',
10
- },
11
- in_progress: {
12
- icon: 'pending',
13
- color: 'var(--color-warning)',
14
- label: 'active',
15
- },
16
- completed: {
17
- icon: 'check_circle',
18
- color: 'var(--color-success)',
19
- label: 'done',
20
- },
21
- } as const
22
-
23
- export function SessionTaskBar() {
24
- const {
25
- tasks,
26
- expanded,
27
- toggleExpanded,
28
- completedAndDismissed,
29
- markCompletedAndDismissed,
30
- } = useCLITaskStore()
31
- const t = useTranslation()
32
-
33
- if (tasks.length === 0) return null
34
-
35
- // Don't show sticky bar if tasks were completed and the user already continued chatting
36
- const allCompleted = tasks.every((tk) => tk.status === 'completed')
37
- if (allCompleted && completedAndDismissed) return null
38
-
39
- const completedCount = tasks.filter((tk) => tk.status === 'completed').length
40
- const totalCount = tasks.length
41
- const progressPercent = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0
42
-
43
- return (
44
- <div className="shrink-0 px-8">
45
- <div className="mx-auto max-w-[860px] rounded-[var(--radius-lg)] border border-[var(--color-outline-variant)]/40 bg-[var(--color-surface-container-lowest)] overflow-hidden mb-2">
46
- {/* Header — always visible, clickable to toggle */}
47
- <div className="flex items-center gap-2 bg-[var(--color-surface-container)] px-2 py-1.5">
48
- <button
49
- type="button"
50
- onClick={toggleExpanded}
51
- className="flex min-w-0 flex-1 items-center gap-3 rounded-[var(--radius-md)] px-2 py-1 hover:bg-[var(--color-surface-container-low)] transition-colors"
52
- >
53
- <div className="flex items-center justify-center w-6 h-6 rounded-[var(--radius-md)] bg-[var(--color-secondary)]/10">
54
- <span
55
- className="material-symbols-outlined text-[14px] text-[var(--color-secondary)]"
56
- >
57
- checklist
58
- </span>
59
- </div>
60
-
61
- <span className="text-xs font-semibold text-[var(--color-text-primary)]">
62
- {t('tasks.title')}
63
- </span>
64
-
65
- {/* Progress bar */}
66
- <div className="flex-1 h-1.5 rounded-full bg-[var(--color-border)] overflow-hidden max-w-[200px]">
67
- <div
68
- className="h-full rounded-full transition-all duration-300"
69
- style={{
70
- width: `${progressPercent}%`,
71
- backgroundColor: completedCount === totalCount
72
- ? 'var(--color-success)'
73
- : 'var(--color-brand)',
74
- }}
75
- />
76
- </div>
77
-
78
- <span className="text-[10px] text-[var(--color-text-tertiary)] tabular-nums">
79
- {completedCount}/{totalCount}
80
- </span>
81
-
82
- <span
83
- className="material-symbols-outlined text-[14px] text-[var(--color-text-tertiary)] transition-transform duration-200"
84
- style={{ transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)' }}
85
- >
86
- expand_less
87
- </span>
88
- </button>
89
-
90
- {allCompleted && (
91
- <button
92
- type="button"
93
- aria-label={t('tasks.dismissCompleted')}
94
- onClick={markCompletedAndDismissed}
95
- className="flex shrink-0 items-center justify-center rounded-[var(--radius-md)] p-1.5 text-[var(--color-text-tertiary)] hover:bg-[var(--color-surface-container-low)] hover:text-[var(--color-text-primary)] transition-colors"
96
- >
97
- <span className="material-symbols-outlined text-[16px]">close</span>
98
- </button>
99
- )}
100
- </div>
101
-
102
- {/* Expanded task list */}
103
- {expanded && (
104
- <div className="px-4 pb-2 pt-1 flex flex-col gap-0.5 max-h-[240px] overflow-y-auto border-t border-[var(--color-outline-variant)]/20">
105
- {tasks.map((task) => (
106
- <TaskItem key={task.id} task={task} />
107
- ))}
108
- </div>
109
- )}
110
- </div>
111
- </div>
112
- )
113
- }
114
-
115
- function TaskItem({ task }: { task: CLITask }) {
116
- const config = statusConfig[task.status]
117
-
118
- return (
119
- <div className="flex items-start gap-2 py-1.5 px-1 rounded-md">
120
- <span
121
- className="material-symbols-outlined text-[16px] mt-px shrink-0"
122
- style={{ color: config.color, fontVariationSettings: "'FILL' 1" }}
123
- >
124
- {config.icon}
125
- </span>
126
-
127
- <div className="flex-1 min-w-0">
128
- <div className="flex items-center gap-1.5">
129
- <span className="text-[10px] font-mono text-[var(--color-text-tertiary)]">
130
- #{task.id}
131
- </span>
132
- <span className={`text-xs ${
133
- task.status === 'completed'
134
- ? 'text-[var(--color-text-tertiary)] line-through'
135
- : 'text-[var(--color-text-primary)]'
136
- }`}>
137
- {task.subject}
138
- </span>
139
- </div>
140
-
141
- {task.status === 'in_progress' && task.activeForm && (
142
- <div className="flex items-center gap-1 mt-0.5">
143
- <span className="w-1.5 h-1.5 rounded-full bg-[var(--color-warning)] animate-pulse" />
144
- <span className="text-[10px] text-[var(--color-warning)]">
145
- {task.activeForm}
146
- </span>
147
- </div>
148
- )}
149
-
150
- {task.owner && (
151
- <span className="text-[10px] text-[var(--color-text-tertiary)] mt-0.5 inline-flex items-center gap-0.5">
152
- <span className="material-symbols-outlined text-[10px]">person</span>
153
- {task.owner}
154
- </span>
155
- )}
156
- </div>
157
- </div>
158
- )
159
- }
@@ -1,41 +0,0 @@
1
- import { useChatStore } from '../../stores/chatStore'
2
- import { useTabStore } from '../../stores/tabStore'
3
-
4
- function formatElapsed(seconds: number): string {
5
- if (seconds < 60) return `${seconds}s`
6
- const m = Math.floor(seconds / 60)
7
- const s = seconds % 60
8
- return `${m}m ${s}s`
9
- }
10
-
11
- export function StreamingIndicator() {
12
- const activeTabId = useTabStore((s) => s.activeTabId)
13
- const sessionState = useChatStore((s) => activeTabId ? s.sessions[activeTabId] : undefined)
14
- const chatState = sessionState?.chatState ?? 'idle'
15
- const statusVerb = sessionState?.statusVerb ?? ''
16
- const elapsedSeconds = sessionState?.elapsedSeconds ?? 0
17
- const tokenUsage = sessionState?.tokenUsage ?? { input_tokens: 0, output_tokens: 0 }
18
- let verb: string
19
- if (statusVerb) {
20
- verb = statusVerb
21
- } else {
22
- verb = chatState === 'thinking' ? 'Thinking' : chatState === 'tool_executing' ? 'Running' : 'Working'
23
- }
24
-
25
- return (
26
- <div className="mb-2 ml-10 flex w-fit items-center gap-2 rounded-full border border-[var(--color-border)]/40 bg-[var(--color-surface-container-low)] px-3 py-1">
27
- <span className="text-[var(--color-brand)] animate-shimmer text-xs">✦</span>
28
- <span className="text-xs font-medium text-[var(--color-text-secondary)]">{verb}...</span>
29
- {elapsedSeconds > 0 && (
30
- <span className="text-[10px] text-[var(--color-text-tertiary)]">
31
- {formatElapsed(elapsedSeconds)}
32
- </span>
33
- )}
34
- {tokenUsage.output_tokens > 0 && (
35
- <span className="text-[10px] text-[var(--color-text-tertiary)]">
36
- · ↓ {tokenUsage.output_tokens}
37
- </span>
38
- )}
39
- </div>
40
- )
41
- }
@@ -1,35 +0,0 @@
1
- import type { ReactNode } from 'react'
2
-
3
- type Props = {
4
- title?: string
5
- children: ReactNode
6
- className?: string
7
- }
8
-
9
- /**
10
- * macOS-style terminal window decoration with traffic light buttons.
11
- * Reusable wrapper for Bash commands, tool results, and code viewers.
12
- */
13
- export function TerminalChrome({ title, children, className = '' }: Props) {
14
- return (
15
- <div className={`overflow-hidden rounded-2xl border border-[var(--color-outline-variant)]/20 bg-[var(--color-surface-dim)] ${className}`}>
16
- {/* Title bar with traffic lights */}
17
- <div className="flex items-center gap-2 border-b border-[var(--color-terminal-border)] bg-[var(--color-terminal-header)] px-3 py-2">
18
- <div className="flex gap-1.5">
19
- <div className="w-2.5 h-2.5 rounded-full bg-[var(--color-terminal-danger)]" />
20
- <div className="w-2.5 h-2.5 rounded-full bg-[var(--color-terminal-warning)]" />
21
- <div className="w-2.5 h-2.5 rounded-full bg-[var(--color-terminal-accent)]" />
22
- </div>
23
- {title && (
24
- <span className="ml-2 truncate font-[var(--font-mono)] text-[10px] text-[var(--color-terminal-muted)]">
25
- {title}
26
- </span>
27
- )}
28
- </div>
29
- {/* Content */}
30
- <div className="bg-[var(--color-terminal-bg)] text-[var(--color-terminal-fg)]">
31
- {children}
32
- </div>
33
- </div>
34
- )
35
- }
@@ -1,87 +0,0 @@
1
- import { useState, useEffect, useRef } from 'react'
2
- import { useTranslation } from '../../i18n'
3
-
4
- export function ThinkingBlock({ content, isActive = false }: { content: string; isActive?: boolean }) {
5
- const t = useTranslation()
6
- const [expanded, setExpanded] = useState(false)
7
- const contentRef = useRef<HTMLDivElement>(null)
8
-
9
- useEffect(() => {
10
- if (expanded && isActive && contentRef.current) {
11
- contentRef.current.scrollTop = contentRef.current.scrollHeight
12
- }
13
- }, [content, expanded, isActive])
14
-
15
- // Preview: take first meaningful line, not first 140 chars
16
- const lines = content.split('\n').filter((l) => l.trim())
17
- const firstLine = lines[0]?.replace(/\s+/g, ' ').trim() || ''
18
- const preview = firstLine.length > 80 ? firstLine.slice(0, 80) + '...' : firstLine
19
-
20
- return (
21
- <div className="mb-1 ml-10">
22
- <style>{thinkingStyles}</style>
23
- <button
24
- onClick={() => setExpanded((v) => !v)}
25
- className="flex w-full items-center gap-1.5 rounded-md px-1 py-0.5 text-left text-[12px] text-[var(--color-text-tertiary)] transition-colors hover:text-[var(--color-text-secondary)]"
26
- >
27
- <span className="text-[10px] text-[var(--color-outline)]">
28
- {expanded ? '\u25BE' : '\u25B8'}
29
- </span>
30
- <span className="shrink-0 font-medium italic">
31
- {t('thinking.label')}
32
- {isActive && <span className="thinking-dots" />}
33
- </span>
34
- {!expanded && preview && (
35
- <span className="min-w-0 flex-1 truncate font-[var(--font-mono)] text-[11px] text-[var(--color-text-tertiary)]">
36
- {preview}
37
- {isActive && <span className="thinking-inline-cursor" />}
38
- </span>
39
- )}
40
- </button>
41
- {expanded && (
42
- <div
43
- ref={contentRef}
44
- className="mt-1 max-h-[300px] overflow-y-auto rounded-lg border border-[var(--color-border)]/40 bg-[var(--color-surface-container-lowest)] p-2.5 font-[var(--font-mono)] text-[11px] leading-[1.35] text-[var(--color-text-secondary)] whitespace-pre-wrap break-words"
45
- >
46
- {content}
47
- {isActive && expanded && <span className="thinking-cursor" />}
48
- </div>
49
- )}
50
- </div>
51
- )
52
- }
53
-
54
- const thinkingStyles = `
55
- @keyframes thinking-cursor-blink {
56
- 0%, 100% { opacity: 1; }
57
- 50% { opacity: 0; }
58
- }
59
- @keyframes thinking-dots {
60
- 0%, 20% { content: ''; }
61
- 40% { content: '.'; }
62
- 60% { content: '..'; }
63
- 80%, 100% { content: '...'; }
64
- }
65
- .thinking-cursor {
66
- display: inline-block;
67
- width: 2px;
68
- height: 1em;
69
- background: var(--color-text-tertiary);
70
- vertical-align: middle;
71
- margin-left: 1px;
72
- animation: thinking-cursor-blink 1s step-end infinite;
73
- }
74
- .thinking-inline-cursor {
75
- display: inline-block;
76
- width: 1px;
77
- height: 0.95em;
78
- margin-left: 3px;
79
- vertical-align: text-bottom;
80
- background: var(--color-text-tertiary);
81
- animation: thinking-cursor-blink 1s step-end infinite;
82
- }
83
- .thinking-dots::after {
84
- content: '';
85
- animation: thinking-dots 1.4s steps(1, end) infinite;
86
- }
87
- `