@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.
- package/README.md +116 -50
- package/bin/package-manager.js +157 -0
- package/bin/package-manager.test.js +90 -0
- package/bin/server-cmd.js +38 -7
- package/bin/swarmclaw.js +54 -4
- package/bin/update-cmd.js +48 -10
- package/bin/update-cmd.test.js +55 -0
- package/package.json +8 -3
- package/scripts/postinstall.mjs +26 -0
- package/src/app/api/agents/[id]/route.ts +43 -0
- package/src/app/api/agents/[id]/thread/route.ts +39 -8
- package/src/app/api/agents/route.ts +35 -2
- package/src/app/api/auth/route.ts +77 -8
- package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
- package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
- package/src/app/api/chatrooms/[id]/route.ts +6 -0
- package/src/app/api/chats/[id]/browser/route.ts +5 -1
- package/src/app/api/chats/[id]/chat/route.ts +7 -3
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +30 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/heartbeat/route.ts +2 -1
- package/src/app/api/chats/route.ts +23 -1
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
- package/src/app/api/connectors/doctor/route.ts +13 -0
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
- package/src/app/api/external-agents/[id]/route.ts +31 -0
- package/src/app/api/external-agents/register/route.ts +3 -0
- package/src/app/api/external-agents/route.ts +66 -0
- package/src/app/api/files/open/route.ts +16 -14
- package/src/app/api/gateways/[id]/health/route.ts +28 -0
- package/src/app/api/gateways/[id]/route.ts +79 -0
- package/src/app/api/gateways/route.ts +57 -0
- package/src/app/api/memory/maintenance/route.ts +11 -1
- package/src/app/api/openclaw/agent-files/route.ts +27 -4
- package/src/app/api/openclaw/gateway/route.ts +10 -7
- package/src/app/api/openclaw/skills/route.ts +12 -4
- package/src/app/api/plugins/dependencies/route.ts +24 -0
- package/src/app/api/plugins/install/route.ts +15 -92
- package/src/app/api/plugins/route.ts +3 -26
- package/src/app/api/plugins/settings/route.ts +17 -12
- package/src/app/api/plugins/ui/route.ts +1 -0
- package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
- package/src/app/api/schedules/[id]/route.ts +38 -9
- package/src/app/api/schedules/route.ts +51 -28
- package/src/app/api/settings/route.ts +55 -17
- package/src/app/api/setup/doctor/route.ts +6 -4
- package/src/app/api/tasks/[id]/route.ts +16 -6
- package/src/app/api/tasks/bulk/route.ts +3 -3
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +135 -17
- package/src/cli/binary.test.js +142 -0
- package/src/cli/index.js +38 -11
- package/src/cli/index.test.js +195 -0
- package/src/cli/index.ts +21 -12
- package/src/cli/server-cmd.test.js +59 -0
- package/src/cli/spec.js +20 -2
- package/src/components/agents/agent-card.tsx +15 -12
- package/src/components/agents/agent-chat-list.tsx +101 -1
- package/src/components/agents/agent-list.tsx +46 -9
- package/src/components/agents/agent-sheet.tsx +456 -23
- package/src/components/agents/inspector-panel.tsx +110 -49
- package/src/components/agents/sandbox-env-panel.tsx +4 -1
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/auth/setup-wizard.tsx +970 -275
- package/src/components/chat/chat-area.tsx +70 -27
- package/src/components/chat/chat-card.tsx +6 -21
- package/src/components/chat/chat-header.tsx +263 -366
- package/src/components/chat/chat-list.tsx +62 -26
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +145 -19
- package/src/components/chatrooms/chatroom-input.tsx +96 -33
- package/src/components/chatrooms/chatroom-list.tsx +141 -72
- package/src/components/chatrooms/chatroom-message.tsx +7 -6
- package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
- package/src/components/chatrooms/chatroom-view.tsx +422 -209
- package/src/components/chatrooms/reaction-picker.tsx +38 -33
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/gateways/gateway-sheet.tsx +567 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/input/chat-input.tsx +135 -86
- package/src/components/layout/app-layout.tsx +385 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- package/src/components/memory/memory-browser.tsx +71 -6
- package/src/components/memory/memory-card.tsx +18 -0
- package/src/components/memory/memory-detail.tsx +58 -31
- package/src/components/memory/memory-sheet.tsx +32 -4
- package/src/components/plugins/plugin-list.tsx +15 -3
- package/src/components/plugins/plugin-sheet.tsx +118 -9
- package/src/components/projects/project-detail.tsx +189 -1
- package/src/components/providers/provider-list.tsx +158 -2
- package/src/components/providers/provider-sheet.tsx +81 -70
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/bottom-sheet.tsx +31 -15
- package/src/components/shared/command-palette.tsx +111 -24
- package/src/components/shared/confirm-dialog.tsx +45 -30
- package/src/components/shared/model-combobox.tsx +90 -8
- package/src/components/shared/settings/plugin-manager.tsx +20 -4
- package/src/components/shared/settings/section-capability-policy.tsx +105 -0
- package/src/components/shared/settings/section-heartbeat.tsx +88 -6
- package/src/components/shared/settings/section-orchestrator.tsx +6 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
- package/src/components/shared/settings/section-secrets.tsx +6 -6
- package/src/components/shared/settings/section-user-preferences.tsx +1 -1
- package/src/components/shared/settings/section-voice.tsx +5 -1
- package/src/components/shared/settings/section-web-search.tsx +10 -2
- package/src/components/shared/settings/settings-page.tsx +248 -47
- package/src/components/tasks/approvals-panel.tsx +211 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
- package/src/components/wallets/wallet-panel.tsx +17 -5
- package/src/components/webhooks/webhook-sheet.tsx +7 -7
- package/src/lib/auth.ts +17 -0
- package/src/lib/chat-streaming-state.test.ts +108 -0
- package/src/lib/chat-streaming-state.ts +108 -0
- package/src/lib/heartbeat-defaults.ts +48 -0
- package/src/lib/memory-presentation.ts +59 -0
- package/src/lib/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/provider-model-discovery-client.ts +29 -0
- package/src/lib/providers/index.ts +12 -5
- package/src/lib/runtime-loop.ts +105 -3
- package/src/lib/safe-storage.ts +6 -1
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/agent-runtime-config.test.ts +141 -0
- package/src/lib/server/agent-runtime-config.ts +277 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +264 -0
- package/src/lib/server/approvals.ts +483 -75
- package/src/lib/server/autonomy-runtime.test.ts +341 -0
- package/src/lib/server/browser-state.test.ts +118 -0
- package/src/lib/server/browser-state.ts +123 -0
- package/src/lib/server/build-llm.test.ts +44 -0
- package/src/lib/server/build-llm.ts +11 -4
- package/src/lib/server/builtin-plugins.ts +34 -0
- package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
- package/src/lib/server/chat-execution.ts +402 -125
- package/src/lib/server/chatroom-health.test.ts +26 -0
- package/src/lib/server/chatroom-health.ts +2 -3
- package/src/lib/server/chatroom-helpers.test.ts +74 -2
- package/src/lib/server/chatroom-helpers.ts +144 -11
- package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
- package/src/lib/server/connectors/discord.ts +175 -11
- package/src/lib/server/connectors/doctor.test.ts +80 -0
- package/src/lib/server/connectors/doctor.ts +116 -0
- package/src/lib/server/connectors/manager.ts +994 -130
- package/src/lib/server/connectors/policy.test.ts +222 -0
- package/src/lib/server/connectors/policy.ts +452 -0
- package/src/lib/server/connectors/slack.ts +189 -10
- package/src/lib/server/connectors/telegram.ts +65 -15
- package/src/lib/server/connectors/thread-context.test.ts +44 -0
- package/src/lib/server/connectors/thread-context.ts +72 -0
- package/src/lib/server/connectors/types.ts +41 -11
- package/src/lib/server/daemon-state.ts +62 -3
- package/src/lib/server/data-dir.ts +13 -0
- package/src/lib/server/delegation-jobs.test.ts +140 -0
- package/src/lib/server/delegation-jobs.ts +248 -0
- package/src/lib/server/document-utils.test.ts +47 -0
- package/src/lib/server/document-utils.ts +397 -0
- package/src/lib/server/eval/agent-regression.test.ts +47 -0
- package/src/lib/server/eval/agent-regression.ts +1742 -0
- package/src/lib/server/eval/runner.ts +11 -1
- package/src/lib/server/eval/store.ts +2 -1
- package/src/lib/server/heartbeat-service.ts +23 -43
- package/src/lib/server/heartbeat-source.test.ts +22 -0
- package/src/lib/server/heartbeat-source.ts +7 -0
- package/src/lib/server/identity-continuity.test.ts +77 -0
- package/src/lib/server/identity-continuity.ts +127 -0
- package/src/lib/server/mailbox-utils.ts +347 -0
- package/src/lib/server/main-agent-loop.ts +31 -964
- package/src/lib/server/memory-db.ts +4 -6
- package/src/lib/server/memory-tiers.ts +40 -0
- package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
- package/src/lib/server/openclaw-agent-resolver.ts +128 -0
- package/src/lib/server/openclaw-exec-config.ts +6 -5
- package/src/lib/server/openclaw-gateway.ts +123 -36
- package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
- package/src/lib/server/openclaw-skills-normalize.ts +136 -0
- package/src/lib/server/openclaw-sync.ts +3 -2
- package/src/lib/server/orchestrator-lg.ts +18 -8
- package/src/lib/server/orchestrator.ts +5 -4
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +215 -0
- package/src/lib/server/plugins.ts +832 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/provider-model-discovery.ts +481 -0
- package/src/lib/server/queue.ts +4 -21
- package/src/lib/server/runtime-settings.test.ts +119 -0
- package/src/lib/server/runtime-settings.ts +12 -92
- package/src/lib/server/schedule-normalization.ts +187 -0
- package/src/lib/server/scheduler.ts +2 -0
- package/src/lib/server/session-archive-memory.test.ts +85 -0
- package/src/lib/server/session-archive-memory.ts +230 -0
- package/src/lib/server/session-mailbox.ts +8 -18
- package/src/lib/server/session-reset-policy.test.ts +99 -0
- package/src/lib/server/session-reset-policy.ts +311 -0
- package/src/lib/server/session-run-manager.ts +33 -80
- package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
- package/src/lib/server/session-tools/calendar.ts +2 -12
- package/src/lib/server/session-tools/connector.ts +109 -8
- package/src/lib/server/session-tools/context.ts +14 -2
- package/src/lib/server/session-tools/crawl.ts +447 -0
- package/src/lib/server/session-tools/crud.ts +96 -34
- package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
- package/src/lib/server/session-tools/delegate.ts +406 -20
- package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
- package/src/lib/server/session-tools/discovery.ts +40 -12
- package/src/lib/server/session-tools/document.ts +283 -0
- package/src/lib/server/session-tools/email.ts +1 -3
- package/src/lib/server/session-tools/extract.ts +137 -0
- package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +243 -24
- package/src/lib/server/session-tools/http.ts +9 -3
- package/src/lib/server/session-tools/human-loop.ts +227 -0
- package/src/lib/server/session-tools/image-gen.ts +1 -3
- package/src/lib/server/session-tools/index.ts +87 -2
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +162 -12
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
- package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
- package/src/lib/server/session-tools/platform.ts +142 -4
- package/src/lib/server/session-tools/plugin-creator.ts +95 -25
- package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
- package/src/lib/server/session-tools/replicate.ts +1 -3
- package/src/lib/server/session-tools/sandbox.ts +51 -92
- package/src/lib/server/session-tools/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +58 -4
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
- package/src/lib/server/session-tools/shell.ts +2 -2
- package/src/lib/server/session-tools/subagent.ts +195 -27
- package/src/lib/server/session-tools/table.ts +587 -0
- package/src/lib/server/session-tools/wallet.ts +13 -10
- package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
- package/src/lib/server/session-tools/web.ts +947 -108
- package/src/lib/server/storage.ts +255 -10
- package/src/lib/server/stream-agent-chat.test.ts +61 -0
- package/src/lib/server/stream-agent-chat.ts +185 -25
- package/src/lib/server/structured-extract.test.ts +72 -0
- package/src/lib/server/structured-extract.ts +373 -0
- package/src/lib/server/task-mention.test.ts +16 -2
- package/src/lib/server/task-mention.ts +61 -11
- package/src/lib/server/tool-aliases.ts +80 -12
- package/src/lib/server/tool-capability-policy.ts +7 -1
- package/src/lib/server/tool-retry.ts +2 -0
- package/src/lib/server/watch-jobs.test.ts +173 -0
- package/src/lib/server/watch-jobs.ts +532 -0
- package/src/lib/server/ws-hub.ts +5 -3
- package/src/lib/setup-defaults.ts +352 -11
- package/src/lib/tool-definitions.ts +3 -4
- package/src/lib/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +62 -1
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +43 -7
- package/src/stores/use-chat-store.ts +31 -2
- package/src/stores/use-chatroom-store.ts +153 -26
- package/src/types/index.ts +470 -44
- package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
- package/src/components/chat/new-chat-sheet.tsx +0 -253
- package/src/lib/server/main-session.ts +0 -17
- 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
|
-
|
|
39
|
-
const [
|
|
40
|
-
const [
|
|
41
|
-
const [
|
|
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
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
|
-
${
|
|
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
|
-
{
|
|
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">
|
|
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<
|
|
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:
|
|
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
|
|
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.
|
|
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
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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:${
|
|
62
|
-
label: `Go to ${
|
|
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(
|
|
89
|
+
onSelect: () => { setActiveView(view.id); setOpen(false) },
|
|
65
90
|
})
|
|
66
91
|
}
|
|
67
92
|
|
|
68
|
-
|
|
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: () => {
|
|
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,
|
|
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) =>
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
</
|
|
45
|
-
</
|
|
59
|
+
</DialogContent>
|
|
60
|
+
</Dialog>
|
|
46
61
|
)
|
|
47
62
|
}
|