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.
- package/bin/bingo-win.cjs +34 -3
- package/desktop/README.md +30 -0
- package/desktop/bunfig.toml +1 -0
- package/desktop/index.html +17 -0
- package/desktop/package.json +55 -0
- package/desktop/pnpm-lock.yaml +3832 -0
- package/desktop/public/app-icon.jpg +0 -0
- package/desktop/public/fonts/inter-latin-ext.woff2 +0 -0
- package/desktop/public/fonts/inter-latin.woff2 +0 -0
- package/desktop/public/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
- package/desktop/public/fonts/jetbrains-mono-latin.woff2 +0 -0
- package/desktop/public/fonts/manrope-latin-ext.woff2 +0 -0
- package/desktop/public/fonts/manrope-latin.woff2 +0 -0
- package/desktop/public/fonts/material-symbols-outlined.woff2 +0 -0
- package/desktop/public/icons/bilibili.svg +1 -0
- package/desktop/public/icons/douyin.svg +1 -0
- package/desktop/public/icons/github.svg +3 -0
- package/desktop/public/icons/xiaohongshu.svg +1 -0
- package/desktop/scripts/build-macos-arm64.sh +270 -0
- package/desktop/scripts/build-sidecars.ts +183 -0
- package/desktop/scripts/build-windows-x64.ps1 +295 -0
- package/desktop/scripts/scan-missing-imports.ts +235 -0
- package/desktop/sidecars/claude-sidecar.ts +156 -0
- package/desktop/src/App.tsx +5 -0
- package/desktop/src/__tests__/agentsSettings.test.tsx +349 -0
- package/desktop/src/__tests__/pages.test.tsx +290 -0
- package/desktop/src/__tests__/skillsSettings.test.tsx +205 -0
- package/desktop/src/api/adapters.ts +12 -0
- package/desktop/src/api/agents.ts +36 -0
- package/desktop/src/api/cliTasks.ts +28 -0
- package/desktop/src/api/client.ts +63 -0
- package/desktop/src/api/computerUse.ts +76 -0
- package/desktop/src/api/filesystem.ts +30 -0
- package/desktop/src/api/hahaOAuth.ts +38 -0
- package/desktop/src/api/models.ts +28 -0
- package/desktop/src/api/providers.ts +63 -0
- package/desktop/src/api/search.ts +29 -0
- package/desktop/src/api/sessions.ts +56 -0
- package/desktop/src/api/settings.ts +20 -0
- package/desktop/src/api/skills.ts +19 -0
- package/desktop/src/api/tasks.ts +36 -0
- package/desktop/src/api/teams.ts +44 -0
- package/desktop/src/api/websocket.ts +164 -0
- package/desktop/src/components/chat/AskUserQuestion.tsx +268 -0
- package/desktop/src/components/chat/AssistantMessage.tsx +29 -0
- package/desktop/src/components/chat/AttachmentGallery.tsx +113 -0
- package/desktop/src/components/chat/ChatInput.tsx +622 -0
- package/desktop/src/components/chat/CodeViewer.tsx +161 -0
- package/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx +174 -0
- package/desktop/src/components/chat/ComputerUsePermissionModal.tsx +311 -0
- package/desktop/src/components/chat/DiffViewer.tsx +157 -0
- package/desktop/src/components/chat/FileSearchMenu.tsx +198 -0
- package/desktop/src/components/chat/ImageGalleryModal.tsx +91 -0
- package/desktop/src/components/chat/InlineImageGallery.tsx +106 -0
- package/desktop/src/components/chat/InlineTaskSummary.tsx +60 -0
- package/desktop/src/components/chat/MermaidRenderer.test.tsx +98 -0
- package/desktop/src/components/chat/MermaidRenderer.tsx +361 -0
- package/desktop/src/components/chat/MessageActionBar.tsx +27 -0
- package/desktop/src/components/chat/MessageList.test.tsx +313 -0
- package/desktop/src/components/chat/MessageList.tsx +249 -0
- package/desktop/src/components/chat/PermissionDialog.tsx +262 -0
- package/desktop/src/components/chat/SessionTaskBar.test.tsx +99 -0
- package/desktop/src/components/chat/SessionTaskBar.tsx +159 -0
- package/desktop/src/components/chat/StreamingIndicator.tsx +41 -0
- package/desktop/src/components/chat/TerminalChrome.tsx +35 -0
- package/desktop/src/components/chat/ThinkingBlock.tsx +87 -0
- package/desktop/src/components/chat/ToolCallBlock.tsx +247 -0
- package/desktop/src/components/chat/ToolCallGroup.tsx +617 -0
- package/desktop/src/components/chat/ToolResultBlock.tsx +107 -0
- package/desktop/src/components/chat/UserMessage.tsx +38 -0
- package/desktop/src/components/chat/chatBlocks.test.tsx +136 -0
- package/desktop/src/components/chat/clipboard.ts +25 -0
- package/desktop/src/components/chat/composerUtils.test.ts +55 -0
- package/desktop/src/components/chat/composerUtils.ts +149 -0
- package/desktop/src/components/controls/ModelSelector.tsx +156 -0
- package/desktop/src/components/controls/PermissionModeSelector.tsx +229 -0
- package/desktop/src/components/layout/AppShell.tsx +107 -0
- package/desktop/src/components/layout/ContentRouter.tsx +27 -0
- package/desktop/src/components/layout/ProjectFilter.tsx +126 -0
- package/desktop/src/components/layout/Sidebar.test.tsx +158 -0
- package/desktop/src/components/layout/Sidebar.tsx +384 -0
- package/desktop/src/components/layout/StatusBar.tsx +31 -0
- package/desktop/src/components/layout/TabBar.test.tsx +136 -0
- package/desktop/src/components/layout/TabBar.tsx +318 -0
- package/desktop/src/components/layout/TitleBar.tsx +96 -0
- package/desktop/src/components/layout/WindowControls.test.tsx +69 -0
- package/desktop/src/components/layout/WindowControls.tsx +89 -0
- package/desktop/src/components/markdown/MarkdownRenderer.test.tsx +100 -0
- package/desktop/src/components/markdown/MarkdownRenderer.tsx +229 -0
- package/desktop/src/components/settings/ClaudeOfficialLogin.tsx +107 -0
- package/desktop/src/components/shared/Button.tsx +63 -0
- package/desktop/src/components/shared/CopyButton.tsx +58 -0
- package/desktop/src/components/shared/DirectoryPicker.tsx +316 -0
- package/desktop/src/components/shared/Dropdown.tsx +91 -0
- package/desktop/src/components/shared/Input.tsx +38 -0
- package/desktop/src/components/shared/Modal.tsx +65 -0
- package/desktop/src/components/shared/ProjectContextChip.tsx +30 -0
- package/desktop/src/components/shared/Spinner.tsx +30 -0
- package/desktop/src/components/shared/Textarea.tsx +38 -0
- package/desktop/src/components/shared/Toast.tsx +47 -0
- package/desktop/src/components/shared/UpdateChecker.tsx +90 -0
- package/desktop/src/components/skills/SkillDetail.test.tsx +89 -0
- package/desktop/src/components/skills/SkillDetail.tsx +403 -0
- package/desktop/src/components/skills/SkillList.tsx +254 -0
- package/desktop/src/components/tasks/DayOfWeekPicker.tsx +57 -0
- package/desktop/src/components/tasks/NewTaskModal.tsx +407 -0
- package/desktop/src/components/tasks/PromptEditor.tsx +74 -0
- package/desktop/src/components/tasks/TaskEmptyState.tsx +30 -0
- package/desktop/src/components/tasks/TaskList.tsx +46 -0
- package/desktop/src/components/tasks/TaskRow.tsx +253 -0
- package/desktop/src/components/tasks/TaskRunsPanel.tsx +195 -0
- package/desktop/src/components/teams/TeamStatusBar.tsx +147 -0
- package/desktop/src/config/providerPresets.ts +78 -0
- package/desktop/src/config/spinnerVerbs.ts +193 -0
- package/desktop/src/hooks/useKeyboardShortcuts.ts +60 -0
- package/desktop/src/i18n/index.ts +54 -0
- package/desktop/src/i18n/locales/en.ts +670 -0
- package/desktop/src/i18n/locales/zh.ts +670 -0
- package/desktop/src/lib/__tests__/cronDescribe.test.ts +93 -0
- package/desktop/src/lib/cronDescribe.ts +188 -0
- package/desktop/src/lib/desktopRuntime.ts +54 -0
- package/desktop/src/lib/parseRunOutput.ts +79 -0
- package/desktop/src/main.tsx +13 -0
- package/desktop/src/mocks/data.ts +202 -0
- package/desktop/src/pages/ActiveSession.test.tsx +181 -0
- package/desktop/src/pages/ActiveSession.tsx +219 -0
- package/desktop/src/pages/AdapterSettings.tsx +375 -0
- package/desktop/src/pages/AgentTeams.tsx +200 -0
- package/desktop/src/pages/ComputerUseSettings.tsx +420 -0
- package/desktop/src/pages/EmptySession.tsx +518 -0
- package/desktop/src/pages/NewTaskModal.tsx +346 -0
- package/desktop/src/pages/ScheduledTasks.tsx +66 -0
- package/desktop/src/pages/ScheduledTasksEmpty.tsx +152 -0
- package/desktop/src/pages/ScheduledTasksList.tsx +416 -0
- package/desktop/src/pages/SessionControls.tsx +460 -0
- package/desktop/src/pages/Settings.tsx +1448 -0
- package/desktop/src/pages/ToolInspection.tsx +235 -0
- package/desktop/src/stores/adapterStore.ts +106 -0
- package/desktop/src/stores/agentStore.ts +34 -0
- package/desktop/src/stores/chatStore.test.ts +505 -0
- package/desktop/src/stores/chatStore.ts +850 -0
- package/desktop/src/stores/cliTaskStore.ts +152 -0
- package/desktop/src/stores/hahaOAuthStore.test.ts +77 -0
- package/desktop/src/stores/hahaOAuthStore.ts +97 -0
- package/desktop/src/stores/providerStore.ts +101 -0
- package/desktop/src/stores/sessionStore.test.ts +63 -0
- package/desktop/src/stores/sessionStore.ts +102 -0
- package/desktop/src/stores/settingsStore.ts +120 -0
- package/desktop/src/stores/skillStore.ts +51 -0
- package/desktop/src/stores/tabStore.ts +169 -0
- package/desktop/src/stores/taskStore.ts +68 -0
- package/desktop/src/stores/teamStore.ts +344 -0
- package/desktop/src/stores/uiStore.ts +100 -0
- package/desktop/src/stores/updateStore.test.ts +71 -0
- package/desktop/src/stores/updateStore.ts +221 -0
- package/desktop/src/theme/globals.css +465 -0
- package/desktop/src/types/adapter.ts +33 -0
- package/desktop/src/types/chat.ts +152 -0
- package/desktop/src/types/cliTask.ts +24 -0
- package/desktop/src/types/provider.ts +62 -0
- package/desktop/src/types/session.ts +27 -0
- package/desktop/src/types/settings.ts +22 -0
- package/desktop/src/types/skill.ts +38 -0
- package/desktop/src/types/task.ts +56 -0
- package/desktop/src/types/team.ts +38 -0
- package/desktop/src-tauri/Cargo.lock +5549 -0
- package/desktop/src-tauri/Cargo.toml +20 -0
- package/desktop/src-tauri/app-icon.svg +13 -0
- package/desktop/src-tauri/build.rs +3 -0
- package/desktop/src-tauri/capabilities/default.json +106 -0
- package/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/desktop/src-tauri/icons/android/values/ic_launcher_background.xml +4 -0
- package/desktop/src-tauri/icons/icon.icns +0 -0
- package/desktop/src-tauri/icons/icon.ico +0 -0
- package/desktop/src-tauri/src/lib.rs +408 -0
- package/desktop/src-tauri/src/main.rs +6 -0
- package/desktop/src-tauri/tauri.conf.json +78 -0
- package/desktop/src-tauri/tauri.macos.conf.json +18 -0
- package/desktop/src-tauri/tauri.release-ci.json +5 -0
- package/desktop/src-tauri/tauri.windows.conf.json +16 -0
- package/desktop/src-tauri/windows-installer-hooks.nsh +17 -0
- package/desktop/tsconfig.json +25 -0
- package/desktop/vite.config.ts +26 -0
- package/desktop/vitest.config.ts +18 -0
- package/package.json +1 -1
- package/src/commands/desktop/desktop.tsx +9 -0
- 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
|
+
}
|