@swarmclawai/swarmclaw 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/README.md +116 -50
  2. package/bin/package-manager.js +157 -0
  3. package/bin/package-manager.test.js +90 -0
  4. package/bin/server-cmd.js +38 -7
  5. package/bin/swarmclaw.js +54 -4
  6. package/bin/update-cmd.js +48 -10
  7. package/bin/update-cmd.test.js +55 -0
  8. package/package.json +8 -3
  9. package/scripts/postinstall.mjs +26 -0
  10. package/src/app/api/agents/[id]/route.ts +43 -0
  11. package/src/app/api/agents/[id]/thread/route.ts +39 -8
  12. package/src/app/api/agents/route.ts +35 -2
  13. package/src/app/api/auth/route.ts +77 -8
  14. package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
  15. package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
  16. package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
  17. package/src/app/api/chatrooms/[id]/route.ts +6 -0
  18. package/src/app/api/chats/[id]/browser/route.ts +5 -1
  19. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  20. package/src/app/api/chats/[id]/messages/route.ts +19 -13
  21. package/src/app/api/chats/[id]/route.ts +30 -0
  22. package/src/app/api/chats/[id]/stop/route.ts +6 -1
  23. package/src/app/api/chats/heartbeat/route.ts +2 -1
  24. package/src/app/api/chats/route.ts +23 -1
  25. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  26. package/src/app/api/connectors/doctor/route.ts +13 -0
  27. package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
  28. package/src/app/api/external-agents/[id]/route.ts +31 -0
  29. package/src/app/api/external-agents/register/route.ts +3 -0
  30. package/src/app/api/external-agents/route.ts +66 -0
  31. package/src/app/api/files/open/route.ts +16 -14
  32. package/src/app/api/gateways/[id]/health/route.ts +28 -0
  33. package/src/app/api/gateways/[id]/route.ts +79 -0
  34. package/src/app/api/gateways/route.ts +57 -0
  35. package/src/app/api/memory/maintenance/route.ts +11 -1
  36. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  37. package/src/app/api/openclaw/gateway/route.ts +10 -7
  38. package/src/app/api/openclaw/skills/route.ts +12 -4
  39. package/src/app/api/plugins/dependencies/route.ts +24 -0
  40. package/src/app/api/plugins/install/route.ts +15 -92
  41. package/src/app/api/plugins/route.ts +3 -26
  42. package/src/app/api/plugins/settings/route.ts +17 -12
  43. package/src/app/api/plugins/ui/route.ts +1 -0
  44. package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
  45. package/src/app/api/schedules/[id]/route.ts +38 -9
  46. package/src/app/api/schedules/route.ts +51 -28
  47. package/src/app/api/settings/route.ts +55 -17
  48. package/src/app/api/setup/doctor/route.ts +6 -4
  49. package/src/app/api/tasks/[id]/route.ts +16 -6
  50. package/src/app/api/tasks/bulk/route.ts +3 -3
  51. package/src/app/api/tasks/route.ts +9 -4
  52. package/src/app/api/webhooks/[id]/route.ts +8 -1
  53. package/src/app/page.tsx +135 -17
  54. package/src/cli/binary.test.js +142 -0
  55. package/src/cli/index.js +38 -11
  56. package/src/cli/index.test.js +195 -0
  57. package/src/cli/index.ts +21 -12
  58. package/src/cli/server-cmd.test.js +59 -0
  59. package/src/cli/spec.js +20 -2
  60. package/src/components/agents/agent-card.tsx +15 -12
  61. package/src/components/agents/agent-chat-list.tsx +101 -1
  62. package/src/components/agents/agent-list.tsx +46 -9
  63. package/src/components/agents/agent-sheet.tsx +456 -23
  64. package/src/components/agents/inspector-panel.tsx +110 -49
  65. package/src/components/agents/sandbox-env-panel.tsx +4 -1
  66. package/src/components/auth/access-key-gate.tsx +36 -97
  67. package/src/components/auth/setup-wizard.tsx +970 -275
  68. package/src/components/chat/chat-area.tsx +70 -27
  69. package/src/components/chat/chat-card.tsx +6 -21
  70. package/src/components/chat/chat-header.tsx +263 -366
  71. package/src/components/chat/chat-list.tsx +62 -26
  72. package/src/components/chat/checkpoint-timeline.tsx +1 -1
  73. package/src/components/chat/message-list.tsx +145 -19
  74. package/src/components/chatrooms/chatroom-input.tsx +96 -33
  75. package/src/components/chatrooms/chatroom-list.tsx +141 -72
  76. package/src/components/chatrooms/chatroom-message.tsx +7 -6
  77. package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
  78. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
  79. package/src/components/chatrooms/chatroom-view.tsx +422 -209
  80. package/src/components/chatrooms/reaction-picker.tsx +38 -33
  81. package/src/components/connectors/connector-list.tsx +265 -127
  82. package/src/components/connectors/connector-sheet.tsx +217 -0
  83. package/src/components/gateways/gateway-sheet.tsx +567 -0
  84. package/src/components/home/home-view.tsx +128 -4
  85. package/src/components/input/chat-input.tsx +135 -86
  86. package/src/components/layout/app-layout.tsx +385 -194
  87. package/src/components/layout/mobile-header.tsx +26 -8
  88. package/src/components/memory/memory-browser.tsx +71 -6
  89. package/src/components/memory/memory-card.tsx +18 -0
  90. package/src/components/memory/memory-detail.tsx +58 -31
  91. package/src/components/memory/memory-sheet.tsx +32 -4
  92. package/src/components/plugins/plugin-list.tsx +15 -3
  93. package/src/components/plugins/plugin-sheet.tsx +118 -9
  94. package/src/components/projects/project-detail.tsx +189 -1
  95. package/src/components/providers/provider-list.tsx +158 -2
  96. package/src/components/providers/provider-sheet.tsx +81 -70
  97. package/src/components/shared/agent-picker-list.tsx +2 -2
  98. package/src/components/shared/bottom-sheet.tsx +31 -15
  99. package/src/components/shared/command-palette.tsx +111 -24
  100. package/src/components/shared/confirm-dialog.tsx +45 -30
  101. package/src/components/shared/model-combobox.tsx +90 -8
  102. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  103. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  104. package/src/components/shared/settings/section-heartbeat.tsx +88 -6
  105. package/src/components/shared/settings/section-orchestrator.tsx +6 -3
  106. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  107. package/src/components/shared/settings/section-secrets.tsx +6 -6
  108. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  109. package/src/components/shared/settings/section-voice.tsx +5 -1
  110. package/src/components/shared/settings/section-web-search.tsx +10 -2
  111. package/src/components/shared/settings/settings-page.tsx +248 -47
  112. package/src/components/tasks/approvals-panel.tsx +211 -18
  113. package/src/components/tasks/task-board.tsx +242 -46
  114. package/src/components/ui/dialog.tsx +2 -2
  115. package/src/components/usage/metrics-dashboard.tsx +74 -1
  116. package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
  117. package/src/components/wallets/wallet-panel.tsx +17 -5
  118. package/src/components/webhooks/webhook-sheet.tsx +7 -7
  119. package/src/lib/auth.ts +17 -0
  120. package/src/lib/chat-streaming-state.test.ts +108 -0
  121. package/src/lib/chat-streaming-state.ts +108 -0
  122. package/src/lib/heartbeat-defaults.ts +48 -0
  123. package/src/lib/memory-presentation.ts +59 -0
  124. package/src/lib/openclaw-agent-id.test.ts +14 -0
  125. package/src/lib/openclaw-agent-id.ts +31 -0
  126. package/src/lib/provider-model-discovery-client.ts +29 -0
  127. package/src/lib/providers/index.ts +12 -5
  128. package/src/lib/runtime-loop.ts +105 -3
  129. package/src/lib/safe-storage.ts +6 -1
  130. package/src/lib/server/agent-assignment.test.ts +112 -0
  131. package/src/lib/server/agent-assignment.ts +169 -0
  132. package/src/lib/server/agent-runtime-config.test.ts +141 -0
  133. package/src/lib/server/agent-runtime-config.ts +277 -0
  134. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  135. package/src/lib/server/approvals-auto-approve.test.ts +264 -0
  136. package/src/lib/server/approvals.ts +483 -75
  137. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  138. package/src/lib/server/browser-state.test.ts +118 -0
  139. package/src/lib/server/browser-state.ts +123 -0
  140. package/src/lib/server/build-llm.test.ts +44 -0
  141. package/src/lib/server/build-llm.ts +11 -4
  142. package/src/lib/server/builtin-plugins.ts +34 -0
  143. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  144. package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
  145. package/src/lib/server/chat-execution.ts +402 -125
  146. package/src/lib/server/chatroom-health.test.ts +26 -0
  147. package/src/lib/server/chatroom-health.ts +2 -3
  148. package/src/lib/server/chatroom-helpers.test.ts +74 -2
  149. package/src/lib/server/chatroom-helpers.ts +144 -11
  150. package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
  151. package/src/lib/server/connectors/discord.ts +175 -11
  152. package/src/lib/server/connectors/doctor.test.ts +80 -0
  153. package/src/lib/server/connectors/doctor.ts +116 -0
  154. package/src/lib/server/connectors/manager.ts +994 -130
  155. package/src/lib/server/connectors/policy.test.ts +222 -0
  156. package/src/lib/server/connectors/policy.ts +452 -0
  157. package/src/lib/server/connectors/slack.ts +189 -10
  158. package/src/lib/server/connectors/telegram.ts +65 -15
  159. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  160. package/src/lib/server/connectors/thread-context.ts +72 -0
  161. package/src/lib/server/connectors/types.ts +41 -11
  162. package/src/lib/server/daemon-state.ts +62 -3
  163. package/src/lib/server/data-dir.ts +13 -0
  164. package/src/lib/server/delegation-jobs.test.ts +140 -0
  165. package/src/lib/server/delegation-jobs.ts +248 -0
  166. package/src/lib/server/document-utils.test.ts +47 -0
  167. package/src/lib/server/document-utils.ts +397 -0
  168. package/src/lib/server/eval/agent-regression.test.ts +47 -0
  169. package/src/lib/server/eval/agent-regression.ts +1742 -0
  170. package/src/lib/server/eval/runner.ts +11 -1
  171. package/src/lib/server/eval/store.ts +2 -1
  172. package/src/lib/server/heartbeat-service.ts +23 -43
  173. package/src/lib/server/heartbeat-source.test.ts +22 -0
  174. package/src/lib/server/heartbeat-source.ts +7 -0
  175. package/src/lib/server/identity-continuity.test.ts +77 -0
  176. package/src/lib/server/identity-continuity.ts +127 -0
  177. package/src/lib/server/mailbox-utils.ts +347 -0
  178. package/src/lib/server/main-agent-loop.ts +31 -964
  179. package/src/lib/server/memory-db.ts +4 -6
  180. package/src/lib/server/memory-tiers.ts +40 -0
  181. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  182. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  183. package/src/lib/server/openclaw-exec-config.ts +6 -5
  184. package/src/lib/server/openclaw-gateway.ts +123 -36
  185. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  186. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  187. package/src/lib/server/openclaw-sync.ts +3 -2
  188. package/src/lib/server/orchestrator-lg.ts +18 -8
  189. package/src/lib/server/orchestrator.ts +5 -4
  190. package/src/lib/server/playwright-proxy.mjs +27 -3
  191. package/src/lib/server/plugins.test.ts +215 -0
  192. package/src/lib/server/plugins.ts +832 -69
  193. package/src/lib/server/provider-health.ts +33 -3
  194. package/src/lib/server/provider-model-discovery.ts +481 -0
  195. package/src/lib/server/queue.ts +4 -21
  196. package/src/lib/server/runtime-settings.test.ts +119 -0
  197. package/src/lib/server/runtime-settings.ts +12 -92
  198. package/src/lib/server/schedule-normalization.ts +187 -0
  199. package/src/lib/server/scheduler.ts +2 -0
  200. package/src/lib/server/session-archive-memory.test.ts +85 -0
  201. package/src/lib/server/session-archive-memory.ts +230 -0
  202. package/src/lib/server/session-mailbox.ts +8 -18
  203. package/src/lib/server/session-reset-policy.test.ts +99 -0
  204. package/src/lib/server/session-reset-policy.ts +311 -0
  205. package/src/lib/server/session-run-manager.ts +33 -80
  206. package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
  207. package/src/lib/server/session-tools/calendar.ts +2 -12
  208. package/src/lib/server/session-tools/connector.ts +109 -8
  209. package/src/lib/server/session-tools/context.ts +14 -2
  210. package/src/lib/server/session-tools/crawl.ts +447 -0
  211. package/src/lib/server/session-tools/crud.ts +96 -34
  212. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  213. package/src/lib/server/session-tools/delegate.ts +406 -20
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
  215. package/src/lib/server/session-tools/discovery.ts +40 -12
  216. package/src/lib/server/session-tools/document.ts +283 -0
  217. package/src/lib/server/session-tools/email.ts +1 -3
  218. package/src/lib/server/session-tools/extract.ts +137 -0
  219. package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
  220. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  221. package/src/lib/server/session-tools/file.ts +243 -24
  222. package/src/lib/server/session-tools/http.ts +9 -3
  223. package/src/lib/server/session-tools/human-loop.ts +227 -0
  224. package/src/lib/server/session-tools/image-gen.ts +1 -3
  225. package/src/lib/server/session-tools/index.ts +87 -2
  226. package/src/lib/server/session-tools/mailbox.ts +276 -0
  227. package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
  228. package/src/lib/server/session-tools/memory.ts +35 -3
  229. package/src/lib/server/session-tools/monitor.ts +162 -12
  230. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  231. package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
  232. package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
  233. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  234. package/src/lib/server/session-tools/platform.ts +142 -4
  235. package/src/lib/server/session-tools/plugin-creator.ts +95 -25
  236. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  237. package/src/lib/server/session-tools/replicate.ts +1 -3
  238. package/src/lib/server/session-tools/sandbox.ts +51 -92
  239. package/src/lib/server/session-tools/schedule.ts +20 -10
  240. package/src/lib/server/session-tools/session-info.ts +58 -4
  241. package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
  242. package/src/lib/server/session-tools/shell.ts +2 -2
  243. package/src/lib/server/session-tools/subagent.ts +195 -27
  244. package/src/lib/server/session-tools/table.ts +587 -0
  245. package/src/lib/server/session-tools/wallet.ts +13 -10
  246. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  247. package/src/lib/server/session-tools/web.ts +947 -108
  248. package/src/lib/server/storage.ts +255 -10
  249. package/src/lib/server/stream-agent-chat.test.ts +61 -0
  250. package/src/lib/server/stream-agent-chat.ts +185 -25
  251. package/src/lib/server/structured-extract.test.ts +72 -0
  252. package/src/lib/server/structured-extract.ts +373 -0
  253. package/src/lib/server/task-mention.test.ts +16 -2
  254. package/src/lib/server/task-mention.ts +61 -11
  255. package/src/lib/server/tool-aliases.ts +80 -12
  256. package/src/lib/server/tool-capability-policy.ts +7 -1
  257. package/src/lib/server/tool-retry.ts +2 -0
  258. package/src/lib/server/watch-jobs.test.ts +173 -0
  259. package/src/lib/server/watch-jobs.ts +532 -0
  260. package/src/lib/server/ws-hub.ts +5 -3
  261. package/src/lib/setup-defaults.ts +352 -11
  262. package/src/lib/tool-definitions.ts +3 -4
  263. package/src/lib/validation/schemas.test.ts +26 -0
  264. package/src/lib/validation/schemas.ts +62 -1
  265. package/src/lib/ws-client.ts +14 -12
  266. package/src/proxy.ts +5 -5
  267. package/src/stores/use-app-store.ts +43 -7
  268. package/src/stores/use-chat-store.ts +31 -2
  269. package/src/stores/use-chatroom-store.ts +153 -26
  270. package/src/types/index.ts +470 -44
  271. package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
  272. package/src/components/chat/new-chat-sheet.tsx +0 -253
  273. package/src/lib/server/main-session.ts +0 -17
  274. package/src/lib/server/session-run-manager.test.ts +0 -26
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { createProviderConfig, updateProviderConfig, deleteProviderConfig } from '@/lib/provider-config'
6
6
  import { api } from '@/lib/api-client'
7
+ import { fetchProviderModelDiscovery } from '@/lib/provider-model-discovery-client'
7
8
  import { BottomSheet } from '@/components/shared/bottom-sheet'
8
9
  import { toast } from 'sonner'
9
10
 
@@ -35,10 +36,10 @@ export function ProviderSheet() {
35
36
  const [testStatus, setTestStatus] = useState<'idle' | 'testing' | 'pass' | 'fail'>('idle')
36
37
  const [testMessage, setTestMessage] = useState('')
37
38
 
38
- // Ollama local models
39
- const [localModels, setLocalModels] = useState<string[]>([])
40
- const [localLoading, setLocalLoading] = useState(false)
41
- const [localError, setLocalError] = useState('')
39
+ const [liveModels, setLiveModels] = useState<string[]>([])
40
+ const [liveLoading, setLiveLoading] = useState(false)
41
+ const [liveMessage, setLiveMessage] = useState('')
42
+ const [liveCached, setLiveCached] = useState(false)
42
43
 
43
44
  // Find editing provider in custom configs OR built-in list
44
45
  const editingCustom = editingId ? providerConfigs.find((c) => c.id === editingId) : null
@@ -50,8 +51,9 @@ export function ProviderSheet() {
50
51
  if (open) {
51
52
  loadCredentials()
52
53
  setNewModel('')
53
- setLocalModels([])
54
- setLocalError('')
54
+ setLiveModels([])
55
+ setLiveMessage('')
56
+ setLiveCached(false)
55
57
  setTestStatus('idle')
56
58
  setTestMessage('')
57
59
  if (editingCustom) {
@@ -79,28 +81,7 @@ export function ProviderSheet() {
79
81
  setIsEnabled(true)
80
82
  }
81
83
  }
82
- }, [open, editingId])
83
-
84
- // Fetch local Ollama models when editing Ollama provider
85
- useEffect(() => {
86
- if (!open || editingId !== 'ollama') return
87
- setLocalLoading(true)
88
- const endpoint = baseUrl || 'http://localhost:11434'
89
- api<{ models: { name: string }[]; error?: string }>('GET', `/providers/ollama?endpoint=${encodeURIComponent(endpoint)}`)
90
- .then((res) => {
91
- if (res.error) {
92
- setLocalError(res.error)
93
- setLocalModels([])
94
- } else {
95
- setLocalModels(res.models.map((m) => m.name))
96
- }
97
- })
98
- .catch(() => {
99
- setLocalError('Failed to connect')
100
- setLocalModels([])
101
- })
102
- .finally(() => setLocalLoading(false))
103
- }, [open, editingId, baseUrl])
84
+ }, [open, editingId, credentials, editingBuiltin, editingCustom, loadCredentials])
104
85
 
105
86
  // Reset test status when connection params change
106
87
  useEffect(() => {
@@ -108,6 +89,12 @@ export function ProviderSheet() {
108
89
  setTestMessage('')
109
90
  }, [credentialId, baseUrl])
110
91
 
92
+ useEffect(() => {
93
+ setLiveModels([])
94
+ setLiveMessage('')
95
+ setLiveCached(false)
96
+ }, [editingId, credentialId, baseUrl, requiresApiKey])
97
+
111
98
  const handleTestConnection = async () => {
112
99
  setTestStatus('testing')
113
100
  setTestMessage('')
@@ -210,10 +197,43 @@ export function ProviderSheet() {
210
197
  setModels(list.join(', '))
211
198
  }
212
199
 
200
+ const handleLoadLiveModels = async (force = false) => {
201
+ if (!open) return
202
+ const providerId = editingId || 'custom'
203
+ setLiveLoading(true)
204
+ setLiveMessage('')
205
+ try {
206
+ const result = await fetchProviderModelDiscovery({
207
+ providerId,
208
+ credentialId,
209
+ endpoint: baseUrl,
210
+ force,
211
+ requiresApiKey: isBuiltin ? undefined : requiresApiKey,
212
+ })
213
+ setLiveModels(result.models)
214
+ setLiveCached(result.cached)
215
+ setLiveMessage(result.message || '')
216
+ if (!result.ok) {
217
+ toast.message(result.message || 'Live model discovery is unavailable.')
218
+ return
219
+ }
220
+ setModels(result.models.join(', '))
221
+ toast.success(`Loaded ${result.models.length} live model${result.models.length === 1 ? '' : 's'}`)
222
+ } catch (err: unknown) {
223
+ const message = err instanceof Error ? err.message : 'Failed to load live models'
224
+ setLiveMessage(message)
225
+ toast.error(message)
226
+ } finally {
227
+ setLiveLoading(false)
228
+ }
229
+ }
230
+
213
231
  const credList = Object.values(credentials)
214
232
  const modelList = models.split(',').map((m) => m.trim()).filter(Boolean)
215
- const isNew = !editing
216
233
  const showApiKey = isBuiltin ? editingBuiltin?.requiresApiKey || editingBuiltin?.optionalApiKey : requiresApiKey
234
+ const canDiscoverModels = isBuiltin
235
+ ? Boolean(editingBuiltin?.supportsModelDiscovery)
236
+ : !!baseUrl.trim()
217
237
 
218
238
  const inputClass = "w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow"
219
239
 
@@ -254,26 +274,45 @@ export function ProviderSheet() {
254
274
  <div className="mb-8">
255
275
  <div className="flex items-center justify-between mb-3">
256
276
  <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em]">Models</label>
257
- {isBuiltin && (
258
- <button onClick={handleResetModels}
259
- className="text-[11px] text-text-3 hover:text-text-2 transition-colors cursor-pointer bg-transparent border-none"
260
- style={{ fontFamily: 'inherit' }}>
261
- Reset to defaults
262
- </button>
263
- )}
277
+ <div className="flex items-center gap-3">
278
+ {canDiscoverModels && (
279
+ <button
280
+ onClick={() => { void handleLoadLiveModels(Boolean(liveModels.length)) }}
281
+ disabled={liveLoading}
282
+ className="text-[11px] text-accent-bright hover:brightness-110 transition-colors cursor-pointer bg-transparent border-none disabled:opacity-50 disabled:cursor-default"
283
+ style={{ fontFamily: 'inherit' }}
284
+ >
285
+ {liveLoading ? 'Loading live models...' : liveModels.length > 0 ? 'Refresh live list' : 'Load live models'}
286
+ </button>
287
+ )}
288
+ {isBuiltin && (
289
+ <button onClick={handleResetModels}
290
+ className="text-[11px] text-text-3 hover:text-text-2 transition-colors cursor-pointer bg-transparent border-none"
291
+ style={{ fontFamily: 'inherit' }}>
292
+ Reset to defaults
293
+ </button>
294
+ )}
295
+ </div>
264
296
  </div>
265
297
 
298
+ {(liveMessage || liveCached) && (
299
+ <p className="text-[11px] text-text-3/70 mb-3">
300
+ {liveMessage}
301
+ {liveCached ? ' Cached.' : ''}
302
+ </p>
303
+ )}
304
+
266
305
  {isBuiltin ? (
267
306
  <>
268
307
  <div className="flex flex-wrap gap-1.5 mb-3">
269
308
  {modelList.map((model, i) => {
270
- const isLocal = editingId === 'ollama' && localModels.includes(model)
309
+ const isLive = liveModels.includes(model)
271
310
  return (
272
311
  <div key={`${model}-${i}`} className={`group/model flex items-center gap-1.5 px-2.5 py-1.5 rounded-[8px] border
273
- ${isLocal ? 'bg-emerald-500/[0.08] border-emerald-500/20' : 'bg-white/[0.04] border-white/[0.06]'}`}>
312
+ ${isLive ? 'bg-emerald-500/[0.08] border-emerald-500/20' : 'bg-white/[0.04] border-white/[0.06]'}`}>
274
313
  <span className="text-[12px] text-text-2 font-mono">{model}</span>
275
- {isLocal && (
276
- <span className="text-[9px] font-600 px-1.5 py-0.5 rounded-[4px] bg-emerald-500/15 text-emerald-400 uppercase tracking-wider">local</span>
314
+ {isLive && (
315
+ <span className="text-[9px] font-600 px-1.5 py-0.5 rounded-[4px] bg-emerald-500/15 text-emerald-400 uppercase tracking-wider">live</span>
277
316
  )}
278
317
  <button
279
318
  onClick={() => handleRemoveModel(i)}
@@ -288,34 +327,6 @@ export function ProviderSheet() {
288
327
  })}
289
328
  </div>
290
329
 
291
- {/* Ollama: show available local models not yet in the list */}
292
- {editingId === 'ollama' && !localLoading && localModels.length > 0 && (() => {
293
- const missing = localModels.filter((m) => !modelList.includes(m))
294
- if (missing.length === 0) return null
295
- return (
296
- <div className="mb-3">
297
- <p className="text-[11px] text-text-3/60 mb-2">Available locally — click to add:</p>
298
- <div className="flex flex-wrap gap-1.5">
299
- {missing.map((m) => (
300
- <button key={m} onClick={() => { setModels(models ? models + ', ' + m : m) }}
301
- className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-[8px] bg-emerald-500/[0.05] border border-emerald-500/15
302
- hover:bg-emerald-500/10 transition-all cursor-pointer text-[12px] text-emerald-300/80 font-mono"
303
- style={{ fontFamily: 'inherit' }}>
304
- <span>+</span> {m}
305
- </button>
306
- ))}
307
- </div>
308
- </div>
309
- )
310
- })()}
311
-
312
- {editingId === 'ollama' && localLoading && (
313
- <p className="text-[11px] text-text-3/70 mb-3">Checking local Ollama instance...</p>
314
- )}
315
- {editingId === 'ollama' && localError && (
316
- <p className="text-[11px] text-amber-400/60 mb-3">{localError}</p>
317
- )}
318
-
319
330
  <div className="flex gap-2">
320
331
  <input
321
332
  type="text"
@@ -431,13 +442,13 @@ export function ProviderSheet() {
431
442
  onClick={async () => {
432
443
  setSavingKey(true)
433
444
  try {
434
- const cred = await api<any>('POST', '/credentials', { provider: editingId || name || 'custom', name: newKeyName.trim() || `${name || editingId || 'Custom'} key`, apiKey: newKeyValue.trim() })
445
+ const cred = await api<{ id: string }>('POST', '/credentials', { provider: editingId || name || 'custom', name: newKeyName.trim() || `${name || editingId || 'Custom'} key`, apiKey: newKeyValue.trim() })
435
446
  await loadCredentials()
436
447
  setCredentialId(cred.id)
437
448
  setAddingKey(false)
438
449
  setNewKeyName('')
439
450
  setNewKeyValue('')
440
- } catch (err: any) { toast.error(`Failed to save: ${err.message}`) }
451
+ } catch (err: unknown) { toast.error(`Failed to save: ${err instanceof Error ? err.message : String(err)}`) }
441
452
  finally { setSavingKey(false) }
442
453
  }}
443
454
  className="px-4 py-1.5 rounded-[8px] bg-accent-bright text-white text-[12px] font-600 cursor-pointer border-none hover:brightness-110 transition-all disabled:opacity-40"
@@ -12,7 +12,7 @@ interface Props {
12
12
  onSelect: (agentId: string) => void
13
13
  /** Show a "None" option at the top for optional single-select */
14
14
  noneOption?: { label: string; onSelect: () => void }
15
- /** Show orchestrator badge */
15
+ /** Show delegation-capable badge */
16
16
  showOrchBadge?: boolean
17
17
  /** Max height of the scrollable list */
18
18
  maxHeight?: number
@@ -76,7 +76,7 @@ export function AgentPickerList({
76
76
  <span className={`text-[13px] font-600 flex-1 truncate ${active ? 'text-accent-bright' : 'text-text-2'}`}>
77
77
  {a.name}
78
78
  </span>
79
- {showOrchBadge && a.isOrchestrator && (
79
+ {showOrchBadge && a.platformAssignScope === 'all' && (
80
80
  <span className="text-[10px] text-text-3/60 flex items-center gap-0.5">
81
81
  <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><path d="M16 3h5v5"/><path d="M21 3l-7 7"/><path d="M8 21H3v-5"/><path d="M3 21l7-7"/></svg>
82
82
  </span>
@@ -1,6 +1,8 @@
1
1
  'use client'
2
2
 
3
3
  import type { ReactNode } from 'react'
4
+ import { XIcon } from 'lucide-react'
5
+ import { Dialog as DialogPrimitive } from 'radix-ui'
4
6
 
5
7
  interface Props {
6
8
  open: boolean
@@ -10,21 +12,35 @@ interface Props {
10
12
  }
11
13
 
12
14
  export function BottomSheet({ open, onClose, children, wide }: Props) {
13
- if (!open) return null
14
-
15
15
  return (
16
- <div className="fixed inset-0 z-100 flex items-center justify-center p-6">
17
- <div className="absolute inset-0 bg-black/70 backdrop-blur-sm" onClick={onClose} />
18
- <div
19
- className={`relative bg-raised w-full ${wide ? 'max-w-[640px]' : 'max-w-[520px]'} max-h-[85vh] flex flex-col
20
- rounded-[24px] border border-white/[0.06]
21
- shadow-[0_24px_80px_rgba(0,0,0,0.6),0_0_1px_rgba(255,255,255,0.05)]`}
22
- style={{ animation: 'modal-in 0.3s cubic-bezier(0.16, 1, 0.3, 1)' }}
23
- >
24
- <div className="flex-1 overflow-y-auto px-8 pt-8 pb-8">
25
- {children}
26
- </div>
27
- </div>
28
- </div>
16
+ <DialogPrimitive.Root open={open} onOpenChange={(nextOpen) => { if (!nextOpen) onClose() }}>
17
+ <DialogPrimitive.Portal>
18
+ <DialogPrimitive.Overlay
19
+ className="fixed inset-0 z-100 bg-black/72 backdrop-blur-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
20
+ />
21
+ <DialogPrimitive.Content
22
+ className={`fixed inset-x-0 bottom-0 z-100 mx-auto flex max-h-[92vh] w-full flex-col bg-raised shadow-[0_24px_80px_rgba(0,0,0,0.6),0_0_1px_rgba(255,255,255,0.05)] outline-none
23
+ rounded-t-[24px] border border-white/[0.06]
24
+ data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom
25
+ sm:inset-x-auto sm:top-[50%] sm:bottom-auto sm:left-[50%] sm:w-[calc(100%-2rem)] sm:translate-x-[-50%] sm:translate-y-[-50%] sm:rounded-[24px]
26
+ sm:data-[state=closed]:zoom-out-95 sm:data-[state=open]:zoom-in-95
27
+ ${wide ? 'sm:max-w-[760px]' : 'sm:max-w-[560px]'}`}
28
+ style={{ animationDuration: '220ms' }}
29
+ >
30
+ <div className="relative shrink-0 px-4 pt-3 sm:px-5 sm:pt-5">
31
+ <div className="mx-auto h-1 w-10 rounded-full bg-white/[0.08] sm:hidden" />
32
+ <DialogPrimitive.Close
33
+ className="absolute right-3 top-2.5 inline-flex h-9 w-9 items-center justify-center rounded-[12px] border border-white/[0.06] bg-white/[0.03] text-text-3 transition-all hover:bg-white/[0.06] hover:text-text-2 focus:outline-none focus:ring-2 focus:ring-accent-bright/30 sm:right-4 sm:top-4"
34
+ >
35
+ <XIcon className="size-4" />
36
+ <span className="sr-only">Close</span>
37
+ </DialogPrimitive.Close>
38
+ </div>
39
+ <div className="flex-1 overflow-y-auto px-5 pb-[max(1.25rem,env(safe-area-inset-bottom))] pt-3 sm:px-8 sm:pb-8 sm:pt-5">
40
+ {children}
41
+ </div>
42
+ </DialogPrimitive.Content>
43
+ </DialogPrimitive.Portal>
44
+ </DialogPrimitive.Root>
29
45
  )
30
46
  }
@@ -6,8 +6,10 @@ import { useAppStore } from '@/stores/use-app-store'
6
6
  interface CommandItem {
7
7
  id: string
8
8
  label: string
9
- category: 'agent' | 'chat' | 'task' | 'nav'
10
- onSelect: () => void
9
+ description?: string
10
+ keywords?: string[]
11
+ category: 'agent' | 'chat' | 'task' | 'nav' | 'setting'
12
+ onSelect: () => void | Promise<void>
11
13
  }
12
14
 
13
15
  export function CommandPalette() {
@@ -20,13 +22,22 @@ export function CommandPalette() {
20
22
  const agents = useAppStore((s) => s.agents)
21
23
  const sessions = useAppStore((s) => s.sessions)
22
24
  const tasks = useAppStore((s) => s.tasks)
25
+ const setCurrentAgent = useAppStore((s) => s.setCurrentAgent)
23
26
  const setCurrentSession = useAppStore((s) => s.setCurrentSession)
24
27
  const setActiveView = useAppStore((s) => s.setActiveView)
25
- const setEditingAgentId = useAppStore((s) => s.setEditingAgentId)
26
- const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
27
28
  const setEditingTaskId = useAppStore((s) => s.setEditingTaskId)
28
29
  const setTaskSheetOpen = useAppStore((s) => s.setTaskSheetOpen)
29
30
 
31
+ const openSettingsSection = useCallback((tabId?: string, sectionId?: string) => {
32
+ setActiveView('settings')
33
+ setOpen(false)
34
+ window.setTimeout(() => {
35
+ window.dispatchEvent(new CustomEvent('swarmclaw:settings-focus', {
36
+ detail: { tabId, sectionId },
37
+ }))
38
+ }, 80)
39
+ }, [setActiveView])
40
+
30
41
  // Register keyboard shortcut
31
42
  useEffect(() => {
32
43
  const handler = (e: KeyboardEvent) => {
@@ -54,32 +65,89 @@ export function CommandPalette() {
54
65
  const items = useMemo<CommandItem[]>(() => {
55
66
  const result: CommandItem[] = []
56
67
 
57
- // Navigation items
58
- const views = ['agents', 'tasks', 'chatrooms', 'schedules', 'connectors', 'providers', 'secrets', 'settings', 'memory', 'skills'] as const
59
- for (const v of views) {
68
+ const views = [
69
+ { id: 'home', label: 'Home', description: 'Overview and triage', keywords: ['dashboard', 'overview', 'activity'] },
70
+ { id: 'agents', label: 'Agents', description: 'Agent chats and configuration', keywords: ['chat', 'assistant', 'default'] },
71
+ { id: 'tasks', label: 'Tasks', description: 'Task board and approvals', keywords: ['board', 'queue', 'backlog', 'approval'] },
72
+ { id: 'projects', label: 'Projects', description: 'Scoped workspaces for agents and tasks', keywords: ['workspace', 'scope'] },
73
+ { id: 'chatrooms', label: 'Chatrooms', description: 'Shared multi-agent conversations', keywords: ['group', 'room', 'mentions'] },
74
+ { id: 'schedules', label: 'Schedules', description: 'Recurring and timed automations', keywords: ['cron', 'automation', 'interval'] },
75
+ { id: 'connectors', label: 'Connectors', description: 'Bridges to Slack, Discord, Telegram, and more', keywords: ['discord', 'slack', 'telegram', 'whatsapp'] },
76
+ { id: 'memory', label: 'Memory', description: 'Stored agent memory and retrieval', keywords: ['knowledge', 'vector', 'retrieval'] },
77
+ { id: 'knowledge', label: 'Knowledge', description: 'Shared knowledge base', keywords: ['docs', 'entries', 'facts'] },
78
+ { id: 'providers', label: 'Providers', description: 'Model providers and endpoints', keywords: ['openai', 'anthropic', 'ollama', 'endpoint'] },
79
+ { id: 'secrets', label: 'Secrets', description: 'Credentials and encrypted secrets', keywords: ['api key', 'token', 'credential'] },
80
+ { id: 'settings', label: 'Settings', description: 'General app configuration', keywords: ['preferences', 'theme', 'heartbeat'] },
81
+ ] as const
82
+ for (const view of views) {
60
83
  result.push({
61
- id: `nav:${v}`,
62
- label: `Go to ${v}`,
84
+ id: `nav:${view.id}`,
85
+ label: `Go to ${view.label}`,
86
+ description: view.description,
87
+ keywords: [...view.keywords],
63
88
  category: 'nav',
64
- onSelect: () => { setActiveView(v); setOpen(false) },
89
+ onSelect: () => { setActiveView(view.id); setOpen(false) },
65
90
  })
66
91
  }
67
92
 
68
- // Agents
93
+ result.push(
94
+ {
95
+ id: 'setting:default-agent',
96
+ label: 'Default Agent Shortcut',
97
+ description: 'Choose which agent the sidebar shortcut opens',
98
+ keywords: ['main chat', 'default agent', 'shortcut'],
99
+ category: 'setting',
100
+ onSelect: () => openSettingsSection('general', 'user-preferences'),
101
+ },
102
+ {
103
+ id: 'setting:automation',
104
+ label: 'Automation Limits',
105
+ description: 'Heartbeat, autonomy, and delegation controls',
106
+ keywords: ['loops', 'coordination', 'delegation', 'heartbeat', 'automation'],
107
+ category: 'setting',
108
+ onSelect: () => openSettingsSection('agents', 'runtime-loop'),
109
+ },
110
+ {
111
+ id: 'setting:providers',
112
+ label: 'Provider Credentials',
113
+ description: 'Manage providers, endpoints, and secrets',
114
+ keywords: ['openai', 'anthropic', 'api keys', 'credentials', 'providers'],
115
+ category: 'setting',
116
+ onSelect: () => openSettingsSection('integrations', 'providers'),
117
+ },
118
+ {
119
+ id: 'setting:voice',
120
+ label: 'Voice & Search',
121
+ description: 'Voice output and web search defaults',
122
+ keywords: ['voice', 'tts', 'web search', 'search'],
123
+ category: 'setting',
124
+ onSelect: () => openSettingsSection('memory', 'voice'),
125
+ },
126
+ )
127
+
69
128
  for (const agent of Object.values(agents)) {
70
129
  result.push({
71
130
  id: `agent:${agent.id}`,
72
131
  label: agent.name,
132
+ description: `Open ${agent.name}'s chat`,
133
+ keywords: [agent.provider, agent.model, agent.description || ''].filter(Boolean),
73
134
  category: 'agent',
74
- onSelect: () => { setEditingAgentId(agent.id); setAgentSheetOpen(true); setOpen(false) },
135
+ onSelect: async () => {
136
+ await setCurrentAgent(agent.id)
137
+ setActiveView('agents')
138
+ setOpen(false)
139
+ },
75
140
  })
76
141
  }
77
142
 
78
143
  // Chats (sessions)
79
144
  for (const session of Object.values(sessions)) {
145
+ const sessionAgent = session.agentId ? agents[session.agentId] : null
80
146
  result.push({
81
147
  id: `chat:${session.id}`,
82
148
  label: session.name || 'Untitled chat',
149
+ description: sessionAgent ? `Recent chat with ${sessionAgent.name}` : 'Direct model chat',
150
+ keywords: [session.provider, session.model, sessionAgent?.name || ''].filter(Boolean),
83
151
  category: 'chat',
84
152
  onSelect: () => { setCurrentSession(session.id); setActiveView('agents'); setOpen(false) },
85
153
  })
@@ -91,19 +159,25 @@ export function CommandPalette() {
91
159
  result.push({
92
160
  id: `task:${task.id}`,
93
161
  label: task.title,
162
+ description: `${task.status.charAt(0).toUpperCase() + task.status.slice(1)} task`,
163
+ keywords: [task.status, task.agentId || ''].filter(Boolean),
94
164
  category: 'task',
95
165
  onSelect: () => { setEditingTaskId(task.id); setTaskSheetOpen(true); setOpen(false) },
96
166
  })
97
167
  }
98
168
 
99
169
  return result
100
- }, [agents, sessions, tasks, setActiveView, setCurrentSession, setEditingAgentId, setAgentSheetOpen, setEditingTaskId, setTaskSheetOpen])
170
+ }, [agents, openSettingsSection, sessions, setActiveView, setCurrentAgent, setCurrentSession, setEditingTaskId, setTaskSheetOpen, tasks])
101
171
 
102
172
  const filtered = useMemo(() => {
103
173
  if (!query.trim()) return items.slice(0, 20)
104
174
  const q = query.toLowerCase()
105
175
  return items
106
- .filter((item) => item.label.toLowerCase().includes(q))
176
+ .filter((item) =>
177
+ item.label.toLowerCase().includes(q)
178
+ || item.description?.toLowerCase().includes(q)
179
+ || item.keywords?.some((keyword) => keyword.toLowerCase().includes(q)),
180
+ )
107
181
  .slice(0, 20)
108
182
  }, [items, query])
109
183
 
@@ -132,7 +206,7 @@ export function CommandPalette() {
132
206
 
133
207
  if (!open) return null
134
208
 
135
- const categoryLabel = { agent: 'Agents', chat: 'Chats', task: 'Tasks', nav: 'Navigation' } as const
209
+ const categoryLabel = { agent: 'Agents', chat: 'Chats', task: 'Tasks', nav: 'Navigation', setting: 'Settings' } as const
136
210
  const categoryIcon = {
137
211
  agent: (
138
212
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
@@ -154,6 +228,12 @@ export function CommandPalette() {
154
228
  <circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" />
155
229
  </svg>
156
230
  ),
231
+ setting: (
232
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
233
+ <circle cx="12" cy="12" r="3" />
234
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
235
+ </svg>
236
+ ),
157
237
  }
158
238
 
159
239
  // Group by category
@@ -183,14 +263,14 @@ export function CommandPalette() {
183
263
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3 shrink-0 relative z-10">
184
264
  <circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" />
185
265
  </svg>
186
- <input
187
- ref={inputRef}
188
- value={query}
189
- onChange={(e) => setQuery(e.target.value)}
190
- onKeyDown={handleKeyDown}
191
- placeholder="Search agents, chats, tasks..."
192
- className="flex-1 bg-transparent border-none outline-none text-[14px] text-text-1 placeholder:text-text-3/50"
193
- />
266
+ <input
267
+ ref={inputRef}
268
+ value={query}
269
+ onChange={(e) => setQuery(e.target.value)}
270
+ onKeyDown={handleKeyDown}
271
+ placeholder="Search chats, agents, tasks, settings..."
272
+ className="flex-1 bg-transparent border-none outline-none text-[14px] text-text-1 placeholder:text-text-3/50"
273
+ />
194
274
  <kbd className="hidden md:inline-flex items-center px-1.5 py-0.5 rounded-[6px] bg-white/[0.06] text-[11px] text-text-3 font-500">
195
275
  esc
196
276
  </kbd>
@@ -220,7 +300,14 @@ export function CommandPalette() {
220
300
  <div className="absolute left-0 top-1 bottom-1 w-1 rounded-r-full bg-accent-bright" style={{ animation: 'spring-in 0.3s var(--ease-spring)' }} />
221
301
  )}
222
302
  <span className="shrink-0 text-text-3">{categoryIcon[item.category as keyof typeof categoryIcon]}</span>
223
- <span className="text-[13px] font-500 truncate">{item.label}</span>
303
+ <div className="min-w-0 flex-1">
304
+ <div className="text-[13px] font-500 truncate">{item.label}</div>
305
+ {item.description && (
306
+ <div className={`text-[11px] truncate ${idx === selectedIndex ? 'text-accent-bright/75' : 'text-text-3/55'}`}>
307
+ {item.description}
308
+ </div>
309
+ )}
310
+ </div>
224
311
  </button>
225
312
  )
226
313
  })}
@@ -1,5 +1,14 @@
1
1
  'use client'
2
2
 
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogFooter,
8
+ DialogHeader,
9
+ DialogTitle,
10
+ } from '@/components/ui/dialog'
11
+
3
12
  interface Props {
4
13
  open: boolean
5
14
  title: string
@@ -11,37 +20,43 @@ interface Props {
11
20
  }
12
21
 
13
22
  export function ConfirmDialog({ open, title, message, confirmLabel = 'Confirm', danger, onConfirm, onCancel }: Props) {
14
- if (!open) return null
15
-
16
23
  return (
17
- <div className="fixed inset-0 z-100 flex items-center justify-center p-6">
18
- <div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onCancel} />
19
- <div className="relative glass rounded-[20px] p-6 w-full max-w-[380px]
20
- shadow-[0_24px_80px_rgba(0,0,0,0.5)]"
21
- style={{ animation: 'fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1)' }}>
22
- <h3 className="font-display text-[18px] font-700 tracking-[-0.02em] mb-2">{title}</h3>
23
- <p className="text-[13px] text-text-2 mb-6 leading-relaxed">{message}</p>
24
- <div className="flex gap-2.5">
25
- <button
26
- onClick={onCancel}
27
- className="flex-1 py-2.5 rounded-[12px] border border-white/[0.06] bg-transparent text-text-2 text-[13px] font-600 cursor-pointer
28
- hover:bg-surface transition-all duration-200"
29
- style={{ fontFamily: 'inherit' }}
30
- >
31
- Cancel
32
- </button>
33
- <button
34
- onClick={onConfirm}
35
- className={`flex-1 py-2.5 rounded-[12px] border-none text-[13px] font-600 cursor-pointer active:scale-[0.97] transition-all duration-200
36
- ${danger
37
- ? 'bg-danger text-white shadow-[0_4px_20px_rgba(244,63,94,0.2)]'
38
- : 'bg-accent-bright text-white shadow-[0_4px_20px_rgba(99,102,241,0.2)]'}`}
39
- style={{ fontFamily: 'inherit' }}
40
- >
41
- {confirmLabel}
42
- </button>
24
+ <Dialog open={open} onOpenChange={(nextOpen) => { if (!nextOpen) onCancel() }}>
25
+ <DialogContent
26
+ className="sm:max-w-[400px] rounded-[20px] border-white/[0.06] bg-raised p-0 shadow-[0_24px_80px_rgba(0,0,0,0.55)]"
27
+ >
28
+ <div className="p-6">
29
+ <DialogHeader className="text-left">
30
+ <DialogTitle className="font-display text-[18px] font-700 tracking-[-0.02em] text-text">
31
+ {title}
32
+ </DialogTitle>
33
+ <DialogDescription className="mt-2 text-[13px] leading-relaxed text-text-2">
34
+ {message}
35
+ </DialogDescription>
36
+ </DialogHeader>
37
+ <DialogFooter className="mt-6">
38
+ <button
39
+ type="button"
40
+ onClick={onCancel}
41
+ className="flex-1 rounded-[12px] border border-white/[0.06] bg-transparent px-4 py-2.5 text-[13px] font-600 text-text-2 transition-all duration-200 hover:bg-surface"
42
+ style={{ fontFamily: 'inherit' }}
43
+ >
44
+ Cancel
45
+ </button>
46
+ <button
47
+ type="button"
48
+ onClick={onConfirm}
49
+ className={`flex-1 rounded-[12px] border-none px-4 py-2.5 text-[13px] font-600 text-white transition-all duration-200 active:scale-[0.98]
50
+ ${danger
51
+ ? 'bg-danger shadow-[0_4px_20px_rgba(244,63,94,0.2)]'
52
+ : 'bg-accent-bright shadow-[0_4px_20px_rgba(99,102,241,0.2)]'}`}
53
+ style={{ fontFamily: 'inherit' }}
54
+ >
55
+ {confirmLabel}
56
+ </button>
57
+ </DialogFooter>
43
58
  </div>
44
- </div>
45
- </div>
59
+ </DialogContent>
60
+ </Dialog>
46
61
  )
47
62
  }