@swarmclawai/swarmclaw 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -22
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +26 -0
- package/src/app/api/agents/[id]/thread/route.ts +36 -7
- package/src/app/api/agents/route.ts +12 -1
- package/src/app/api/auth/route.ts +76 -7
- package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
- 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]/main-loop/route.ts +7 -88
- package/src/app/api/chats/[id]/messages/route.ts +19 -13
- package/src/app/api/chats/[id]/route.ts +18 -0
- package/src/app/api/chats/[id]/stop/route.ts +6 -1
- package/src/app/api/chats/route.ts +16 -0
- 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/files/open/route.ts +16 -14
- 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/skills/route.ts +11 -3
- 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/settings/route.ts +49 -7
- package/src/app/api/tasks/[id]/route.ts +15 -6
- package/src/app/api/tasks/bulk/route.ts +2 -2
- package/src/app/api/tasks/route.ts +9 -4
- package/src/app/api/webhooks/[id]/route.ts +8 -1
- package/src/app/page.tsx +9 -2
- package/src/cli/index.js +4 -0
- package/src/cli/index.ts +3 -10
- 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 +207 -16
- package/src/components/agents/inspector-panel.tsx +108 -48
- package/src/components/auth/access-key-gate.tsx +36 -97
- package/src/components/chat/chat-area.tsx +29 -13
- package/src/components/chat/chat-card.tsx +4 -20
- package/src/components/chat/chat-header.tsx +255 -353
- package/src/components/chat/chat-list.tsx +7 -9
- package/src/components/chat/checkpoint-timeline.tsx +1 -1
- package/src/components/chat/message-list.tsx +3 -1
- package/src/components/chatrooms/chatroom-view.tsx +347 -205
- package/src/components/connectors/connector-list.tsx +265 -127
- package/src/components/connectors/connector-sheet.tsx +217 -0
- package/src/components/home/home-view.tsx +128 -4
- package/src/components/layout/app-layout.tsx +383 -194
- package/src/components/layout/mobile-header.tsx +26 -8
- 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 +183 -0
- package/src/components/shared/agent-picker-list.tsx +2 -2
- package/src/components/shared/command-palette.tsx +111 -24
- 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 +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +3 -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 +245 -46
- package/src/components/tasks/approvals-panel.tsx +205 -18
- package/src/components/tasks/task-board.tsx +242 -46
- package/src/components/usage/metrics-dashboard.tsx +74 -1
- 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/openclaw-agent-id.test.ts +14 -0
- package/src/lib/openclaw-agent-id.ts +31 -0
- package/src/lib/server/agent-assignment.test.ts +112 -0
- package/src/lib/server/agent-assignment.ts +169 -0
- package/src/lib/server/approval-connector-notify.test.ts +253 -0
- package/src/lib/server/approvals-auto-approve.test.ts +205 -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 +36 -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 +134 -0
- package/src/lib/server/chat-execution.ts +250 -61
- 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 +67 -2
- package/src/lib/server/chatroom-helpers.ts +45 -5
- 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 +946 -110
- 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 +188 -9
- 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 +59 -1
- 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/heartbeat-service.ts +13 -39
- 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 +27 -967
- 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 +5 -6
- 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 +17 -6
- package/src/lib/server/orchestrator.ts +2 -2
- package/src/lib/server/playwright-proxy.mjs +27 -3
- package/src/lib/server/plugins.test.ts +207 -0
- package/src/lib/server/plugins.ts +822 -69
- package/src/lib/server/provider-health.ts +33 -3
- package/src/lib/server/queue.ts +3 -20
- 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 +105 -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 +70 -32
- 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.ts +22 -4
- 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 +93 -0
- package/src/lib/server/session-tools/file-send.test.ts +84 -1
- package/src/lib/server/session-tools/file.ts +237 -24
- 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 +56 -1
- package/src/lib/server/session-tools/mailbox.ts +276 -0
- package/src/lib/server/session-tools/memory.ts +35 -3
- package/src/lib/server/session-tools/monitor.ts +150 -7
- package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
- 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 +86 -23
- 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/schedule.ts +20 -10
- package/src/lib/server/session-tools/session-info.ts +36 -3
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
- package/src/lib/server/session-tools/subagent.ts +193 -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 +896 -100
- package/src/lib/server/storage.ts +226 -7
- package/src/lib/server/stream-agent-chat.ts +46 -21
- 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 -10
- package/src/lib/server/tool-aliases.ts +44 -7
- package/src/lib/server/tool-capability-policy.ts +6 -0
- 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/validation/schemas.test.ts +26 -0
- package/src/lib/validation/schemas.ts +7 -0
- package/src/lib/ws-client.ts +14 -12
- package/src/proxy.ts +5 -5
- package/src/stores/use-app-store.ts +0 -6
- package/src/stores/use-chat-store.ts +31 -2
- package/src/types/index.ts +287 -44
- 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,8 @@ import type { SettingsSectionProps } from './types'
|
|
|
4
4
|
|
|
5
5
|
export function WebSearchSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
|
|
6
6
|
const provider = appSettings.webSearchProvider || 'duckduckgo'
|
|
7
|
+
const hasTavilyKey = appSettings.tavilyApiKeyConfigured === true
|
|
8
|
+
const hasBraveKey = appSettings.braveApiKeyConfigured === true
|
|
7
9
|
|
|
8
10
|
return (
|
|
9
11
|
<div className="mb-10">
|
|
@@ -52,10 +54,13 @@ export function WebSearchSection({ appSettings, patchSettings, inputClass }: Set
|
|
|
52
54
|
type="password"
|
|
53
55
|
value={appSettings.tavilyApiKey || ''}
|
|
54
56
|
onChange={(e) => patchSettings({ tavilyApiKey: e.target.value || null })}
|
|
55
|
-
placeholder=
|
|
57
|
+
placeholder={hasTavilyKey ? 'Stored securely. Enter a new key to replace it.' : 'tvly-...'}
|
|
56
58
|
className={inputClass}
|
|
57
59
|
style={{ fontFamily: 'inherit' }}
|
|
58
60
|
/>
|
|
61
|
+
{hasTavilyKey && (
|
|
62
|
+
<p className="text-[11px] text-emerald-400/90 mt-1.5">Stored securely. Clear the field and save to remove it.</p>
|
|
63
|
+
)}
|
|
59
64
|
<p className="text-[11px] text-text-3/60 mt-2">Get your API key from <a href="https://tavily.com" target="_blank" rel="noopener noreferrer" className="text-accent-bright hover:underline">tavily.com</a></p>
|
|
60
65
|
</div>
|
|
61
66
|
)}
|
|
@@ -67,10 +72,13 @@ export function WebSearchSection({ appSettings, patchSettings, inputClass }: Set
|
|
|
67
72
|
type="password"
|
|
68
73
|
value={appSettings.braveApiKey || ''}
|
|
69
74
|
onChange={(e) => patchSettings({ braveApiKey: e.target.value || null })}
|
|
70
|
-
placeholder=
|
|
75
|
+
placeholder={hasBraveKey ? 'Stored securely. Enter a new key to replace it.' : 'BSA...'}
|
|
71
76
|
className={inputClass}
|
|
72
77
|
style={{ fontFamily: 'inherit' }}
|
|
73
78
|
/>
|
|
79
|
+
{hasBraveKey && (
|
|
80
|
+
<p className="text-[11px] text-emerald-400/90 mt-1.5">Stored securely. Clear the field and save to remove it.</p>
|
|
81
|
+
)}
|
|
74
82
|
<p className="text-[11px] text-text-3/60 mt-2">Get your API key from <a href="https://brave.com/search/api/" target="_blank" rel="noopener noreferrer" className="text-accent-bright hover:underline">brave.com/search/api</a></p>
|
|
75
83
|
</div>
|
|
76
84
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState, useRef, useCallback } from 'react'
|
|
3
|
+
import { useEffect, useState, useRef, useCallback, useMemo } from 'react'
|
|
4
|
+
import type { ReactNode } from 'react'
|
|
4
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
6
|
import { inputClass } from './utils'
|
|
6
7
|
import { UserPreferencesSection } from './section-user-preferences'
|
|
@@ -20,15 +21,29 @@ import { ProvidersSection } from './section-providers'
|
|
|
20
21
|
interface Tab {
|
|
21
22
|
id: string
|
|
22
23
|
label: string
|
|
23
|
-
icon:
|
|
24
|
+
icon: ReactNode
|
|
24
25
|
keywords: string[]
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
interface SettingsSectionDef {
|
|
29
|
+
id: string
|
|
30
|
+
tabId: string
|
|
31
|
+
title: string
|
|
32
|
+
description: string
|
|
33
|
+
keywords: string[]
|
|
34
|
+
render: () => ReactNode
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface SettingsFocusDetail {
|
|
38
|
+
tabId?: string
|
|
39
|
+
sectionId?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
const TABS: Tab[] = [
|
|
28
43
|
{
|
|
29
44
|
id: 'general',
|
|
30
45
|
label: 'General',
|
|
31
|
-
keywords: ['preferences', 'user', 'language', 'default', 'capability', 'policy', 'permissions', 'tools', 'storage', 'uploads', 'disk', 'files', 'cleanup'],
|
|
46
|
+
keywords: ['preferences', 'user', 'language', 'default', 'default agent', 'shortcut', 'capability', 'policy', 'permissions', 'tools', 'storage', 'uploads', 'disk', 'files', 'cleanup'],
|
|
32
47
|
icon: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><circle cx="12" cy="12" r="3" /><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" /></svg>,
|
|
33
48
|
},
|
|
34
49
|
{
|
|
@@ -39,8 +54,8 @@ const TABS: Tab[] = [
|
|
|
39
54
|
},
|
|
40
55
|
{
|
|
41
56
|
id: 'agents',
|
|
42
|
-
label: 'Agents &
|
|
43
|
-
keywords: ['orchestrator', 'runtime', 'loop', 'heartbeat', 'delegation', 'agent', 'swarm', 'turns'],
|
|
57
|
+
label: 'Agents & Automation',
|
|
58
|
+
keywords: ['orchestrator', 'runtime', 'loop', 'automation', 'heartbeat', 'delegation', 'agent', 'swarm', 'turns'],
|
|
44
59
|
icon: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>,
|
|
45
60
|
},
|
|
46
61
|
{
|
|
@@ -74,6 +89,8 @@ export function SettingsPage() {
|
|
|
74
89
|
return tab && validTabIds.includes(tab) ? tab : 'general'
|
|
75
90
|
})
|
|
76
91
|
const contentRef = useRef<HTMLDivElement>(null)
|
|
92
|
+
const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({})
|
|
93
|
+
const [pendingSectionId, setPendingSectionId] = useState<string | null>(null)
|
|
77
94
|
|
|
78
95
|
const setActiveTab = useCallback((tab: string) => {
|
|
79
96
|
setActiveTabRaw(tab)
|
|
@@ -101,12 +118,145 @@ export function SettingsPage() {
|
|
|
101
118
|
const credList = Object.values(credentials)
|
|
102
119
|
const patchSettings = updateSettings
|
|
103
120
|
const sectionProps = { appSettings, patchSettings, inputClass }
|
|
121
|
+
const sections = useMemo<SettingsSectionDef[]>(() => [
|
|
122
|
+
{
|
|
123
|
+
id: 'user-preferences',
|
|
124
|
+
tabId: 'general',
|
|
125
|
+
title: 'Profile & Default Chat',
|
|
126
|
+
description: 'User identity, language, and which agent your sidebar shortcut opens.',
|
|
127
|
+
keywords: ['profile', 'default chat', 'default agent', 'shortcut', 'user', 'language', 'main chat'],
|
|
128
|
+
render: () => <UserPreferencesSection {...sectionProps} />,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'capability-policy',
|
|
132
|
+
tabId: 'general',
|
|
133
|
+
title: 'Capabilities & Permissions',
|
|
134
|
+
description: 'Global controls for tool use, permissions, and execution policy.',
|
|
135
|
+
keywords: ['tools', 'permissions', 'capability', 'policy', 'security', 'approvals'],
|
|
136
|
+
render: () => <CapabilityPolicySection {...sectionProps} />,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'storage',
|
|
140
|
+
tabId: 'general',
|
|
141
|
+
title: 'Storage & Uploads',
|
|
142
|
+
description: 'Manage upload retention, cleanup, and file storage behavior.',
|
|
143
|
+
keywords: ['storage', 'uploads', 'disk', 'cleanup', 'files'],
|
|
144
|
+
render: () => <StorageSection {...sectionProps} />,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
id: 'theme',
|
|
148
|
+
tabId: 'appearance',
|
|
149
|
+
title: 'Theme',
|
|
150
|
+
description: 'Adjust theme hue and interface styling.',
|
|
151
|
+
keywords: ['theme', 'appearance', 'color', 'hue'],
|
|
152
|
+
render: () => <ThemeSection {...sectionProps} />,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'coordination-engine',
|
|
156
|
+
tabId: 'agents',
|
|
157
|
+
title: 'Coordination Engine',
|
|
158
|
+
description: 'Choose the model settings used for delegation-heavy agent work.',
|
|
159
|
+
keywords: ['coordination', 'delegation', 'engine', 'orchestrator'],
|
|
160
|
+
render: () => <OrchestratorSection {...sectionProps} />,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'runtime-loop',
|
|
164
|
+
tabId: 'agents',
|
|
165
|
+
title: 'Automation Limits',
|
|
166
|
+
description: 'Control how far agents can run, recurse, and delegate on their own.',
|
|
167
|
+
keywords: ['automation', 'loop', 'runtime', 'turns', 'autonomy', 'heartbeat'],
|
|
168
|
+
render: () => <RuntimeLoopSection {...sectionProps} />,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 'heartbeat',
|
|
172
|
+
tabId: 'agents',
|
|
173
|
+
title: 'Heartbeat',
|
|
174
|
+
description: 'Configure automatic follow-up checks for active agent chats.',
|
|
175
|
+
keywords: ['heartbeat', 'follow up', 'interval', 'ongoing'],
|
|
176
|
+
render: () => <HeartbeatSection {...sectionProps} />,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'embedding',
|
|
180
|
+
tabId: 'memory',
|
|
181
|
+
title: 'Embeddings',
|
|
182
|
+
description: 'Configure providers for embeddings and vector-backed features.',
|
|
183
|
+
keywords: ['embedding', 'vector', 'provider', 'semantic'],
|
|
184
|
+
render: () => <EmbeddingSection {...sectionProps} credList={credList} />,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'memory',
|
|
188
|
+
tabId: 'memory',
|
|
189
|
+
title: 'Memory Governance',
|
|
190
|
+
description: 'Tune how memory is stored, consolidated, and retrieved.',
|
|
191
|
+
keywords: ['memory', 'consolidation', 'retention', 'governance'],
|
|
192
|
+
render: () => <MemorySection {...sectionProps} />,
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: 'voice',
|
|
196
|
+
tabId: 'memory',
|
|
197
|
+
title: 'Voice',
|
|
198
|
+
description: 'Control speech output and voice provider settings.',
|
|
199
|
+
keywords: ['voice', 'speech', 'tts', 'audio'],
|
|
200
|
+
render: () => <VoiceSection {...sectionProps} />,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
id: 'web-search',
|
|
204
|
+
tabId: 'memory',
|
|
205
|
+
title: 'Web Search',
|
|
206
|
+
description: 'Set defaults for search providers and browsing behavior.',
|
|
207
|
+
keywords: ['web search', 'browse', 'internet', 'search'],
|
|
208
|
+
render: () => <WebSearchSection {...sectionProps} />,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 'providers',
|
|
212
|
+
tabId: 'integrations',
|
|
213
|
+
title: 'Providers',
|
|
214
|
+
description: 'Manage model providers, endpoints, and credentials.',
|
|
215
|
+
keywords: ['providers', 'endpoints', 'openai', 'anthropic', 'ollama', 'models'],
|
|
216
|
+
render: () => <ProvidersSection {...sectionProps} />,
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
id: 'secrets',
|
|
220
|
+
tabId: 'integrations',
|
|
221
|
+
title: 'Secrets',
|
|
222
|
+
description: 'Store encrypted credentials for agents and integrations.',
|
|
223
|
+
keywords: ['secrets', 'credentials', 'api keys', 'tokens'],
|
|
224
|
+
render: () => <SecretsSection {...sectionProps} />,
|
|
225
|
+
},
|
|
226
|
+
], [credList, sectionProps])
|
|
227
|
+
const sectionsByTab = useMemo(() => {
|
|
228
|
+
const map = new Map<string, SettingsSectionDef[]>()
|
|
229
|
+
for (const section of sections) {
|
|
230
|
+
const group = map.get(section.tabId) || []
|
|
231
|
+
group.push(section)
|
|
232
|
+
map.set(section.tabId, group)
|
|
233
|
+
}
|
|
234
|
+
return map
|
|
235
|
+
}, [sections])
|
|
236
|
+
const setSectionRef = useCallback((id: string, node: HTMLDivElement | null) => {
|
|
237
|
+
sectionRefs.current[id] = node
|
|
238
|
+
}, [])
|
|
239
|
+
const focusSection = useCallback((sectionId: string, tabId?: string) => {
|
|
240
|
+
if (tabId && tabId !== activeTab) {
|
|
241
|
+
setPendingSectionId(sectionId)
|
|
242
|
+
setActiveTab(tabId)
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
sectionRefs.current[sectionId]?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
246
|
+
}, [activeTab, setActiveTab])
|
|
247
|
+
|
|
248
|
+
const matchingSections = useMemo(() => {
|
|
249
|
+
if (!searchQuery.trim()) return sections
|
|
250
|
+
const q = searchQuery.toLowerCase()
|
|
251
|
+
return sections.filter((section) =>
|
|
252
|
+
section.title.toLowerCase().includes(q)
|
|
253
|
+
|| section.description.toLowerCase().includes(q)
|
|
254
|
+
|| section.keywords.some((keyword) => keyword.toLowerCase().includes(q)),
|
|
255
|
+
)
|
|
256
|
+
}, [searchQuery, sections])
|
|
104
257
|
|
|
105
258
|
const matchingTabIds = searchQuery
|
|
106
|
-
? new Set(
|
|
107
|
-
const q = searchQuery.toLowerCase()
|
|
108
|
-
return t.label.toLowerCase().includes(q) || t.keywords.some((k) => k.includes(q))
|
|
109
|
-
}).map((t) => t.id))
|
|
259
|
+
? new Set(matchingSections.map((section) => section.tabId))
|
|
110
260
|
: null
|
|
111
261
|
|
|
112
262
|
// Auto-switch to first matching tab when searching
|
|
@@ -118,6 +268,31 @@ export function SettingsPage() {
|
|
|
118
268
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
269
|
}, [searchQuery])
|
|
120
270
|
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
if (!pendingSectionId) return
|
|
273
|
+
const frame = window.requestAnimationFrame(() => {
|
|
274
|
+
sectionRefs.current[pendingSectionId]?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
275
|
+
setPendingSectionId(null)
|
|
276
|
+
})
|
|
277
|
+
return () => window.cancelAnimationFrame(frame)
|
|
278
|
+
}, [activeTab, pendingSectionId])
|
|
279
|
+
|
|
280
|
+
useEffect(() => {
|
|
281
|
+
const handleFocus = (event: Event) => {
|
|
282
|
+
const detail = (event as CustomEvent<SettingsFocusDetail>).detail
|
|
283
|
+
if (!detail) return
|
|
284
|
+
if (detail.sectionId) {
|
|
285
|
+
focusSection(detail.sectionId, detail.tabId)
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
if (detail.tabId) setActiveTab(detail.tabId)
|
|
289
|
+
}
|
|
290
|
+
window.addEventListener('swarmclaw:settings-focus', handleFocus as EventListener)
|
|
291
|
+
return () => window.removeEventListener('swarmclaw:settings-focus', handleFocus as EventListener)
|
|
292
|
+
}, [focusSection, setActiveTab])
|
|
293
|
+
|
|
294
|
+
const visibleSections = sectionsByTab.get(activeTab) || []
|
|
295
|
+
|
|
121
296
|
return (
|
|
122
297
|
<div className="flex-1 flex h-full min-w-0">
|
|
123
298
|
{/* Tab sidebar */}
|
|
@@ -133,7 +308,7 @@ export function SettingsPage() {
|
|
|
133
308
|
type="text"
|
|
134
309
|
value={searchQuery}
|
|
135
310
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
136
|
-
placeholder="Search settings..."
|
|
311
|
+
placeholder="Search settings or jump to a section..."
|
|
137
312
|
className="w-full pl-8 pr-2 py-1.5 text-[12px] bg-white/[0.04] rounded-[8px] border border-white/[0.06] text-text placeholder:text-text-3/40 outline-none focus:border-white/[0.12] transition-colors"
|
|
138
313
|
style={{ fontFamily: 'inherit' }}
|
|
139
314
|
/>
|
|
@@ -168,49 +343,73 @@ export function SettingsPage() {
|
|
|
168
343
|
{TABS.find((t) => t.id === activeTab)?.label}
|
|
169
344
|
</h3>
|
|
170
345
|
<p className="text-[13px] text-text-3 mt-1">
|
|
171
|
-
{activeTab === 'general' && 'User preferences and global
|
|
346
|
+
{activeTab === 'general' && 'User preferences, default-chat behavior, and global controls.'}
|
|
172
347
|
{activeTab === 'appearance' && 'Customize the look and feel of the interface.'}
|
|
173
|
-
{activeTab === 'agents' && '
|
|
348
|
+
{activeTab === 'agents' && 'Agent coordination, autonomy, delegation, and heartbeat.'}
|
|
174
349
|
{activeTab === 'memory' && 'Embedding, memory governance, voice and web search.'}
|
|
175
|
-
{activeTab === 'integrations' && 'Providers,
|
|
350
|
+
{activeTab === 'integrations' && 'Providers, endpoints, and encrypted credentials.'}
|
|
176
351
|
</p>
|
|
177
352
|
</div>
|
|
178
353
|
|
|
179
|
-
{
|
|
180
|
-
|
|
181
|
-
<
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
354
|
+
{searchQuery && (
|
|
355
|
+
<div className="mb-8 rounded-[16px] border border-white/[0.06] bg-white/[0.02] p-4">
|
|
356
|
+
<div className="flex items-center justify-between gap-3 mb-3">
|
|
357
|
+
<div>
|
|
358
|
+
<p className="text-[12px] font-600 text-text-2">
|
|
359
|
+
{matchingSections.length > 0 ? `${matchingSections.length} matching section${matchingSections.length === 1 ? '' : 's'}` : 'No direct section matches'}
|
|
360
|
+
</p>
|
|
361
|
+
<p className="text-[11px] text-text-3/60">
|
|
362
|
+
Search now lands on individual settings sections instead of only tab names.
|
|
363
|
+
</p>
|
|
364
|
+
</div>
|
|
365
|
+
{searchQuery && (
|
|
366
|
+
<button
|
|
367
|
+
onClick={() => setSearchQuery('')}
|
|
368
|
+
className="px-2.5 py-1.5 rounded-[8px] bg-white/[0.04] text-[11px] text-text-3 hover:text-text hover:bg-white/[0.08] transition-colors border-none cursor-pointer"
|
|
369
|
+
style={{ fontFamily: 'inherit' }}
|
|
370
|
+
>
|
|
371
|
+
Clear
|
|
372
|
+
</button>
|
|
373
|
+
)}
|
|
374
|
+
</div>
|
|
375
|
+
{matchingSections.length > 0 && (
|
|
376
|
+
<div className="flex flex-wrap gap-2">
|
|
377
|
+
{matchingSections.slice(0, 8).map((section) => (
|
|
378
|
+
<button
|
|
379
|
+
key={section.id}
|
|
380
|
+
onClick={() => focusSection(section.id, section.tabId)}
|
|
381
|
+
className="px-3 py-2 rounded-[10px] border border-white/[0.06] bg-transparent text-left hover:bg-white/[0.04] transition-colors cursor-pointer"
|
|
382
|
+
style={{ fontFamily: 'inherit' }}
|
|
383
|
+
>
|
|
384
|
+
<div className="text-[12px] font-600 text-text">{section.title}</div>
|
|
385
|
+
<div className="text-[10px] text-text-3/60">{TABS.find((tab) => tab.id === section.tabId)?.label}</div>
|
|
386
|
+
</button>
|
|
387
|
+
))}
|
|
388
|
+
</div>
|
|
389
|
+
)}
|
|
390
|
+
</div>
|
|
189
391
|
)}
|
|
190
392
|
|
|
191
|
-
{
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
<SecretsSection {...sectionProps} />
|
|
212
|
-
</>
|
|
213
|
-
)}
|
|
393
|
+
{visibleSections.map((section) => (
|
|
394
|
+
<div
|
|
395
|
+
key={section.id}
|
|
396
|
+
ref={(node) => setSectionRef(section.id, node)}
|
|
397
|
+
className="mb-10 scroll-mt-6 last:mb-0"
|
|
398
|
+
>
|
|
399
|
+
<div className="mb-4">
|
|
400
|
+
<div className="text-[11px] uppercase tracking-[0.08em] text-text-3/45 mb-1">
|
|
401
|
+
{TABS.find((tab) => tab.id === section.tabId)?.label}
|
|
402
|
+
</div>
|
|
403
|
+
<h4 className="font-display text-[18px] font-700 tracking-[-0.02em] text-text">
|
|
404
|
+
{section.title}
|
|
405
|
+
</h4>
|
|
406
|
+
<p className="text-[12px] text-text-3 mt-1">
|
|
407
|
+
{section.description}
|
|
408
|
+
</p>
|
|
409
|
+
</div>
|
|
410
|
+
{section.render()}
|
|
411
|
+
</div>
|
|
412
|
+
))}
|
|
214
413
|
</div>
|
|
215
414
|
</div>
|
|
216
415
|
</div>
|