bingocode 1.0.1 → 1.0.3

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 (187) hide show
  1. package/bin/bingo-win.cjs +34 -3
  2. package/desktop/README.md +30 -0
  3. package/desktop/bunfig.toml +1 -0
  4. package/desktop/index.html +17 -0
  5. package/desktop/package.json +55 -0
  6. package/desktop/pnpm-lock.yaml +3832 -0
  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 +1 -0
  16. package/desktop/public/icons/douyin.svg +1 -0
  17. package/desktop/public/icons/github.svg +3 -0
  18. package/desktop/public/icons/xiaohongshu.svg +1 -0
  19. package/desktop/scripts/build-macos-arm64.sh +270 -0
  20. package/desktop/scripts/build-sidecars.ts +183 -0
  21. package/desktop/scripts/build-windows-x64.ps1 +295 -0
  22. package/desktop/scripts/scan-missing-imports.ts +235 -0
  23. package/desktop/sidecars/claude-sidecar.ts +156 -0
  24. package/desktop/src/App.tsx +5 -0
  25. package/desktop/src/__tests__/agentsSettings.test.tsx +349 -0
  26. package/desktop/src/__tests__/pages.test.tsx +290 -0
  27. package/desktop/src/__tests__/skillsSettings.test.tsx +205 -0
  28. package/desktop/src/api/adapters.ts +12 -0
  29. package/desktop/src/api/agents.ts +36 -0
  30. package/desktop/src/api/cliTasks.ts +28 -0
  31. package/desktop/src/api/client.ts +63 -0
  32. package/desktop/src/api/computerUse.ts +76 -0
  33. package/desktop/src/api/filesystem.ts +30 -0
  34. package/desktop/src/api/hahaOAuth.ts +38 -0
  35. package/desktop/src/api/models.ts +28 -0
  36. package/desktop/src/api/providers.ts +63 -0
  37. package/desktop/src/api/search.ts +29 -0
  38. package/desktop/src/api/sessions.ts +56 -0
  39. package/desktop/src/api/settings.ts +20 -0
  40. package/desktop/src/api/skills.ts +19 -0
  41. package/desktop/src/api/tasks.ts +36 -0
  42. package/desktop/src/api/teams.ts +44 -0
  43. package/desktop/src/api/websocket.ts +164 -0
  44. package/desktop/src/components/chat/AskUserQuestion.tsx +268 -0
  45. package/desktop/src/components/chat/AssistantMessage.tsx +29 -0
  46. package/desktop/src/components/chat/AttachmentGallery.tsx +113 -0
  47. package/desktop/src/components/chat/ChatInput.tsx +622 -0
  48. package/desktop/src/components/chat/CodeViewer.tsx +161 -0
  49. package/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx +174 -0
  50. package/desktop/src/components/chat/ComputerUsePermissionModal.tsx +311 -0
  51. package/desktop/src/components/chat/DiffViewer.tsx +157 -0
  52. package/desktop/src/components/chat/FileSearchMenu.tsx +198 -0
  53. package/desktop/src/components/chat/ImageGalleryModal.tsx +91 -0
  54. package/desktop/src/components/chat/InlineImageGallery.tsx +106 -0
  55. package/desktop/src/components/chat/InlineTaskSummary.tsx +60 -0
  56. package/desktop/src/components/chat/MermaidRenderer.test.tsx +98 -0
  57. package/desktop/src/components/chat/MermaidRenderer.tsx +361 -0
  58. package/desktop/src/components/chat/MessageActionBar.tsx +27 -0
  59. package/desktop/src/components/chat/MessageList.test.tsx +313 -0
  60. package/desktop/src/components/chat/MessageList.tsx +249 -0
  61. package/desktop/src/components/chat/PermissionDialog.tsx +262 -0
  62. package/desktop/src/components/chat/SessionTaskBar.test.tsx +99 -0
  63. package/desktop/src/components/chat/SessionTaskBar.tsx +159 -0
  64. package/desktop/src/components/chat/StreamingIndicator.tsx +41 -0
  65. package/desktop/src/components/chat/TerminalChrome.tsx +35 -0
  66. package/desktop/src/components/chat/ThinkingBlock.tsx +87 -0
  67. package/desktop/src/components/chat/ToolCallBlock.tsx +247 -0
  68. package/desktop/src/components/chat/ToolCallGroup.tsx +617 -0
  69. package/desktop/src/components/chat/ToolResultBlock.tsx +107 -0
  70. package/desktop/src/components/chat/UserMessage.tsx +38 -0
  71. package/desktop/src/components/chat/chatBlocks.test.tsx +136 -0
  72. package/desktop/src/components/chat/clipboard.ts +25 -0
  73. package/desktop/src/components/chat/composerUtils.test.ts +55 -0
  74. package/desktop/src/components/chat/composerUtils.ts +149 -0
  75. package/desktop/src/components/controls/ModelSelector.tsx +156 -0
  76. package/desktop/src/components/controls/PermissionModeSelector.tsx +229 -0
  77. package/desktop/src/components/layout/AppShell.tsx +107 -0
  78. package/desktop/src/components/layout/ContentRouter.tsx +27 -0
  79. package/desktop/src/components/layout/ProjectFilter.tsx +126 -0
  80. package/desktop/src/components/layout/Sidebar.test.tsx +158 -0
  81. package/desktop/src/components/layout/Sidebar.tsx +384 -0
  82. package/desktop/src/components/layout/StatusBar.tsx +31 -0
  83. package/desktop/src/components/layout/TabBar.test.tsx +136 -0
  84. package/desktop/src/components/layout/TabBar.tsx +318 -0
  85. package/desktop/src/components/layout/TitleBar.tsx +96 -0
  86. package/desktop/src/components/layout/WindowControls.test.tsx +69 -0
  87. package/desktop/src/components/layout/WindowControls.tsx +89 -0
  88. package/desktop/src/components/markdown/MarkdownRenderer.test.tsx +100 -0
  89. package/desktop/src/components/markdown/MarkdownRenderer.tsx +229 -0
  90. package/desktop/src/components/settings/ClaudeOfficialLogin.tsx +107 -0
  91. package/desktop/src/components/shared/Button.tsx +63 -0
  92. package/desktop/src/components/shared/CopyButton.tsx +58 -0
  93. package/desktop/src/components/shared/DirectoryPicker.tsx +316 -0
  94. package/desktop/src/components/shared/Dropdown.tsx +91 -0
  95. package/desktop/src/components/shared/Input.tsx +38 -0
  96. package/desktop/src/components/shared/Modal.tsx +65 -0
  97. package/desktop/src/components/shared/ProjectContextChip.tsx +30 -0
  98. package/desktop/src/components/shared/Spinner.tsx +30 -0
  99. package/desktop/src/components/shared/Textarea.tsx +38 -0
  100. package/desktop/src/components/shared/Toast.tsx +47 -0
  101. package/desktop/src/components/shared/UpdateChecker.tsx +90 -0
  102. package/desktop/src/components/skills/SkillDetail.test.tsx +89 -0
  103. package/desktop/src/components/skills/SkillDetail.tsx +403 -0
  104. package/desktop/src/components/skills/SkillList.tsx +254 -0
  105. package/desktop/src/components/tasks/DayOfWeekPicker.tsx +57 -0
  106. package/desktop/src/components/tasks/NewTaskModal.tsx +407 -0
  107. package/desktop/src/components/tasks/PromptEditor.tsx +74 -0
  108. package/desktop/src/components/tasks/TaskEmptyState.tsx +30 -0
  109. package/desktop/src/components/tasks/TaskList.tsx +46 -0
  110. package/desktop/src/components/tasks/TaskRow.tsx +253 -0
  111. package/desktop/src/components/tasks/TaskRunsPanel.tsx +195 -0
  112. package/desktop/src/components/teams/TeamStatusBar.tsx +147 -0
  113. package/desktop/src/config/providerPresets.ts +78 -0
  114. package/desktop/src/config/spinnerVerbs.ts +193 -0
  115. package/desktop/src/hooks/useKeyboardShortcuts.ts +60 -0
  116. package/desktop/src/i18n/index.ts +54 -0
  117. package/desktop/src/i18n/locales/en.ts +670 -0
  118. package/desktop/src/i18n/locales/zh.ts +670 -0
  119. package/desktop/src/lib/__tests__/cronDescribe.test.ts +93 -0
  120. package/desktop/src/lib/cronDescribe.ts +188 -0
  121. package/desktop/src/lib/desktopRuntime.ts +54 -0
  122. package/desktop/src/lib/parseRunOutput.ts +79 -0
  123. package/desktop/src/main.tsx +13 -0
  124. package/desktop/src/mocks/data.ts +202 -0
  125. package/desktop/src/pages/ActiveSession.test.tsx +181 -0
  126. package/desktop/src/pages/ActiveSession.tsx +219 -0
  127. package/desktop/src/pages/AdapterSettings.tsx +375 -0
  128. package/desktop/src/pages/AgentTeams.tsx +200 -0
  129. package/desktop/src/pages/ComputerUseSettings.tsx +420 -0
  130. package/desktop/src/pages/EmptySession.tsx +518 -0
  131. package/desktop/src/pages/NewTaskModal.tsx +346 -0
  132. package/desktop/src/pages/ScheduledTasks.tsx +66 -0
  133. package/desktop/src/pages/ScheduledTasksEmpty.tsx +152 -0
  134. package/desktop/src/pages/ScheduledTasksList.tsx +416 -0
  135. package/desktop/src/pages/SessionControls.tsx +460 -0
  136. package/desktop/src/pages/Settings.tsx +1448 -0
  137. package/desktop/src/pages/ToolInspection.tsx +235 -0
  138. package/desktop/src/stores/adapterStore.ts +106 -0
  139. package/desktop/src/stores/agentStore.ts +34 -0
  140. package/desktop/src/stores/chatStore.test.ts +505 -0
  141. package/desktop/src/stores/chatStore.ts +850 -0
  142. package/desktop/src/stores/cliTaskStore.ts +152 -0
  143. package/desktop/src/stores/hahaOAuthStore.test.ts +77 -0
  144. package/desktop/src/stores/hahaOAuthStore.ts +97 -0
  145. package/desktop/src/stores/providerStore.ts +101 -0
  146. package/desktop/src/stores/sessionStore.test.ts +63 -0
  147. package/desktop/src/stores/sessionStore.ts +102 -0
  148. package/desktop/src/stores/settingsStore.ts +120 -0
  149. package/desktop/src/stores/skillStore.ts +51 -0
  150. package/desktop/src/stores/tabStore.ts +169 -0
  151. package/desktop/src/stores/taskStore.ts +68 -0
  152. package/desktop/src/stores/teamStore.ts +344 -0
  153. package/desktop/src/stores/uiStore.ts +100 -0
  154. package/desktop/src/stores/updateStore.test.ts +71 -0
  155. package/desktop/src/stores/updateStore.ts +221 -0
  156. package/desktop/src/theme/globals.css +465 -0
  157. package/desktop/src/types/adapter.ts +33 -0
  158. package/desktop/src/types/chat.ts +152 -0
  159. package/desktop/src/types/cliTask.ts +24 -0
  160. package/desktop/src/types/provider.ts +62 -0
  161. package/desktop/src/types/session.ts +27 -0
  162. package/desktop/src/types/settings.ts +22 -0
  163. package/desktop/src/types/skill.ts +38 -0
  164. package/desktop/src/types/task.ts +56 -0
  165. package/desktop/src/types/team.ts +38 -0
  166. package/desktop/src-tauri/Cargo.lock +5549 -0
  167. package/desktop/src-tauri/Cargo.toml +20 -0
  168. package/desktop/src-tauri/app-icon.svg +13 -0
  169. package/desktop/src-tauri/build.rs +3 -0
  170. package/desktop/src-tauri/capabilities/default.json +106 -0
  171. package/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  172. package/desktop/src-tauri/icons/android/values/ic_launcher_background.xml +4 -0
  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 +408 -0
  176. package/desktop/src-tauri/src/main.rs +6 -0
  177. package/desktop/src-tauri/tauri.conf.json +78 -0
  178. package/desktop/src-tauri/tauri.macos.conf.json +18 -0
  179. package/desktop/src-tauri/tauri.release-ci.json +5 -0
  180. package/desktop/src-tauri/tauri.windows.conf.json +16 -0
  181. package/desktop/src-tauri/windows-installer-hooks.nsh +17 -0
  182. package/desktop/tsconfig.json +25 -0
  183. package/desktop/vite.config.ts +26 -0
  184. package/desktop/vitest.config.ts +18 -0
  185. package/package.json +1 -1
  186. package/src/commands/desktop/desktop.tsx +9 -0
  187. package/src/commands/desktop/index.ts +26 -0
@@ -0,0 +1,375 @@
1
+ import { useState, useEffect, useCallback } from 'react'
2
+ import { useAdapterStore } from '../stores/adapterStore'
3
+ import { useTranslation } from '../i18n'
4
+ import { Input } from '../components/shared/Input'
5
+ import { Button } from '../components/shared/Button'
6
+ import { DirectoryPicker } from '../components/shared/DirectoryPicker'
7
+
8
+ type ImTab = 'feishu' | 'telegram'
9
+
10
+ export function AdapterSettings() {
11
+ const t = useTranslation()
12
+ const { config, isLoading, fetchConfig, updateConfig, generatePairingCode, removePairedUser } = useAdapterStore()
13
+
14
+ // Active IM tab —— Feishu 默认展示,在前
15
+ const [activeIm, setActiveIm] = useState<ImTab>('feishu')
16
+
17
+ // Server —— serverUrl 不再暴露在 UI 里(见下方 Server URL 注释),
18
+ // 桌面端用 Tauri env var 注入动态端口。
19
+ const [defaultProjectDir, setDefaultProjectDir] = useState('')
20
+
21
+ // Telegram
22
+ const [tgBotToken, setTgBotToken] = useState('')
23
+ const [tgAllowedUsers, setTgAllowedUsers] = useState('')
24
+
25
+ // Feishu
26
+ const [fsAppId, setFsAppId] = useState('')
27
+ const [fsAppSecret, setFsAppSecret] = useState('')
28
+ const [fsEncryptKey, setFsEncryptKey] = useState('')
29
+ const [fsVerificationToken, setFsVerificationToken] = useState('')
30
+ const [fsAllowedUsers, setFsAllowedUsers] = useState('')
31
+ const [fsStreamingCard, setFsStreamingCard] = useState(false)
32
+
33
+ const [isSaving, setIsSaving] = useState(false)
34
+ const [saveStatus, setSaveStatus] = useState<'idle' | 'saved' | 'error'>('idle')
35
+ const [saveError, setSaveError] = useState('')
36
+
37
+ // Pairing
38
+ const [pairingCode, setPairingCode] = useState<string | null>(null)
39
+ const [isGenerating, setIsGenerating] = useState(false)
40
+
41
+ useEffect(() => {
42
+ fetchConfig()
43
+ }, [])
44
+
45
+ // Sync form state when config is loaded
46
+ useEffect(() => {
47
+ setDefaultProjectDir(config.defaultProjectDir ?? '')
48
+ setTgBotToken(config.telegram?.botToken ?? '')
49
+ setTgAllowedUsers(config.telegram?.allowedUsers?.join(', ') ?? '')
50
+ setFsAppId(config.feishu?.appId ?? '')
51
+ setFsAppSecret(config.feishu?.appSecret ?? '')
52
+ setFsEncryptKey(config.feishu?.encryptKey ?? '')
53
+ setFsVerificationToken(config.feishu?.verificationToken ?? '')
54
+ setFsAllowedUsers(config.feishu?.allowedUsers?.join(', ') ?? '')
55
+ setFsStreamingCard(config.feishu?.streamingCard ?? false)
56
+ }, [config])
57
+
58
+ async function handleSave() {
59
+ setIsSaving(true)
60
+ setSaveStatus('idle')
61
+ setSaveError('')
62
+ try {
63
+ const patch: Record<string, unknown> = {}
64
+
65
+ if (defaultProjectDir) patch.defaultProjectDir = defaultProjectDir
66
+
67
+ const tgUsers = tgAllowedUsers
68
+ .split(',')
69
+ .map((s) => s.trim())
70
+ .filter(Boolean)
71
+ .map(Number)
72
+ .filter((n) => !isNaN(n))
73
+
74
+ patch.telegram = {
75
+ botToken: tgBotToken || undefined,
76
+ allowedUsers: tgUsers.length ? tgUsers : [],
77
+ }
78
+
79
+ const fsUsers = fsAllowedUsers
80
+ .split(',')
81
+ .map((s) => s.trim())
82
+ .filter(Boolean)
83
+
84
+ patch.feishu = {
85
+ appId: fsAppId || undefined,
86
+ appSecret: fsAppSecret || undefined,
87
+ encryptKey: fsEncryptKey || undefined,
88
+ verificationToken: fsVerificationToken || undefined,
89
+ allowedUsers: fsUsers.length ? fsUsers : [],
90
+ streamingCard: fsStreamingCard,
91
+ }
92
+
93
+ await updateConfig(patch)
94
+ setSaveStatus('saved')
95
+ setTimeout(() => setSaveStatus('idle'), 2000)
96
+ } catch (err) {
97
+ setSaveStatus('error')
98
+ setSaveError(err instanceof Error ? err.message : 'Save failed')
99
+ } finally {
100
+ setIsSaving(false)
101
+ }
102
+ }
103
+
104
+ const handleGenerateCode = useCallback(async () => {
105
+ setIsGenerating(true)
106
+ try {
107
+ const code = await generatePairingCode()
108
+ setPairingCode(code)
109
+ } catch (err) {
110
+ console.error('Failed to generate pairing code:', err)
111
+ } finally {
112
+ setIsGenerating(false)
113
+ }
114
+ }, [generatePairingCode])
115
+
116
+ const handleUnbind = useCallback(async (platform: 'telegram' | 'feishu', userId: string | number) => {
117
+ if (!confirm(t('settings.adapters.unbindConfirm'))) return
118
+ await removePairedUser(platform, userId)
119
+ await fetchConfig()
120
+ }, [removePairedUser, fetchConfig, t])
121
+
122
+ // Collect all paired users across platforms
123
+ const allPairedUsers = [
124
+ ...(config.telegram?.pairedUsers ?? []).map((u) => ({ ...u, platform: 'telegram' as const })),
125
+ ...(config.feishu?.pairedUsers ?? []).map((u) => ({ ...u, platform: 'feishu' as const })),
126
+ ]
127
+
128
+ // Check pairing expiry
129
+ const pairingExpiry = config.pairing?.expiresAt
130
+ const isPairingActive = pairingExpiry ? Date.now() < pairingExpiry : false
131
+ const minutesLeft = pairingExpiry ? Math.max(0, Math.ceil((pairingExpiry - Date.now()) / 60000)) : 0
132
+
133
+ if (isLoading) {
134
+ return (
135
+ <div className="flex items-center justify-center py-12 text-[var(--color-text-tertiary)]">
136
+ <span className="material-symbols-outlined animate-spin text-[20px] mr-2">progress_activity</span>
137
+ Loading...
138
+ </div>
139
+ )
140
+ }
141
+
142
+ return (
143
+ <div className="max-w-2xl space-y-8">
144
+ {/* Description */}
145
+ <div>
146
+ <p className="text-sm text-[var(--color-text-secondary)]">{t('settings.adapters.description')}</p>
147
+ </div>
148
+
149
+ {/* Pairing */}
150
+ <section className="rounded-xl border border-[var(--color-border)] overflow-hidden">
151
+ <div className="flex items-center gap-2 px-4 py-3 bg-[var(--color-surface-hover)] border-b border-[var(--color-border)]">
152
+ <span className="material-symbols-outlined text-[18px] text-[var(--color-text-secondary)]">link</span>
153
+ <span className="text-sm font-semibold text-[var(--color-text-primary)]">{t('settings.adapters.pairing')}</span>
154
+ </div>
155
+ <div className="p-4 space-y-4">
156
+ <p className="text-sm text-[var(--color-text-secondary)]">{t('settings.adapters.pairingDesc')}</p>
157
+
158
+ {/* Generate code */}
159
+ <div className="flex items-center gap-3">
160
+ <Button onClick={handleGenerateCode} loading={isGenerating}>
161
+ {pairingCode || isPairingActive ? t('settings.adapters.regenerateCode') : t('settings.adapters.generateCode')}
162
+ </Button>
163
+ {pairingCode && (
164
+ <div className="flex items-center gap-2">
165
+ <span className="font-mono text-2xl font-bold tracking-[0.3em] text-[var(--color-brand)]">
166
+ {pairingCode}
167
+ </span>
168
+ <span className="text-xs text-[var(--color-text-tertiary)]">
169
+ {t('settings.adapters.codeExpiresIn')} 60 {t('settings.adapters.minutes')}
170
+ </span>
171
+ </div>
172
+ )}
173
+ {!pairingCode && isPairingActive && (
174
+ <span className="text-xs text-[var(--color-text-tertiary)]">
175
+ {t('settings.adapters.codeExpiresIn')} {minutesLeft} {t('settings.adapters.minutes')}
176
+ </span>
177
+ )}
178
+ </div>
179
+ {pairingCode && (
180
+ <p className="text-xs text-[var(--color-text-tertiary)]">{t('settings.adapters.pairingCodeHint')}</p>
181
+ )}
182
+
183
+ {/* Paired users list */}
184
+ <div>
185
+ <h4 className="text-sm font-medium text-[var(--color-text-primary)] mb-2">{t('settings.adapters.pairedUsers')}</h4>
186
+ {allPairedUsers.length === 0 ? (
187
+ <p className="text-sm text-[var(--color-text-tertiary)]">{t('settings.adapters.noPairedUsers')}</p>
188
+ ) : (
189
+ <div className="space-y-2">
190
+ {allPairedUsers.map((user) => (
191
+ <div
192
+ key={`${user.platform}-${user.userId}`}
193
+ className="flex items-center justify-between px-3 py-2 rounded-lg bg-[var(--color-surface-hover)]"
194
+ >
195
+ <div className="flex items-center gap-2">
196
+ <span className="text-xs px-1.5 py-0.5 rounded bg-[var(--color-surface)] text-[var(--color-text-secondary)]">
197
+ {t(`settings.adapters.platform.${user.platform}`)}
198
+ </span>
199
+ <span className="text-sm text-[var(--color-text-primary)]">{user.displayName}</span>
200
+ <span className="text-xs text-[var(--color-text-tertiary)]">
201
+ {new Date(user.pairedAt).toLocaleDateString()}
202
+ </span>
203
+ </div>
204
+ <button
205
+ onClick={() => handleUnbind(user.platform, user.userId)}
206
+ className="text-xs text-[var(--color-error)] hover:underline cursor-pointer"
207
+ >
208
+ {t('settings.adapters.unbind')}
209
+ </button>
210
+ </div>
211
+ ))}
212
+ </div>
213
+ )}
214
+ </div>
215
+ </div>
216
+ </section>
217
+
218
+ {/* Server URL —— 之前是个手填字段,但桌面端 Tauri 启动 adapter sidecar
219
+ 时已经把 server 的动态端口通过 ADAPTER_SERVER_URL env var 注进去了,
220
+ loadConfig() 里 env 优先级高于这里的 file value,所以这个字段在桌面
221
+ 运行时完全不会被读到。用户也根本不知道该填什么端口(每次启动随机)。
222
+ Standalone 模式(直接 bun run adapters/...)保留 file 字段兜底就够了。 */}
223
+
224
+ {/* Default Project */}
225
+ <div className="flex flex-col gap-1">
226
+ <label className="text-sm font-medium text-[var(--color-text-primary)]">
227
+ {t('settings.adapters.defaultProject')}
228
+ </label>
229
+ <DirectoryPicker value={defaultProjectDir} onChange={setDefaultProjectDir} />
230
+ <p className="text-xs text-[var(--color-text-tertiary)]">
231
+ {t('settings.adapters.defaultProjectHint')}
232
+ </p>
233
+ </div>
234
+
235
+ {/* IM Adapter Tabs —— Feishu 默认在前,Telegram 在后 */}
236
+ <section className="rounded-xl border border-[var(--color-border)] overflow-hidden">
237
+ <div role="tablist" aria-label="IM adapter" className="flex items-stretch border-b border-[var(--color-border)] bg-[var(--color-surface-hover)]">
238
+ <ImTabButton
239
+ label={t('settings.adapters.feishu')}
240
+ active={activeIm === 'feishu'}
241
+ onClick={() => setActiveIm('feishu')}
242
+ />
243
+ <ImTabButton
244
+ label={t('settings.adapters.telegram')}
245
+ active={activeIm === 'telegram'}
246
+ onClick={() => setActiveIm('telegram')}
247
+ />
248
+ </div>
249
+
250
+ {activeIm === 'feishu' && (
251
+ <div className="p-4 space-y-4">
252
+ <div className="grid grid-cols-2 gap-4">
253
+ <Input
254
+ label={t('settings.adapters.appId')}
255
+ value={fsAppId}
256
+ onChange={(e) => setFsAppId(e.target.value)}
257
+ placeholder={t('settings.adapters.appIdPlaceholder')}
258
+ />
259
+ <Input
260
+ label={t('settings.adapters.appSecret')}
261
+ type="password"
262
+ value={fsAppSecret}
263
+ onChange={(e) => setFsAppSecret(e.target.value)}
264
+ placeholder={t('settings.adapters.appSecretPlaceholder')}
265
+ />
266
+ </div>
267
+ <div className="grid grid-cols-2 gap-4">
268
+ <Input
269
+ label={t('settings.adapters.encryptKey')}
270
+ type="password"
271
+ value={fsEncryptKey}
272
+ onChange={(e) => setFsEncryptKey(e.target.value)}
273
+ placeholder={t('settings.adapters.encryptKeyPlaceholder')}
274
+ />
275
+ <Input
276
+ label={t('settings.adapters.verificationToken')}
277
+ type="password"
278
+ value={fsVerificationToken}
279
+ onChange={(e) => setFsVerificationToken(e.target.value)}
280
+ placeholder={t('settings.adapters.verificationTokenPlaceholder')}
281
+ />
282
+ </div>
283
+ <div className="flex flex-col gap-1">
284
+ <Input
285
+ label={t('settings.adapters.allowedUsers')}
286
+ value={fsAllowedUsers}
287
+ onChange={(e) => setFsAllowedUsers(e.target.value)}
288
+ placeholder={t('settings.adapters.fsAllowedUsersPlaceholder')}
289
+ />
290
+ <p className="text-xs text-[var(--color-text-tertiary)]">{t('settings.adapters.allowedUsersHint')}</p>
291
+ </div>
292
+ <label className="flex items-center gap-3 cursor-pointer">
293
+ <input
294
+ type="checkbox"
295
+ checked={fsStreamingCard}
296
+ onChange={(e) => setFsStreamingCard(e.target.checked)}
297
+ className="w-4 h-4 rounded border-[var(--color-border)] accent-[var(--color-brand)]"
298
+ />
299
+ <div>
300
+ <span className="text-sm text-[var(--color-text-primary)]">{t('settings.adapters.streamingCard')}</span>
301
+ <p className="text-xs text-[var(--color-text-tertiary)]">{t('settings.adapters.streamingCardDesc')}</p>
302
+ </div>
303
+ </label>
304
+ </div>
305
+ )}
306
+
307
+ {activeIm === 'telegram' && (
308
+ <div className="p-4 space-y-4">
309
+ <Input
310
+ label={t('settings.adapters.botToken')}
311
+ type="password"
312
+ value={tgBotToken}
313
+ onChange={(e) => setTgBotToken(e.target.value)}
314
+ placeholder={t('settings.adapters.botTokenPlaceholder')}
315
+ />
316
+ <div className="flex flex-col gap-1">
317
+ <Input
318
+ label={t('settings.adapters.allowedUsers')}
319
+ value={tgAllowedUsers}
320
+ onChange={(e) => setTgAllowedUsers(e.target.value)}
321
+ placeholder={t('settings.adapters.tgAllowedUsersPlaceholder')}
322
+ />
323
+ <p className="text-xs text-[var(--color-text-tertiary)]">{t('settings.adapters.allowedUsersHint')}</p>
324
+ </div>
325
+ </div>
326
+ )}
327
+ </section>
328
+
329
+ {/* Save */}
330
+ <div className="flex items-center gap-3">
331
+ <Button onClick={handleSave} loading={isSaving}>
332
+ {saveStatus === 'saved' ? t('settings.adapters.saved') : t('settings.adapters.save')}
333
+ </Button>
334
+ {saveStatus === 'saved' && (
335
+ <span className="text-sm text-[var(--color-success)]">
336
+ <span className="material-symbols-outlined text-[16px] align-middle mr-1">check_circle</span>
337
+ {t('settings.adapters.saved')}
338
+ </span>
339
+ )}
340
+ {saveStatus === 'error' && (
341
+ <span className="text-sm text-[var(--color-error)]">
342
+ <span className="material-symbols-outlined text-[16px] align-middle mr-1">error</span>
343
+ {saveError}
344
+ </span>
345
+ )}
346
+ </div>
347
+ </div>
348
+ )
349
+ }
350
+
351
+ function ImTabButton({
352
+ label,
353
+ active,
354
+ onClick,
355
+ }: {
356
+ label: string
357
+ active: boolean
358
+ onClick: () => void
359
+ }) {
360
+ return (
361
+ <button
362
+ type="button"
363
+ role="tab"
364
+ aria-selected={active}
365
+ onClick={onClick}
366
+ className={`relative px-4 py-2.5 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-brand)] focus-visible:ring-inset ${
367
+ active
368
+ ? 'text-[var(--color-text-primary)] font-semibold after:absolute after:left-3 after:right-3 after:bottom-0 after:h-[2px] after:bg-[var(--color-brand)]'
369
+ : 'text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]'
370
+ }`}
371
+ >
372
+ {label}
373
+ </button>
374
+ )
375
+ }
@@ -0,0 +1,200 @@
1
+ import { useState } from 'react'
2
+ import { mockTeam, mockTeamMessages } from '../mocks/data'
3
+
4
+ // ─── Inline keyframes for pulse-subtle animation ─────────────────
5
+ const pulseSubtleStyle = `
6
+ @keyframes pulse-subtle {
7
+ 0%, 100% { opacity: 1; }
8
+ 50% { opacity: 0.7; transform: scale(0.98); }
9
+ }
10
+ .animate-pulse-subtle {
11
+ animation: pulse-subtle 2s ease-in-out infinite;
12
+ }
13
+ `
14
+
15
+ export function AgentTeams() {
16
+ const [inputValue, setInputValue] = useState('')
17
+
18
+ return (
19
+ <>
20
+ <style>{pulseSubtleStyle}</style>
21
+
22
+ <div className="flex-1 flex flex-col relative overflow-hidden bg-[var(--color-surface)] text-[var(--color-text-primary)]" style={{ fontFamily: 'var(--font-body)' }}>
23
+ {/* Code Content Area */}
24
+ <div className="flex-1 overflow-y-auto p-6 md:p-10 max-w-5xl mx-auto w-full">
25
+ <div className="space-y-8">
26
+ {/* ─── Message Thread ─── */}
27
+ <div className="space-y-6">
28
+ {/* USER message */}
29
+ <div className="flex gap-4 group">
30
+ <div className="w-8 h-8 rounded-full bg-[var(--color-primary-fixed)] flex-shrink-0 flex items-center justify-center text-[var(--color-on-primary)] font-bold text-xs">
31
+ U
32
+ </div>
33
+ <div className="space-y-2">
34
+ <p className="text-xs font-semibold text-[var(--color-text-tertiary)] uppercase tracking-widest">
35
+ User
36
+ </p>
37
+ <p className="text-[var(--color-text-primary)] leading-relaxed">
38
+ {mockTeamMessages.userMessage}
39
+ </p>
40
+ </div>
41
+ </div>
42
+
43
+ {/* CLAUDE COMPANION response */}
44
+ <div className="flex gap-4 group">
45
+ <div className="w-8 h-8 rounded-full bg-[var(--color-tertiary-container)] flex-shrink-0 flex items-center justify-center text-[var(--color-tertiary)]">
46
+ <span
47
+ className="material-symbols-outlined text-sm"
48
+ style={{ fontVariationSettings: "'FILL' 1" }}
49
+ >
50
+ smart_toy
51
+ </span>
52
+ </div>
53
+ <div className="space-y-4 flex-1">
54
+ <p className="text-xs font-semibold text-[var(--color-text-tertiary)] uppercase tracking-widest">
55
+ Claude Companion
56
+ </p>
57
+ <div className="rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-container-low)] p-5 shadow-[var(--shadow-dropdown)]">
58
+ <p className="mb-4 text-[var(--color-text-primary)]">
59
+ {mockTeamMessages.assistantMessage}
60
+ </p>
61
+ <div className="rounded-lg bg-[var(--color-surface-container-high)] p-4 font-[var(--font-mono)] text-[13px] text-[var(--color-text-secondary)] overflow-x-auto">
62
+ <span className="text-[var(--color-brand)]">info:</span> spawning child_processes for parallel development
63
+ <br />
64
+ <span className="text-[var(--color-secondary)]">active:</span> session-dev cluster initiated
65
+ <br />
66
+ <span className="text-[var(--color-tertiary)]">ready:</span> 4 agents assigned
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ {/* ─── TEAM STRIP ─── */}
74
+ <div className="relative py-8">
75
+ <div className="absolute inset-x-0 top-1/2 -translate-y-1/2 h-px bg-[var(--color-border-separator)]" />
76
+
77
+ <div className="relative glass-panel p-4 rounded-2xl flex flex-col md:flex-row md:items-center gap-4 overflow-hidden">
78
+ {/* Team label */}
79
+ <div className="flex items-center gap-3 pr-4 md:border-r border-[var(--color-border-separator)]">
80
+ <div className="p-2 bg-[var(--color-primary-fixed)]/20 rounded-lg">
81
+ <span className="material-symbols-outlined text-[var(--color-brand)] text-xl">
82
+ groups
83
+ </span>
84
+ </div>
85
+ <div>
86
+ <h3 className="text-sm font-bold text-[var(--color-text-primary)]" style={{ fontFamily: 'var(--font-headline)' }}>
87
+ Team: {mockTeam.name}
88
+ </h3>
89
+ <p className="text-[11px] font-medium text-[var(--color-text-tertiary)] uppercase tracking-tighter">
90
+ {mockTeam.memberCount} members
91
+ </p>
92
+ </div>
93
+ </div>
94
+
95
+ {/* Agent Chips */}
96
+ <div className="flex flex-wrap gap-2 items-center flex-1">
97
+ {mockTeam.members.map((member) => {
98
+ if (member.status === 'completed') {
99
+ return (
100
+ <div
101
+ key={member.id}
102
+ className="flex items-center gap-2 px-3 py-1.5 bg-[var(--color-surface-container-high)] rounded-full border border-[var(--color-success)]/20 group hover:border-[var(--color-success)]/50 transition-all cursor-pointer"
103
+ >
104
+ <div className="w-2 h-2 rounded-full bg-[var(--color-success)] shadow-[0_0_8px_rgba(126,219,139,0.4)]" />
105
+ <span className="text-xs font-semibold text-[var(--color-text-primary)]">
106
+ {member.role}
107
+ </span>
108
+ <span
109
+ className="material-symbols-outlined text-[14px] text-[var(--color-success)]"
110
+ style={{ fontVariationSettings: "'FILL' 1" }}
111
+ >
112
+ check_circle
113
+ </span>
114
+ </div>
115
+ )
116
+ }
117
+
118
+ if (member.status === 'running') {
119
+ return (
120
+ <div
121
+ key={member.id}
122
+ className="flex items-center gap-2 px-3 py-1.5 bg-[var(--color-surface-container-high)] rounded-full border border-[var(--color-brand)]/20 animate-pulse-subtle group hover:border-[var(--color-brand)]/50 transition-all cursor-pointer"
123
+ >
124
+ <div className="w-2 h-2 rounded-full bg-[var(--color-warning)] shadow-[0_0_8px_rgba(247,196,108,0.4)]" />
125
+ <span className="text-xs font-semibold text-[var(--color-text-primary)]">
126
+ {member.role}
127
+ </span>
128
+ <span
129
+ className="material-symbols-outlined text-[14px] text-[var(--color-warning)]"
130
+ style={{ fontVariationSettings: "'FILL' 1" }}
131
+ >
132
+ sync
133
+ </span>
134
+ </div>
135
+ )
136
+ }
137
+
138
+ return (
139
+ <div
140
+ key={member.id}
141
+ className="flex items-center gap-2 px-3 py-1.5 bg-[var(--color-surface-container-low)] rounded-full border border-[var(--color-border)] grayscale group hover:grayscale-0 hover:border-[var(--color-secondary)]/50 transition-all cursor-pointer"
142
+ >
143
+ <div className="w-2 h-2 rounded-full bg-[var(--color-text-tertiary)] shadow-[0_0_8px_rgba(135,115,109,0.2)]" />
144
+ <span className="text-xs font-semibold text-[var(--color-text-tertiary)] group-hover:text-[var(--color-text-primary)]">
145
+ {member.role}
146
+ </span>
147
+ <span className="material-symbols-outlined text-[14px] text-[var(--color-text-tertiary)]">
148
+ {member.role === 'Tester' ? 'schedule' : 'pause_circle'}
149
+ </span>
150
+ </div>
151
+ )
152
+ })}
153
+ </div>
154
+
155
+ {/* Expand button */}
156
+ <button className="ml-auto p-2 hover:bg-[var(--color-surface-hover)] rounded-full transition-colors text-[var(--color-text-tertiary)]">
157
+ <span className="material-symbols-outlined text-sm">expand_more</span>
158
+ </button>
159
+ </div>
160
+ </div>
161
+
162
+ {/* ─── Chat Composer ─── */}
163
+ <div className="max-w-3xl mx-auto w-full mt-auto">
164
+ <div className="glass-panel relative rounded-xl p-1.5 flex items-center gap-2 transition-all">
165
+ <div className="p-2 text-[var(--color-text-secondary)]">
166
+ <span className="material-symbols-outlined">attach_file</span>
167
+ </div>
168
+ <input
169
+ className="flex-1 bg-transparent border-none focus:ring-0 focus:outline-none text-sm text-[var(--color-text-primary)] py-2"
170
+ placeholder="Type a command or ask Claude..."
171
+ type="text"
172
+ value={inputValue}
173
+ onChange={(e) => setInputValue(e.target.value)}
174
+ />
175
+ <button className="bg-[image:var(--gradient-btn-primary)] text-[var(--color-btn-primary-fg)] shadow-[var(--shadow-button-primary)] w-9 h-9 rounded-lg flex items-center justify-center transition-all hover:brightness-105 active:scale-95">
176
+ <span
177
+ className="material-symbols-outlined text-lg"
178
+ style={{ fontVariationSettings: "'FILL' 1" }}
179
+ >
180
+ send
181
+ </span>
182
+ </button>
183
+ </div>
184
+ <div className="mt-3 flex justify-center gap-4">
185
+ <div className="flex items-center gap-1.5 text-[10px] text-[var(--color-text-tertiary)] font-semibold uppercase tracking-widest">
186
+ <span className="w-1.5 h-1.5 rounded-full bg-[var(--color-success)]" />
187
+ Auto-run enabled
188
+ </div>
189
+ <div className="flex items-center gap-1.5 text-[10px] text-[var(--color-text-tertiary)] font-semibold uppercase tracking-widest">
190
+ <span className="w-1.5 h-1.5 rounded-full bg-[var(--color-secondary)]" />
191
+ Local LLM
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ </div>
198
+ </>
199
+ )
200
+ }