cowork-os 0.3.21 → 0.3.25

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 (252) hide show
  1. package/README.md +372 -10
  2. package/connectors/README.md +20 -0
  3. package/connectors/asana-mcp/README.md +24 -0
  4. package/connectors/asana-mcp/dist/index.js +427 -0
  5. package/connectors/asana-mcp/package.json +15 -0
  6. package/connectors/asana-mcp/src/index.ts +553 -0
  7. package/connectors/asana-mcp/tsconfig.json +13 -0
  8. package/connectors/hubspot-mcp/README.md +35 -0
  9. package/connectors/hubspot-mcp/dist/index.js +454 -0
  10. package/connectors/hubspot-mcp/package.json +15 -0
  11. package/connectors/hubspot-mcp/src/index.ts +562 -0
  12. package/connectors/hubspot-mcp/tsconfig.json +13 -0
  13. package/connectors/jira-mcp/README.md +49 -0
  14. package/connectors/jira-mcp/dist/index.js +588 -0
  15. package/connectors/jira-mcp/package.json +15 -0
  16. package/connectors/jira-mcp/src/index.ts +711 -0
  17. package/connectors/jira-mcp/tsconfig.json +13 -0
  18. package/connectors/linear-mcp/README.md +22 -0
  19. package/connectors/linear-mcp/dist/index.js +402 -0
  20. package/connectors/linear-mcp/package.json +15 -0
  21. package/connectors/linear-mcp/src/index.ts +522 -0
  22. package/connectors/linear-mcp/tsconfig.json +13 -0
  23. package/connectors/okta-mcp/README.md +24 -0
  24. package/connectors/okta-mcp/dist/index.js +411 -0
  25. package/connectors/okta-mcp/package.json +15 -0
  26. package/connectors/okta-mcp/src/index.ts +520 -0
  27. package/connectors/okta-mcp/tsconfig.json +13 -0
  28. package/connectors/salesforce-mcp/README.md +47 -0
  29. package/connectors/salesforce-mcp/dist/index.js +584 -0
  30. package/connectors/salesforce-mcp/package.json +15 -0
  31. package/connectors/salesforce-mcp/src/index.ts +722 -0
  32. package/connectors/salesforce-mcp/tsconfig.json +13 -0
  33. package/connectors/servicenow-mcp/README.md +26 -0
  34. package/connectors/servicenow-mcp/dist/index.js +400 -0
  35. package/connectors/servicenow-mcp/package.json +15 -0
  36. package/connectors/servicenow-mcp/src/index.ts +500 -0
  37. package/connectors/servicenow-mcp/tsconfig.json +13 -0
  38. package/connectors/templates/mcp-connector/README.md +31 -0
  39. package/connectors/templates/mcp-connector/package.json +15 -0
  40. package/connectors/templates/mcp-connector/src/index.ts +330 -0
  41. package/connectors/templates/mcp-connector/tsconfig.json +13 -0
  42. package/connectors/zendesk-mcp/README.md +40 -0
  43. package/connectors/zendesk-mcp/dist/index.js +431 -0
  44. package/connectors/zendesk-mcp/package.json +15 -0
  45. package/connectors/zendesk-mcp/src/index.ts +543 -0
  46. package/connectors/zendesk-mcp/tsconfig.json +13 -0
  47. package/dist/electron/electron/agent/custom-skill-loader.js +31 -1
  48. package/dist/electron/electron/agent/daemon.js +189 -13
  49. package/dist/electron/electron/agent/executor.js +895 -78
  50. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  51. package/dist/electron/electron/agent/llm/azure-openai-provider.js +328 -0
  52. package/dist/electron/electron/agent/llm/bedrock-provider.js +49 -9
  53. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  54. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  55. package/dist/electron/electron/agent/llm/index.js +13 -1
  56. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  57. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  58. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  59. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  60. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  61. package/dist/electron/electron/agent/llm/provider-factory.js +350 -4
  62. package/dist/electron/electron/agent/llm/types.js +66 -1
  63. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  64. package/dist/electron/electron/agent/search/provider-factory.js +38 -2
  65. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  66. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  67. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  68. package/dist/electron/electron/agent/tools/file-tools.js +66 -3
  69. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  70. package/dist/electron/electron/agent/tools/grep-tools.js +90 -10
  71. package/dist/electron/electron/agent/tools/image-tools.js +11 -1
  72. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  73. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  74. package/dist/electron/electron/agent/tools/registry.js +548 -10
  75. package/dist/electron/electron/agent/tools/search-tools.js +28 -10
  76. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  77. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  78. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  79. package/dist/electron/electron/agents/agent-dispatch.js +63 -0
  80. package/dist/electron/electron/database/repositories.js +19 -5
  81. package/dist/electron/electron/database/schema.js +8 -0
  82. package/dist/electron/electron/gateway/channels/whatsapp.js +55 -0
  83. package/dist/electron/electron/gateway/index.js +75 -1
  84. package/dist/electron/electron/gateway/router.js +209 -154
  85. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  86. package/dist/electron/electron/ipc/handlers.js +763 -267
  87. package/dist/electron/electron/main.js +63 -0
  88. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  89. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  90. package/dist/electron/electron/memory/MemoryService.js +2 -1
  91. package/dist/electron/electron/preload.js +78 -1
  92. package/dist/electron/electron/settings/appearance-manager.js +18 -1
  93. package/dist/electron/electron/settings/box-manager.js +54 -0
  94. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  95. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  96. package/dist/electron/electron/settings/notion-manager.js +56 -0
  97. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  98. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  99. package/dist/electron/electron/utils/box-api.js +153 -0
  100. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  101. package/dist/electron/electron/utils/env-migration.js +19 -0
  102. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  103. package/dist/electron/electron/utils/notion-api.js +103 -0
  104. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  105. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  106. package/dist/electron/electron/utils/validation.js +98 -3
  107. package/dist/electron/electron/utils/x-cli.js +1 -1
  108. package/dist/electron/shared/channelMessages.js +284 -3
  109. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  110. package/dist/electron/shared/types.js +90 -1
  111. package/package.json +14 -3
  112. package/resources/skills/nano-banana-pro.json +4 -4
  113. package/resources/skills/openai-image-gen.json +3 -3
  114. package/resources/skills/scripts/gen.py +163 -0
  115. package/resources/skills/scripts/generate_image.py +91 -0
  116. package/src/electron/agent/custom-skill-loader.ts +34 -1
  117. package/src/electron/agent/daemon.ts +210 -14
  118. package/src/electron/agent/executor.ts +1124 -85
  119. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  120. package/src/electron/agent/llm/azure-openai-provider.ts +388 -0
  121. package/src/electron/agent/llm/bedrock-provider.ts +62 -9
  122. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  123. package/src/electron/agent/llm/groq-provider.ts +39 -0
  124. package/src/electron/agent/llm/index.ts +6 -0
  125. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  126. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  127. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  128. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  129. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  130. package/src/electron/agent/llm/provider-factory.ts +459 -6
  131. package/src/electron/agent/llm/types.ts +95 -1
  132. package/src/electron/agent/llm/xai-provider.ts +39 -0
  133. package/src/electron/agent/search/provider-factory.ts +43 -2
  134. package/src/electron/agent/tools/box-tools.ts +239 -0
  135. package/src/electron/agent/tools/builtin-settings.ts +36 -0
  136. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  137. package/src/electron/agent/tools/file-tools.ts +66 -3
  138. package/src/electron/agent/tools/gmail-tools.ts +240 -0
  139. package/src/electron/agent/tools/google-calendar-tools.ts +258 -0
  140. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  141. package/src/electron/agent/tools/grep-tools.ts +97 -12
  142. package/src/electron/agent/tools/image-tools.ts +11 -1
  143. package/src/electron/agent/tools/notion-tools.ts +330 -0
  144. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  145. package/src/electron/agent/tools/registry.ts +794 -10
  146. package/src/electron/agent/tools/search-tools.ts +29 -11
  147. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  148. package/src/electron/agent/tools/shell-tools.ts +11 -3
  149. package/src/electron/agent/tools/x-tools.ts +1 -1
  150. package/src/electron/agents/agent-dispatch.ts +79 -0
  151. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  152. package/src/electron/database/repositories.ts +58 -6
  153. package/src/electron/database/schema.ts +8 -0
  154. package/src/electron/gateway/channels/discord.ts +4 -0
  155. package/src/electron/gateway/channels/google-chat.ts +3 -0
  156. package/src/electron/gateway/channels/line.ts +3 -0
  157. package/src/electron/gateway/channels/matrix-client.ts +15 -0
  158. package/src/electron/gateway/channels/matrix.ts +31 -0
  159. package/src/electron/gateway/channels/mattermost.ts +3 -0
  160. package/src/electron/gateway/channels/signal.ts +3 -0
  161. package/src/electron/gateway/channels/slack.ts +9 -4
  162. package/src/electron/gateway/channels/teams.ts +4 -0
  163. package/src/electron/gateway/channels/telegram.ts +2 -0
  164. package/src/electron/gateway/channels/twitch.ts +2 -0
  165. package/src/electron/gateway/channels/types.ts +8 -0
  166. package/src/electron/gateway/channels/whatsapp.ts +66 -0
  167. package/src/electron/gateway/index.ts +95 -2
  168. package/src/electron/gateway/router.ts +231 -161
  169. package/src/electron/gateway/security.ts +21 -9
  170. package/src/electron/ipc/canvas-handlers.ts +10 -0
  171. package/src/electron/ipc/handlers.ts +848 -292
  172. package/src/electron/main.ts +35 -0
  173. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  174. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  175. package/src/electron/memory/MemoryService.ts +7 -1
  176. package/src/electron/preload.ts +200 -5
  177. package/src/electron/settings/appearance-manager.ts +20 -2
  178. package/src/electron/settings/box-manager.ts +58 -0
  179. package/src/electron/settings/dropbox-manager.ts +58 -0
  180. package/src/electron/settings/google-workspace-manager.ts +59 -0
  181. package/src/electron/settings/notion-manager.ts +60 -0
  182. package/src/electron/settings/onedrive-manager.ts +58 -0
  183. package/src/electron/settings/sharepoint-manager.ts +58 -0
  184. package/src/electron/utils/box-api.ts +184 -0
  185. package/src/electron/utils/dropbox-api.ts +171 -0
  186. package/src/electron/utils/env-migration.ts +22 -0
  187. package/src/electron/utils/gmail-api.ts +121 -0
  188. package/src/electron/utils/google-calendar-api.ts +115 -0
  189. package/src/electron/utils/google-workspace-api.ts +228 -0
  190. package/src/electron/utils/google-workspace-auth.ts +109 -0
  191. package/src/electron/utils/google-workspace-oauth.ts +232 -0
  192. package/src/electron/utils/notion-api.ts +126 -0
  193. package/src/electron/utils/onedrive-api.ts +137 -0
  194. package/src/electron/utils/sharepoint-api.ts +132 -0
  195. package/src/electron/utils/validation.ts +128 -1
  196. package/src/electron/utils/x-cli.ts +1 -1
  197. package/src/renderer/App.tsx +119 -8
  198. package/src/renderer/components/ActivityFeedItem.tsx +34 -17
  199. package/src/renderer/components/AgentWorkingStatePanel.tsx +7 -5
  200. package/src/renderer/components/AppearanceSettings.tsx +37 -2
  201. package/src/renderer/components/BlueBubblesSettings.tsx +18 -7
  202. package/src/renderer/components/BoxSettings.tsx +203 -0
  203. package/src/renderer/components/BrowserView.tsx +101 -0
  204. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  205. package/src/renderer/components/CanvasPreview.tsx +68 -1
  206. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  207. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  208. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  209. package/src/renderer/components/ControlPlaneSettings.tsx +2 -0
  210. package/src/renderer/components/DiscordSettings.tsx +18 -7
  211. package/src/renderer/components/DropboxSettings.tsx +202 -0
  212. package/src/renderer/components/EmailSettings.tsx +18 -7
  213. package/src/renderer/components/FileViewer.tsx +21 -13
  214. package/src/renderer/components/GoogleChatSettings.tsx +17 -7
  215. package/src/renderer/components/GoogleWorkspaceSettings.tsx +332 -0
  216. package/src/renderer/components/ImessageSettings.tsx +22 -11
  217. package/src/renderer/components/LineIcons.tsx +376 -0
  218. package/src/renderer/components/LineSettings.tsx +18 -7
  219. package/src/renderer/components/MCPSettings.tsx +56 -0
  220. package/src/renderer/components/MainContent.tsx +740 -76
  221. package/src/renderer/components/MatrixSettings.tsx +18 -7
  222. package/src/renderer/components/MattermostSettings.tsx +18 -7
  223. package/src/renderer/components/NodesSettings.tsx +58 -99
  224. package/src/renderer/components/NotificationPanel.tsx +25 -11
  225. package/src/renderer/components/NotionSettings.tsx +231 -0
  226. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  227. package/src/renderer/components/OnboardingModal.tsx +70 -1
  228. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  229. package/src/renderer/components/RightPanel.tsx +141 -28
  230. package/src/renderer/components/ScheduledTasksSettings.tsx +10 -62
  231. package/src/renderer/components/SearchSettings.tsx +118 -114
  232. package/src/renderer/components/Settings.tsx +1425 -651
  233. package/src/renderer/components/SharePointSettings.tsx +224 -0
  234. package/src/renderer/components/Sidebar.tsx +94 -19
  235. package/src/renderer/components/SignalSettings.tsx +18 -7
  236. package/src/renderer/components/SkillHubBrowser.tsx +144 -185
  237. package/src/renderer/components/SlackSettings.tsx +18 -7
  238. package/src/renderer/components/TaskQuickActions.tsx +11 -6
  239. package/src/renderer/components/TaskTimeline.tsx +58 -26
  240. package/src/renderer/components/TeamsSettings.tsx +18 -7
  241. package/src/renderer/components/TelegramSettings.tsx +18 -7
  242. package/src/renderer/components/ThemeIcon.tsx +16 -0
  243. package/src/renderer/components/TwitchSettings.tsx +18 -7
  244. package/src/renderer/components/VoiceSettings.tsx +30 -74
  245. package/src/renderer/components/WhatsAppSettings.tsx +48 -37
  246. package/src/renderer/components/WorkingStateHistory.tsx +7 -5
  247. package/src/renderer/components/WorkspaceSelector.tsx +42 -13
  248. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  249. package/src/renderer/styles/index.css +2333 -209
  250. package/src/shared/channelMessages.ts +367 -4
  251. package/src/shared/llm-provider-catalog.ts +217 -0
  252. package/src/shared/types.ts +251 -2
@@ -221,24 +221,9 @@ function formatDuration(ms: number): string {
221
221
  // Styles
222
222
  const styles = {
223
223
  container: {
224
- padding: '24px',
225
- maxWidth: '900px',
226
- } as React.CSSProperties,
227
- header: {
228
- marginBottom: '24px',
229
- } as React.CSSProperties,
230
- title: {
231
- fontSize: '24px',
232
- fontWeight: 600,
233
- color: 'var(--color-text-primary)',
234
- marginBottom: '8px',
235
- display: 'flex',
236
- alignItems: 'center',
237
- gap: '12px',
238
- } as React.CSSProperties,
239
- subtitle: {
240
- fontSize: '14px',
241
- color: 'var(--color-text-muted)',
224
+ padding: 0,
225
+ maxWidth: '100%',
226
+ width: '100%',
242
227
  } as React.CSSProperties,
243
228
  statsGrid: {
244
229
  display: 'grid',
@@ -266,21 +251,6 @@ const styles = {
266
251
  fontWeight: 600,
267
252
  color: 'var(--color-text-primary)',
268
253
  } as React.CSSProperties,
269
- addButton: {
270
- display: 'inline-flex',
271
- alignItems: 'center',
272
- gap: '8px',
273
- padding: '12px 20px',
274
- backgroundColor: 'var(--color-accent)',
275
- color: '#000',
276
- border: 'none',
277
- borderRadius: 'var(--radius-md)',
278
- fontSize: '14px',
279
- fontWeight: 600,
280
- cursor: 'pointer',
281
- transition: 'all 0.2s ease',
282
- marginBottom: '24px',
283
- } as React.CSSProperties,
284
254
  jobCard: {
285
255
  backgroundColor: 'var(--color-bg-glass)',
286
256
  border: '1px solid var(--color-border-subtle)',
@@ -517,19 +487,7 @@ export function ScheduledTasksSettings() {
517
487
  };
518
488
 
519
489
  if (loading) {
520
- return (
521
- <div style={styles.container}>
522
- <div style={styles.header}>
523
- <div style={styles.title}>
524
- {Icons.clock}
525
- <span>Scheduled Tasks</span>
526
- </div>
527
- </div>
528
- <div style={{ padding: '40px', textAlign: 'center', color: 'var(--color-text-muted)' }}>
529
- Loading...
530
- </div>
531
- </div>
532
- );
490
+ return <div className="settings-loading">Loading scheduled tasks...</div>;
533
491
  }
534
492
 
535
493
  const lastRunJob = jobs.reduce<CronJob | null>((latest, job) => {
@@ -540,15 +498,13 @@ export function ScheduledTasksSettings() {
540
498
 
541
499
  return (
542
500
  <div style={styles.container}>
543
- {/* Header */}
544
- <div style={styles.header}>
545
- <div style={styles.title}>
546
- {Icons.clock}
547
- <span>Scheduled Tasks</span>
501
+ <div className="settings-section">
502
+ <div className="settings-section-header">
503
+ <h3>Scheduled Tasks</h3>
548
504
  </div>
549
- <div style={styles.subtitle}>
505
+ <p className="settings-description">
550
506
  Automate tasks to run on a schedule. Results appear in your workspace.
551
- </div>
507
+ </p>
552
508
  </div>
553
509
 
554
510
  {/* Error Banner */}
@@ -599,19 +555,11 @@ export function ScheduledTasksSettings() {
599
555
 
600
556
  {/* Add Button */}
601
557
  <button
602
- style={styles.addButton}
558
+ className="button-primary button-with-icon scheduled-tasks-add"
603
559
  onClick={() => {
604
560
  setEditingJob(null);
605
561
  setShowCreateModal(true);
606
562
  }}
607
- onMouseOver={(e) => {
608
- e.currentTarget.style.backgroundColor = 'var(--color-accent-hover)';
609
- e.currentTarget.style.transform = 'translateY(-1px)';
610
- }}
611
- onMouseOut={(e) => {
612
- e.currentTarget.style.backgroundColor = 'var(--color-accent)';
613
- e.currentTarget.style.transform = 'translateY(0)';
614
- }}
615
563
  >
616
564
  {Icons.plus}
617
565
  <span>New Scheduled Task</span>
@@ -23,8 +23,8 @@ export function SearchSettings({ onStatusChange }: SearchSettingsProps) {
23
23
  const [googleApiKey, setGoogleApiKey] = useState('');
24
24
  const [googleSearchEngineId, setGoogleSearchEngineId] = useState('');
25
25
 
26
- // Track which sections are expanded
27
- const [expandedProvider, setExpandedProvider] = useState<SearchProviderType | null>(null);
26
+ // Track which provider is active in the tab view
27
+ const [activeProvider, setActiveProvider] = useState<SearchProviderType | null>(null);
28
28
 
29
29
  useEffect(() => {
30
30
  loadConfig();
@@ -37,6 +37,12 @@ export function SearchSettings({ onStatusChange }: SearchSettingsProps) {
37
37
  setConfigStatus(status);
38
38
  setPrimaryProvider(status.primaryProvider);
39
39
  setFallbackProvider(status.fallbackProvider);
40
+ setActiveProvider((prev) => {
41
+ if (prev && status.providers.some((provider) => provider.type === prev)) {
42
+ return prev;
43
+ }
44
+ return status.primaryProvider ?? status.providers[0]?.type ?? null;
45
+ });
40
46
  onStatusChange?.(status.isConfigured);
41
47
  } catch (error) {
42
48
  console.error('Failed to load search config:', error);
@@ -89,6 +95,7 @@ export function SearchSettings({ onStatusChange }: SearchSettingsProps) {
89
95
 
90
96
  const configuredProviders = configStatus?.providers.filter(p => p.configured) || [];
91
97
  const hasMultipleProviders = configuredProviders.length > 1;
98
+ const activeProviderConfig = configStatus?.providers.find(p => p.type === activeProvider) || null;
92
99
 
93
100
  if (loading) {
94
101
  return <div className="settings-loading">Loading search settings...</div>;
@@ -102,129 +109,126 @@ export function SearchSettings({ onStatusChange }: SearchSettingsProps) {
102
109
  Add API keys to enable web search. You can configure multiple providers and set a primary and fallback.
103
110
  </p>
104
111
 
105
- <div className="provider-config-list">
112
+ <div className="llm-provider-tabs">
106
113
  {configStatus?.providers.map(provider => (
107
- <div key={provider.type} className="provider-config-item">
108
- <div
109
- className="provider-config-header"
110
- onClick={() => setExpandedProvider(
111
- expandedProvider === provider.type ? null : provider.type
112
- )}
113
- >
114
- <div className="provider-config-info">
115
- <span className="provider-name">{provider.name}</span>
116
- <span className={`provider-status ${provider.configured ? 'configured' : 'not-configured'}`}>
117
- {provider.configured ? '✓ Configured' : '○ Not configured'}
118
- </span>
119
- </div>
120
- <span className="provider-expand-icon">
121
- {expandedProvider === provider.type ? '▼' : '▶'}
122
- </span>
123
- </div>
114
+ <button
115
+ key={provider.type}
116
+ className={`llm-provider-tab ${activeProvider === provider.type ? 'active' : ''}`}
117
+ onClick={() => {
118
+ setActiveProvider(provider.type);
119
+ setTestResult(null);
120
+ }}
121
+ >
122
+ <span className="llm-provider-tab-label">{provider.name}</span>
123
+ {provider.configured && <span className="llm-provider-tab-status" />}
124
+ </button>
125
+ ))}
126
+ </div>
124
127
 
125
- {expandedProvider === provider.type && (
126
- <div className="provider-config-form">
127
- <p className="provider-description">{provider.description}</p>
128
- <p className="provider-types">
129
- Supports: {provider.supportedTypes.join(', ')}
130
- </p>
128
+ {activeProviderConfig ? (
129
+ <div className="settings-card provider-config-panel">
130
+ <div className="provider-config-form">
131
+ <p className="provider-description">{activeProviderConfig.description}</p>
132
+ <p className="provider-types">
133
+ Supports: {activeProviderConfig.supportedTypes.join(', ')}
134
+ </p>
131
135
 
132
- {provider.type === 'tavily' && (
133
- <div className="settings-field">
134
- <label>Tavily API Key</label>
135
- <input
136
- type="password"
137
- className="settings-input"
138
- placeholder={provider.configured ? '••••••••••••••••' : 'tvly-...'}
139
- value={tavilyApiKey}
140
- onChange={(e) => setTavilyApiKey(e.target.value)}
141
- />
142
- <p className="settings-hint">
143
- Get your API key from <a href="https://tavily.com/" target="_blank" rel="noopener noreferrer">tavily.com</a>
144
- </p>
145
- </div>
146
- )}
136
+ {activeProviderConfig.type === 'tavily' && (
137
+ <div className="settings-field">
138
+ <label>Tavily API Key</label>
139
+ <input
140
+ type="password"
141
+ className="settings-input"
142
+ placeholder={activeProviderConfig.configured ? '••••••••••••••••' : 'tvly-...'}
143
+ value={tavilyApiKey}
144
+ onChange={(e) => setTavilyApiKey(e.target.value)}
145
+ />
146
+ <p className="settings-hint">
147
+ Get your API key from <a href="https://tavily.com/" target="_blank" rel="noopener noreferrer">tavily.com</a>
148
+ </p>
149
+ </div>
150
+ )}
147
151
 
148
- {provider.type === 'brave' && (
149
- <div className="settings-field">
150
- <label>Brave Search API Key</label>
151
- <input
152
- type="password"
153
- className="settings-input"
154
- placeholder={provider.configured ? '••••••••••••••••' : 'BSA...'}
155
- value={braveApiKey}
156
- onChange={(e) => setBraveApiKey(e.target.value)}
157
- />
158
- <p className="settings-hint">
159
- Get your API key from <a href="https://brave.com/search/api/" target="_blank" rel="noopener noreferrer">brave.com/search/api</a>
160
- </p>
161
- </div>
162
- )}
152
+ {activeProviderConfig.type === 'brave' && (
153
+ <div className="settings-field">
154
+ <label>Brave Search API Key</label>
155
+ <input
156
+ type="password"
157
+ className="settings-input"
158
+ placeholder={activeProviderConfig.configured ? '••••••••••••••••' : 'BSA...'}
159
+ value={braveApiKey}
160
+ onChange={(e) => setBraveApiKey(e.target.value)}
161
+ />
162
+ <p className="settings-hint">
163
+ Get your API key from <a href="https://brave.com/search/api/" target="_blank" rel="noopener noreferrer">brave.com/search/api</a>
164
+ </p>
165
+ </div>
166
+ )}
163
167
 
164
- {provider.type === 'serpapi' && (
165
- <div className="settings-field">
166
- <label>SerpAPI Key</label>
167
- <input
168
- type="password"
169
- className="settings-input"
170
- placeholder={provider.configured ? '••••••••••••••••' : 'Enter API key'}
171
- value={serpapiApiKey}
172
- onChange={(e) => setSerpapiApiKey(e.target.value)}
173
- />
174
- <p className="settings-hint">
175
- Get your API key from <a href="https://serpapi.com/" target="_blank" rel="noopener noreferrer">serpapi.com</a>
176
- </p>
177
- </div>
178
- )}
168
+ {activeProviderConfig.type === 'serpapi' && (
169
+ <div className="settings-field">
170
+ <label>SerpAPI Key</label>
171
+ <input
172
+ type="password"
173
+ className="settings-input"
174
+ placeholder={activeProviderConfig.configured ? '••••••••••••••••' : 'Enter API key'}
175
+ value={serpapiApiKey}
176
+ onChange={(e) => setSerpapiApiKey(e.target.value)}
177
+ />
178
+ <p className="settings-hint">
179
+ Get your API key from <a href="https://serpapi.com/" target="_blank" rel="noopener noreferrer">serpapi.com</a>
180
+ </p>
181
+ </div>
182
+ )}
179
183
 
180
- {provider.type === 'google' && (
181
- <>
182
- <div className="settings-field">
183
- <label>Google API Key</label>
184
- <input
185
- type="password"
186
- className="settings-input"
187
- placeholder={provider.configured ? '••••••••••••••••' : 'AIza...'}
188
- value={googleApiKey}
189
- onChange={(e) => setGoogleApiKey(e.target.value)}
190
- />
191
- </div>
192
- <div className="settings-field">
193
- <label>Search Engine ID</label>
194
- <input
195
- type="text"
196
- className="settings-input"
197
- placeholder="Enter Search Engine ID"
198
- value={googleSearchEngineId}
199
- onChange={(e) => setGoogleSearchEngineId(e.target.value)}
200
- />
201
- <p className="settings-hint">
202
- Get your credentials from <a href="https://developers.google.com/custom-search/v1/introduction" target="_blank" rel="noopener noreferrer">Google Custom Search</a>
203
- </p>
204
- </div>
205
- </>
206
- )}
184
+ {activeProviderConfig.type === 'google' && (
185
+ <>
186
+ <div className="settings-field">
187
+ <label>Google API Key</label>
188
+ <input
189
+ type="password"
190
+ className="settings-input"
191
+ placeholder={activeProviderConfig.configured ? '••••••••••••••••' : 'AIza...'}
192
+ value={googleApiKey}
193
+ onChange={(e) => setGoogleApiKey(e.target.value)}
194
+ />
195
+ </div>
196
+ <div className="settings-field">
197
+ <label>Search Engine ID</label>
198
+ <input
199
+ type="text"
200
+ className="settings-input"
201
+ placeholder="Enter Search Engine ID"
202
+ value={googleSearchEngineId}
203
+ onChange={(e) => setGoogleSearchEngineId(e.target.value)}
204
+ />
205
+ <p className="settings-hint">
206
+ Get your credentials from <a href="https://developers.google.com/custom-search/v1/introduction" target="_blank" rel="noopener noreferrer">Google Custom Search</a>
207
+ </p>
208
+ </div>
209
+ </>
210
+ )}
207
211
 
208
- {provider.configured && (
209
- <button
210
- className="button-small button-secondary"
211
- onClick={() => handleTestProvider(provider.type)}
212
- disabled={testingProvider === provider.type}
213
- >
214
- {testingProvider === provider.type ? 'Testing...' : 'Test Connection'}
215
- </button>
216
- )}
212
+ {activeProviderConfig.configured && (
213
+ <button
214
+ className="button-small button-secondary"
215
+ onClick={() => handleTestProvider(activeProviderConfig.type)}
216
+ disabled={testingProvider === activeProviderConfig.type}
217
+ >
218
+ {testingProvider === activeProviderConfig.type ? 'Testing...' : 'Test Connection'}
219
+ </button>
220
+ )}
217
221
 
218
- {testResult?.provider === provider.type && (
219
- <div className={`test-result-inline ${testResult.success ? 'success' : 'error'}`}>
220
- {testResult.success ? '✓ Connection successful' : `✗ ${testResult.error}`}
221
- </div>
222
- )}
222
+ {testResult?.provider === activeProviderConfig.type && (
223
+ <div className={`test-result-inline ${testResult.success ? 'success' : 'error'}`}>
224
+ {testResult.success ? '✓ Connection successful' : `✗ ${testResult.error}`}
223
225
  </div>
224
226
  )}
225
227
  </div>
226
- ))}
227
- </div>
228
+ </div>
229
+ ) : (
230
+ <div className="settings-empty">Select a provider to configure.</div>
231
+ )}
228
232
  </div>
229
233
 
230
234
  {configuredProviders.length > 0 && (