@swarmclawai/swarmclaw 0.5.3 → 0.6.0
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 +39 -8
- package/bin/swarmclaw.js +76 -16
- package/next.config.ts +11 -1
- package/package.json +4 -2
- package/scripts/postinstall.mjs +18 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +410 -0
- package/src/app/api/chatrooms/[id]/members/route.ts +82 -0
- package/src/app/api/chatrooms/[id]/pins/route.ts +39 -0
- package/src/app/api/chatrooms/[id]/reactions/route.ts +42 -0
- package/src/app/api/chatrooms/[id]/route.ts +84 -0
- package/src/app/api/chatrooms/route.ts +50 -0
- package/src/app/api/credentials/route.ts +2 -3
- package/src/app/api/knowledge/[id]/route.ts +13 -2
- package/src/app/api/knowledge/route.ts +8 -1
- package/src/app/api/memory/route.ts +8 -0
- package/src/app/api/notifications/route.ts +4 -0
- package/src/app/api/orchestrator/run/route.ts +1 -1
- package/src/app/api/plugins/install/route.ts +2 -2
- package/src/app/api/search/route.ts +51 -1
- package/src/app/api/sessions/[id]/chat/route.ts +2 -0
- package/src/app/api/sessions/[id]/edit-resend/route.ts +1 -1
- package/src/app/api/sessions/[id]/fork/route.ts +1 -1
- package/src/app/api/sessions/route.ts +3 -3
- package/src/app/api/settings/route.ts +9 -0
- package/src/app/api/setup/check-provider/route.ts +3 -16
- package/src/app/api/skills/[id]/route.ts +6 -0
- package/src/app/api/skills/route.ts +6 -0
- package/src/app/api/tasks/[id]/route.ts +12 -0
- package/src/app/api/tasks/bulk/route.ts +100 -0
- package/src/app/api/tasks/route.ts +1 -0
- package/src/app/api/webhooks/[id]/route.ts +15 -1
- package/src/app/globals.css +58 -15
- package/src/app/page.tsx +142 -13
- package/src/cli/index.js +24 -0
- package/src/cli/index.test.js +30 -0
- package/src/cli/spec.js +16 -0
- package/src/components/agents/agent-avatar.tsx +57 -10
- package/src/components/agents/agent-card.tsx +48 -15
- package/src/components/agents/agent-chat-list.tsx +123 -10
- package/src/components/agents/agent-list.tsx +50 -19
- package/src/components/agents/agent-sheet.tsx +56 -63
- package/src/components/auth/access-key-gate.tsx +10 -3
- package/src/components/auth/setup-wizard.tsx +2 -2
- package/src/components/auth/user-picker.tsx +31 -3
- package/src/components/chat/activity-moment.tsx +169 -0
- package/src/components/chat/chat-header.tsx +2 -0
- package/src/components/chat/chat-tool-toggles.tsx +1 -1
- package/src/components/chat/file-path-chip.tsx +125 -0
- package/src/components/chat/markdown-utils.ts +9 -0
- package/src/components/chat/message-bubble.tsx +46 -295
- package/src/components/chat/message-list.tsx +50 -1
- package/src/components/chat/streaming-bubble.tsx +36 -46
- package/src/components/chat/suggestions-bar.tsx +1 -1
- package/src/components/chat/thinking-indicator.tsx +72 -10
- package/src/components/chat/tool-call-bubble.tsx +66 -70
- package/src/components/chat/tool-request-banner.tsx +31 -7
- package/src/components/chat/transfer-agent-picker.tsx +63 -0
- package/src/components/chatrooms/agent-hover-card.tsx +124 -0
- package/src/components/chatrooms/chatroom-input.tsx +320 -0
- package/src/components/chatrooms/chatroom-list.tsx +123 -0
- package/src/components/chatrooms/chatroom-message.tsx +427 -0
- package/src/components/chatrooms/chatroom-sheet.tsx +215 -0
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +134 -0
- package/src/components/chatrooms/chatroom-typing-bar.tsx +88 -0
- package/src/components/chatrooms/chatroom-view.tsx +344 -0
- package/src/components/chatrooms/reaction-picker.tsx +273 -0
- package/src/components/connectors/connector-sheet.tsx +34 -47
- package/src/components/home/home-view.tsx +501 -0
- package/src/components/input/chat-input.tsx +79 -41
- package/src/components/knowledge/knowledge-list.tsx +31 -1
- package/src/components/knowledge/knowledge-sheet.tsx +83 -2
- package/src/components/layout/app-layout.tsx +175 -95
- package/src/components/layout/update-banner.tsx +2 -2
- package/src/components/logs/log-list.tsx +2 -2
- package/src/components/mcp-servers/mcp-server-sheet.tsx +1 -1
- package/src/components/memory/memory-agent-list.tsx +143 -0
- package/src/components/memory/memory-browser.tsx +205 -0
- package/src/components/memory/memory-card.tsx +34 -7
- package/src/components/memory/memory-detail.tsx +359 -120
- package/src/components/memory/memory-sheet.tsx +157 -23
- package/src/components/plugins/plugin-list.tsx +1 -1
- package/src/components/plugins/plugin-sheet.tsx +1 -1
- package/src/components/projects/project-detail.tsx +509 -0
- package/src/components/projects/project-list.tsx +195 -59
- package/src/components/providers/provider-list.tsx +2 -2
- package/src/components/providers/provider-sheet.tsx +3 -3
- package/src/components/schedules/schedule-card.tsx +1 -1
- package/src/components/schedules/schedule-list.tsx +1 -1
- package/src/components/schedules/schedule-sheet.tsx +25 -25
- package/src/components/secrets/secret-sheet.tsx +47 -24
- package/src/components/secrets/secrets-list.tsx +18 -8
- package/src/components/sessions/new-session-sheet.tsx +33 -65
- package/src/components/sessions/session-card.tsx +45 -14
- package/src/components/sessions/session-list.tsx +35 -18
- package/src/components/shared/agent-picker-list.tsx +90 -0
- package/src/components/shared/agent-switch-dialog.tsx +156 -0
- package/src/components/shared/attachment-chip.tsx +165 -0
- package/src/components/shared/avatar.tsx +10 -1
- package/src/components/shared/check-icon.tsx +12 -0
- package/src/components/shared/confirm-dialog.tsx +1 -1
- package/src/components/shared/empty-state.tsx +32 -0
- package/src/components/shared/file-preview.tsx +34 -0
- package/src/components/shared/form-styles.ts +2 -0
- package/src/components/shared/keyboard-shortcuts-dialog.tsx +116 -0
- package/src/components/shared/notification-center.tsx +44 -6
- package/src/components/shared/profile-sheet.tsx +115 -0
- package/src/components/shared/reply-quote.tsx +26 -0
- package/src/components/shared/search-dialog.tsx +14 -5
- package/src/components/shared/section-label.tsx +12 -0
- package/src/components/shared/settings/plugin-manager.tsx +1 -1
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/section-secrets.tsx +1 -1
- package/src/components/shared/settings/section-theme.tsx +95 -0
- package/src/components/shared/settings/section-user-preferences.tsx +39 -0
- package/src/components/shared/settings/settings-page.tsx +180 -27
- package/src/components/shared/settings/settings-sheet.tsx +9 -73
- package/src/components/shared/sheet-footer.tsx +33 -0
- package/src/components/skills/skill-list.tsx +61 -30
- package/src/components/skills/skill-sheet.tsx +81 -2
- package/src/components/tasks/task-board.tsx +448 -26
- package/src/components/tasks/task-card.tsx +46 -9
- package/src/components/tasks/task-column.tsx +62 -3
- package/src/components/tasks/task-list.tsx +12 -4
- package/src/components/tasks/task-sheet.tsx +89 -72
- package/src/components/ui/hover-card.tsx +52 -0
- package/src/components/usage/usage-list.tsx +1 -1
- package/src/components/webhooks/webhook-sheet.tsx +1 -1
- package/src/hooks/use-view-router.ts +69 -19
- package/src/instrumentation.ts +15 -1
- package/src/lib/chat.ts +2 -0
- package/src/lib/memory.ts +3 -0
- package/src/lib/server/chat-execution.ts +24 -4
- package/src/lib/server/connectors/manager.ts +11 -0
- package/src/lib/server/context-manager.ts +225 -13
- package/src/lib/server/create-notification.ts +14 -2
- package/src/lib/server/daemon-state.ts +157 -10
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service.ts +40 -5
- package/src/lib/server/heartbeat-wake.ts +110 -0
- package/src/lib/server/langgraph-checkpoint.ts +1 -0
- package/src/lib/server/memory-consolidation.ts +92 -0
- package/src/lib/server/memory-db.ts +51 -6
- package/src/lib/server/openclaw-gateway.ts +9 -1
- package/src/lib/server/provider-health.ts +125 -0
- package/src/lib/server/queue.ts +5 -4
- package/src/lib/server/scheduler.ts +8 -0
- package/src/lib/server/session-run-manager.ts +4 -0
- package/src/lib/server/session-tools/chatroom.ts +136 -0
- package/src/lib/server/session-tools/context-mgmt.ts +36 -18
- package/src/lib/server/session-tools/index.ts +2 -0
- package/src/lib/server/session-tools/memory.ts +6 -1
- package/src/lib/server/storage.ts +53 -29
- package/src/lib/server/stream-agent-chat.ts +153 -47
- package/src/lib/server/system-events.ts +49 -0
- package/src/lib/server/ws-hub.ts +11 -0
- package/src/lib/soul-suggestions.ts +109 -0
- package/src/lib/tasks.ts +4 -1
- package/src/lib/view-routes.ts +36 -1
- package/src/lib/ws-client.ts +14 -4
- package/src/stores/use-app-store.ts +36 -2
- package/src/stores/use-chat-store.ts +48 -3
- package/src/stores/use-chatroom-store.ts +276 -0
- package/src/types/index.ts +56 -2
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
6
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
7
|
+
import { api } from '@/lib/api-client'
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
open: boolean
|
|
11
|
+
onClose: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ProfileSheet({ open, onClose }: Props) {
|
|
15
|
+
const appSettings = useAppStore((s) => s.appSettings)
|
|
16
|
+
const loadSettings = useAppStore((s) => s.loadSettings)
|
|
17
|
+
const setUser = useAppStore((s) => s.setUser)
|
|
18
|
+
const currentUser = useAppStore((s) => s.currentUser)
|
|
19
|
+
|
|
20
|
+
const [name, setName] = useState('')
|
|
21
|
+
const [avatarSeed, setAvatarSeed] = useState('')
|
|
22
|
+
const [saving, setSaving] = useState(false)
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (open) {
|
|
26
|
+
setName(appSettings.userName || currentUser || '')
|
|
27
|
+
setAvatarSeed(appSettings.userAvatarSeed || '')
|
|
28
|
+
}
|
|
29
|
+
}, [open, appSettings.userName, appSettings.userAvatarSeed, currentUser])
|
|
30
|
+
|
|
31
|
+
const handleSave = async () => {
|
|
32
|
+
const trimmed = name.trim()
|
|
33
|
+
if (!trimmed || saving) return
|
|
34
|
+
setSaving(true)
|
|
35
|
+
try {
|
|
36
|
+
await api('PUT', '/settings', {
|
|
37
|
+
userName: trimmed.toLowerCase(),
|
|
38
|
+
userAvatarSeed: avatarSeed.trim() || undefined,
|
|
39
|
+
})
|
|
40
|
+
setUser(trimmed.toLowerCase())
|
|
41
|
+
await loadSettings()
|
|
42
|
+
onClose()
|
|
43
|
+
} finally {
|
|
44
|
+
setSaving(false)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const handleSignOut = () => {
|
|
49
|
+
setUser(null)
|
|
50
|
+
onClose()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<BottomSheet open={open} onClose={onClose}>
|
|
55
|
+
<div className="p-6 max-w-[400px] mx-auto">
|
|
56
|
+
<h2 className="font-display text-[18px] font-700 text-text mb-6 text-center">Profile</h2>
|
|
57
|
+
|
|
58
|
+
{/* Avatar preview */}
|
|
59
|
+
<div className="flex justify-center mb-6">
|
|
60
|
+
<AgentAvatar seed={avatarSeed || null} name={name || '?'} size={72} />
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
{/* Avatar seed */}
|
|
64
|
+
<div className="mb-4">
|
|
65
|
+
<label className="block text-[12px] font-600 text-text-2 mb-1.5">Avatar</label>
|
|
66
|
+
<div className="flex items-center gap-2">
|
|
67
|
+
<input
|
|
68
|
+
type="text"
|
|
69
|
+
value={avatarSeed}
|
|
70
|
+
onChange={(e) => setAvatarSeed(e.target.value)}
|
|
71
|
+
placeholder="Avatar seed (any text)"
|
|
72
|
+
className="flex-1 px-3 py-2 rounded-[8px] bg-white/[0.06] border border-white/[0.08] text-[13px] text-text placeholder:text-text-3 focus:outline-none focus:border-accent-bright/40"
|
|
73
|
+
/>
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
onClick={() => setAvatarSeed(Math.random().toString(36).slice(2, 10))}
|
|
77
|
+
className="px-3 py-2 rounded-[8px] border border-white/[0.08] bg-transparent text-text-3 text-[12px] font-600 cursor-pointer transition-all hover:bg-white/[0.04] shrink-0"
|
|
78
|
+
>
|
|
79
|
+
Randomize
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{/* Name */}
|
|
85
|
+
<div className="mb-6">
|
|
86
|
+
<label className="block text-[12px] font-600 text-text-2 mb-1.5">Name</label>
|
|
87
|
+
<input
|
|
88
|
+
type="text"
|
|
89
|
+
value={name}
|
|
90
|
+
onChange={(e) => setName(e.target.value)}
|
|
91
|
+
placeholder="Your name"
|
|
92
|
+
className="w-full px-3 py-2 rounded-[8px] bg-white/[0.06] border border-white/[0.08] text-[13px] text-text placeholder:text-text-3 focus:outline-none focus:border-accent-bright/40"
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
{/* Save */}
|
|
97
|
+
<button
|
|
98
|
+
onClick={handleSave}
|
|
99
|
+
disabled={!name.trim() || saving}
|
|
100
|
+
className="w-full py-2.5 rounded-[8px] text-[13px] font-600 bg-accent-bright text-white hover:bg-accent-bright/90 transition-all disabled:opacity-50 cursor-pointer mb-4"
|
|
101
|
+
>
|
|
102
|
+
{saving ? 'Saving...' : 'Save'}
|
|
103
|
+
</button>
|
|
104
|
+
|
|
105
|
+
{/* Sign out */}
|
|
106
|
+
<button
|
|
107
|
+
onClick={handleSignOut}
|
|
108
|
+
className="w-full text-center text-[12px] text-text-3 hover:text-text-2 transition-all cursor-pointer bg-transparent border-none"
|
|
109
|
+
>
|
|
110
|
+
Sign in as different user
|
|
111
|
+
</button>
|
|
112
|
+
</div>
|
|
113
|
+
</BottomSheet>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
senderName: string
|
|
5
|
+
text: string
|
|
6
|
+
onClick?: () => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function ReplyQuote({ senderName, text, onClick }: Props) {
|
|
10
|
+
const truncated = text.length > 120 ? text.slice(0, 120) + '...' : text
|
|
11
|
+
return (
|
|
12
|
+
<button
|
|
13
|
+
type="button"
|
|
14
|
+
onClick={onClick}
|
|
15
|
+
className="flex items-start gap-2 mb-1.5 text-left w-full bg-transparent border-none p-0 cursor-pointer group/reply"
|
|
16
|
+
>
|
|
17
|
+
<div className="w-0.5 shrink-0 self-stretch rounded-full bg-accent-bright/50" />
|
|
18
|
+
<div className="min-w-0 flex-1">
|
|
19
|
+
<span className="text-[11px] font-600 text-accent-bright">{senderName}</span>
|
|
20
|
+
<p className="text-[12px] text-text-3 leading-[1.4] break-words m-0 group-hover/reply:text-text-2 transition-colors">
|
|
21
|
+
{truncated}
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
</button>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -7,11 +7,12 @@ import { api } from '@/lib/api-client'
|
|
|
7
7
|
import type { AppView } from '@/types'
|
|
8
8
|
|
|
9
9
|
interface SearchResult {
|
|
10
|
-
type: 'task' | 'agent' | 'session' | 'schedule' | 'webhook' | 'skill'
|
|
10
|
+
type: 'task' | 'agent' | 'session' | 'schedule' | 'webhook' | 'skill' | 'message'
|
|
11
11
|
id: string
|
|
12
12
|
title: string
|
|
13
13
|
description?: string
|
|
14
14
|
status?: string
|
|
15
|
+
messageIndex?: number
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const TYPE_ICONS: Record<SearchResult['type'], string> = {
|
|
@@ -21,11 +22,13 @@ const TYPE_ICONS: Record<SearchResult['type'], string> = {
|
|
|
21
22
|
schedule: 'M12 6v6l4 2',
|
|
22
23
|
webhook: 'M22 12h-4l-3 7L9 5l-3 7H2',
|
|
23
24
|
skill: 'M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z',
|
|
25
|
+
message: 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z',
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const TYPE_EXTRA_PATHS: Partial<Record<SearchResult['type'], string>> = {
|
|
27
29
|
agent: 'M12 7a4 4 0 1 0 0-0.01',
|
|
28
30
|
schedule: 'M12 12a10 10 0 1 0 0-0.01',
|
|
31
|
+
message: 'M8 10h8',
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
const TYPE_VIEW_MAP: Record<SearchResult['type'], AppView> = {
|
|
@@ -35,15 +38,17 @@ const TYPE_VIEW_MAP: Record<SearchResult['type'], AppView> = {
|
|
|
35
38
|
schedule: 'schedules',
|
|
36
39
|
webhook: 'webhooks',
|
|
37
40
|
skill: 'skills',
|
|
41
|
+
message: 'agents',
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
const TYPE_LABELS: Record<SearchResult['type'], string> = {
|
|
41
45
|
agent: 'Agent',
|
|
42
46
|
task: 'Task',
|
|
43
|
-
session: '
|
|
47
|
+
session: 'Chat',
|
|
44
48
|
schedule: 'Schedule',
|
|
45
49
|
webhook: 'Webhook',
|
|
46
50
|
skill: 'Skill',
|
|
51
|
+
message: 'Message',
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
export function SearchDialog() {
|
|
@@ -68,7 +73,7 @@ export function SearchDialog() {
|
|
|
68
73
|
const setWebhookSheetOpen = useAppStore((s) => s.setWebhookSheetOpen)
|
|
69
74
|
const setEditingSkillId = useAppStore((s) => s.setEditingSkillId)
|
|
70
75
|
const setSkillSheetOpen = useAppStore((s) => s.setSkillSheetOpen)
|
|
71
|
-
const
|
|
76
|
+
const setCurrentSession = useAppStore((s) => s.setCurrentSession)
|
|
72
77
|
|
|
73
78
|
// Global Cmd+K / Ctrl+K listener
|
|
74
79
|
useEffect(() => {
|
|
@@ -141,7 +146,11 @@ export function SearchDialog() {
|
|
|
141
146
|
setTaskSheetOpen(true)
|
|
142
147
|
break
|
|
143
148
|
case 'session':
|
|
144
|
-
|
|
149
|
+
setCurrentSession(result.id)
|
|
150
|
+
setActiveView('agents')
|
|
151
|
+
break
|
|
152
|
+
case 'message':
|
|
153
|
+
setCurrentSession(result.id)
|
|
145
154
|
setActiveView('agents')
|
|
146
155
|
break
|
|
147
156
|
case 'schedule':
|
|
@@ -235,7 +244,7 @@ export function SearchDialog() {
|
|
|
235
244
|
>
|
|
236
245
|
{/* Type icon */}
|
|
237
246
|
<div className={`w-8 h-8 rounded-[8px] flex items-center justify-center shrink-0
|
|
238
|
-
${idx === selectedIdx ? 'bg-
|
|
247
|
+
${idx === selectedIdx ? 'bg-accent-bright/20' : 'bg-white/[0.04]'}`}>
|
|
239
248
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"
|
|
240
249
|
className={idx === selectedIdx ? 'text-[#818CF8]' : 'text-text-3'}>
|
|
241
250
|
<path d={TYPE_ICONS[result.type]} />
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
children: React.ReactNode
|
|
3
|
+
className?: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function SectionLabel({ children, className = '' }: Props) {
|
|
7
|
+
return (
|
|
8
|
+
<label className={`block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3 ${className}`}>
|
|
9
|
+
{children}
|
|
10
|
+
</label>
|
|
11
|
+
)
|
|
12
|
+
}
|
|
@@ -103,7 +103,7 @@ export function PluginManager() {
|
|
|
103
103
|
<div
|
|
104
104
|
onClick={() => togglePlugin(p.filename, !p.enabled)}
|
|
105
105
|
className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
|
|
106
|
-
${p.enabled ? 'bg-
|
|
106
|
+
${p.enabled ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
|
|
107
107
|
>
|
|
108
108
|
<div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
|
|
109
109
|
${p.enabled ? 'left-[22px]' : 'left-0.5'}`} />
|
|
@@ -167,7 +167,7 @@ export function ProvidersSection({ inputClass }: SettingsSectionProps) {
|
|
|
167
167
|
<button
|
|
168
168
|
onClick={handleAdd}
|
|
169
169
|
disabled={!newKey.trim()}
|
|
170
|
-
className="flex-1 py-3 rounded-[14px] border-none bg-
|
|
170
|
+
className="flex-1 py-3 rounded-[14px] border-none bg-accent-bright text-white text-[14px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110"
|
|
171
171
|
style={{ fontFamily: 'inherit' }}
|
|
172
172
|
>
|
|
173
173
|
Save Key
|
|
@@ -121,7 +121,7 @@ export function SecretsSection({ appSettings, inputClass }: SettingsSectionProps
|
|
|
121
121
|
|
|
122
122
|
<div className="flex gap-3 pt-2">
|
|
123
123
|
<button onClick={() => setAddingSecret(false)} className="flex-1 py-3 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px] font-600 cursor-pointer hover:bg-surface-2 transition-colors" style={{ fontFamily: 'inherit' }}>Cancel</button>
|
|
124
|
-
<button onClick={handleAddSecret} disabled={!secretName.trim() || !secretValue.trim()} className="flex-1 py-3 rounded-[14px] border-none bg-
|
|
124
|
+
<button onClick={handleAddSecret} disabled={!secretName.trim() || !secretValue.trim()} className="flex-1 py-3 rounded-[14px] border-none bg-accent-bright text-white text-[14px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110" style={{ fontFamily: 'inherit' }}>Save Secret</button>
|
|
125
125
|
</div>
|
|
126
126
|
</div>
|
|
127
127
|
) : (
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { toast } from 'sonner'
|
|
5
|
+
import type { SettingsSectionProps } from './types'
|
|
6
|
+
|
|
7
|
+
const PRESETS = [
|
|
8
|
+
{ label: 'Default', color: '#1e1e30' },
|
|
9
|
+
{ label: 'Midnight', color: '#1a1a3a' },
|
|
10
|
+
{ label: 'Forest', color: '#1a2e1e' },
|
|
11
|
+
{ label: 'Warm', color: '#2e1e1a' },
|
|
12
|
+
{ label: 'Slate', color: '#1e2428' },
|
|
13
|
+
{ label: 'Rose', color: '#2e1a24' },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
export function ThemeSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
|
|
17
|
+
const currentHue = appSettings.themeHue || PRESETS[0].color
|
|
18
|
+
const [customHex, setCustomHex] = useState(
|
|
19
|
+
PRESETS.some((p) => p.color === currentHue) ? '' : currentHue,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const applyHue = (color: string) => {
|
|
23
|
+
patchSettings({ themeHue: color })
|
|
24
|
+
document.documentElement.style.setProperty('--neutral-tint', color)
|
|
25
|
+
toast.success('Theme updated')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const handleCustomChange = (value: string) => {
|
|
29
|
+
setCustomHex(value)
|
|
30
|
+
if (/^#[0-9a-fA-F]{6}$/.test(value)) {
|
|
31
|
+
applyHue(value)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="mb-10">
|
|
37
|
+
<h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
38
|
+
Theme
|
|
39
|
+
</h3>
|
|
40
|
+
<p className="text-[12px] text-text-3 mb-5">
|
|
41
|
+
Shift the UI color palette. Pick a preset or enter a custom hex color.
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
{/* Preset swatches */}
|
|
45
|
+
<div className="flex flex-wrap gap-3 mb-4">
|
|
46
|
+
{PRESETS.map((preset) => {
|
|
47
|
+
const isActive = currentHue === preset.color && !customHex
|
|
48
|
+
return (
|
|
49
|
+
<button
|
|
50
|
+
key={preset.color}
|
|
51
|
+
onClick={() => { setCustomHex(''); applyHue(preset.color) }}
|
|
52
|
+
className={`group flex flex-col items-center gap-1.5 cursor-pointer bg-transparent border-none p-0`}
|
|
53
|
+
title={preset.label}
|
|
54
|
+
>
|
|
55
|
+
<div
|
|
56
|
+
className={`w-9 h-9 rounded-full transition-all duration-200 ${
|
|
57
|
+
isActive
|
|
58
|
+
? 'ring-2 ring-accent-bright ring-offset-2 ring-offset-bg scale-110'
|
|
59
|
+
: 'hover:scale-105'
|
|
60
|
+
}`}
|
|
61
|
+
style={{ backgroundColor: preset.color }}
|
|
62
|
+
/>
|
|
63
|
+
<span className={`text-[10px] font-500 ${isActive ? 'text-text' : 'text-text-3'}`}>
|
|
64
|
+
{preset.label}
|
|
65
|
+
</span>
|
|
66
|
+
</button>
|
|
67
|
+
)
|
|
68
|
+
})}
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{/* Custom color picker + hex input */}
|
|
72
|
+
<div className="flex items-center gap-3">
|
|
73
|
+
<label className="text-[12px] text-text-3 shrink-0">Custom</label>
|
|
74
|
+
<div className="relative shrink-0">
|
|
75
|
+
<input
|
|
76
|
+
type="color"
|
|
77
|
+
value={customHex || currentHue}
|
|
78
|
+
onChange={(e) => handleCustomChange(e.target.value)}
|
|
79
|
+
className="w-9 h-9 rounded-full cursor-pointer border-2 border-white/[0.1] bg-transparent appearance-none [&::-webkit-color-swatch-wrapper]:p-0 [&::-webkit-color-swatch]:rounded-full [&::-webkit-color-swatch]:border-none [&::-moz-color-swatch]:rounded-full [&::-moz-color-swatch]:border-none"
|
|
80
|
+
title="Pick a custom color"
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
<input
|
|
84
|
+
type="text"
|
|
85
|
+
value={customHex}
|
|
86
|
+
onChange={(e) => handleCustomChange(e.target.value)}
|
|
87
|
+
placeholder="#2a1f3d"
|
|
88
|
+
maxLength={7}
|
|
89
|
+
className={`${inputClass} max-w-[120px] font-mono`}
|
|
90
|
+
style={{ fontFamily: 'inherit' }}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
4
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
3
5
|
import type { SettingsSectionProps } from './types'
|
|
4
6
|
|
|
5
7
|
export function UserPreferencesSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
|
|
8
|
+
const agents = useAppStore((s) => s.agents)
|
|
9
|
+
const sortedAgents = Object.values(agents).sort((a, b) => a.name.localeCompare(b.name))
|
|
10
|
+
|
|
6
11
|
return (
|
|
7
12
|
<div className="mb-10">
|
|
8
13
|
<h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
@@ -19,6 +24,40 @@ export function UserPreferencesSection({ appSettings, patchSettings, inputClass
|
|
|
19
24
|
className={`${inputClass} resize-y min-h-[100px]`}
|
|
20
25
|
style={{ fontFamily: 'inherit' }}
|
|
21
26
|
/>
|
|
27
|
+
|
|
28
|
+
{/* Default agent */}
|
|
29
|
+
<div className="mt-6">
|
|
30
|
+
<label className="text-[12px] font-600 text-text-2 block mb-1.5">Default Agent</label>
|
|
31
|
+
<p className="text-[11px] text-text-3/60 mb-2">
|
|
32
|
+
The agent that opens automatically when you start the app or click Main Chat.
|
|
33
|
+
</p>
|
|
34
|
+
<div className="flex flex-wrap gap-2">
|
|
35
|
+
<button
|
|
36
|
+
onClick={() => patchSettings({ defaultAgentId: null })}
|
|
37
|
+
className={`flex items-center gap-2 px-3 py-2 rounded-[10px] text-[12px] font-600 cursor-pointer transition-all border
|
|
38
|
+
${!appSettings.defaultAgentId
|
|
39
|
+
? 'bg-white/[0.06] border-accent-bright/30 text-text'
|
|
40
|
+
: 'bg-transparent border-white/[0.06] text-text-3 hover:bg-white/[0.03]'}`}
|
|
41
|
+
style={{ fontFamily: 'inherit' }}
|
|
42
|
+
>
|
|
43
|
+
Auto (first agent)
|
|
44
|
+
</button>
|
|
45
|
+
{sortedAgents.map((agent) => (
|
|
46
|
+
<button
|
|
47
|
+
key={agent.id}
|
|
48
|
+
onClick={() => patchSettings({ defaultAgentId: agent.id })}
|
|
49
|
+
className={`flex items-center gap-2 px-3 py-2 rounded-[10px] text-[12px] font-600 cursor-pointer transition-all border
|
|
50
|
+
${appSettings.defaultAgentId === agent.id
|
|
51
|
+
? 'bg-white/[0.06] border-accent-bright/30 text-text'
|
|
52
|
+
: 'bg-transparent border-white/[0.06] text-text-3 hover:bg-white/[0.03]'}`}
|
|
53
|
+
style={{ fontFamily: 'inherit' }}
|
|
54
|
+
>
|
|
55
|
+
<AgentAvatar seed={agent.avatarSeed || null} name={agent.name} size={18} />
|
|
56
|
+
{agent.name}
|
|
57
|
+
</button>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
22
61
|
</div>
|
|
23
62
|
)
|
|
24
63
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect } from 'react'
|
|
3
|
+
import { useEffect, useState, useRef, useCallback } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { inputClass } from './utils'
|
|
6
6
|
import { UserPreferencesSection } from './section-user-preferences'
|
|
7
|
+
import { ThemeSection } from './section-theme'
|
|
7
8
|
import { OrchestratorSection } from './section-orchestrator'
|
|
8
9
|
import { RuntimeLoopSection } from './section-runtime-loop'
|
|
9
10
|
import { CapabilityPolicySection } from './section-capability-policy'
|
|
@@ -16,6 +17,46 @@ import { SecretsSection } from './section-secrets'
|
|
|
16
17
|
import { ProvidersSection } from './section-providers'
|
|
17
18
|
import { PluginManager } from './plugin-manager'
|
|
18
19
|
|
|
20
|
+
interface Tab {
|
|
21
|
+
id: string
|
|
22
|
+
label: string
|
|
23
|
+
icon: React.ReactNode
|
|
24
|
+
keywords: string[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const TABS: Tab[] = [
|
|
28
|
+
{
|
|
29
|
+
id: 'general',
|
|
30
|
+
label: 'General',
|
|
31
|
+
keywords: ['preferences', 'user', 'language', 'default', 'capability', 'policy', 'permissions', 'tools'],
|
|
32
|
+
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
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'appearance',
|
|
36
|
+
label: 'Appearance',
|
|
37
|
+
keywords: ['theme', 'color', 'hue', 'palette', 'dark', 'light', 'style', 'swatch'],
|
|
38
|
+
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="5" /><line x1="12" y1="1" x2="12" y2="3" /><line x1="12" y1="21" x2="12" y2="23" /><line x1="4.22" y1="4.22" x2="5.64" y2="5.64" /><line x1="18.36" y1="18.36" x2="19.78" y2="19.78" /><line x1="1" y1="12" x2="3" y2="12" /><line x1="21" y1="12" x2="23" y2="12" /><line x1="4.22" y1="19.78" x2="5.64" y2="18.36" /><line x1="18.36" y1="5.64" x2="19.78" y2="4.22" /></svg>,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'agents',
|
|
42
|
+
label: 'Agents & Loops',
|
|
43
|
+
keywords: ['orchestrator', 'runtime', 'loop', 'heartbeat', 'delegation', 'agent', 'swarm', 'turns'],
|
|
44
|
+
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
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'memory',
|
|
48
|
+
label: 'Memory & AI',
|
|
49
|
+
keywords: ['embedding', 'vector', 'voice', 'web search', 'memory', 'consolidation', 'tts', 'ai'],
|
|
50
|
+
icon: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2z" /><path d="M12 16v-4" /><path d="M12 8h.01" /></svg>,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'integrations',
|
|
54
|
+
label: 'Integrations',
|
|
55
|
+
keywords: ['provider', 'secret', 'plugin', 'api', 'key', 'openai', 'anthropic', 'ollama', 'credential'],
|
|
56
|
+
icon: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M12 2v4m0 12v4M2 12h4m12 0h4" /><circle cx="12" cy="12" r="4" /><path d="M8 8L5.5 5.5M16 8l2.5-2.5M8 16l-2.5 2.5M16 16l2.5 2.5" /></svg>,
|
|
57
|
+
},
|
|
58
|
+
]
|
|
59
|
+
|
|
19
60
|
export function SettingsPage() {
|
|
20
61
|
const loadProviders = useAppStore((s) => s.loadProviders)
|
|
21
62
|
const loadCredentials = useAppStore((s) => s.loadCredentials)
|
|
@@ -25,6 +66,22 @@ export function SettingsPage() {
|
|
|
25
66
|
const loadSecrets = useAppStore((s) => s.loadSecrets)
|
|
26
67
|
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
27
68
|
const credentials = useAppStore((s) => s.credentials)
|
|
69
|
+
const validTabIds = TABS.map((t) => t.id)
|
|
70
|
+
const [activeTab, setActiveTabRaw] = useState(() => {
|
|
71
|
+
if (typeof window === 'undefined') return 'general'
|
|
72
|
+
const params = new URLSearchParams(window.location.search)
|
|
73
|
+
const tab = params.get('tab')
|
|
74
|
+
return tab && validTabIds.includes(tab) ? tab : 'general'
|
|
75
|
+
})
|
|
76
|
+
const contentRef = useRef<HTMLDivElement>(null)
|
|
77
|
+
|
|
78
|
+
const setActiveTab = useCallback((tab: string) => {
|
|
79
|
+
setActiveTabRaw(tab)
|
|
80
|
+
const url = new URL(window.location.href)
|
|
81
|
+
if (tab === 'general') url.searchParams.delete('tab')
|
|
82
|
+
else url.searchParams.set('tab', tab)
|
|
83
|
+
window.history.replaceState(null, '', url.toString())
|
|
84
|
+
}, [])
|
|
28
85
|
|
|
29
86
|
useEffect(() => {
|
|
30
87
|
loadProviders()
|
|
@@ -35,38 +92,134 @@ export function SettingsPage() {
|
|
|
35
92
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
36
93
|
}, [])
|
|
37
94
|
|
|
95
|
+
// Scroll to top when switching tabs
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
contentRef.current?.scrollTo({ top: 0, behavior: 'smooth' })
|
|
98
|
+
}, [activeTab])
|
|
99
|
+
|
|
100
|
+
const [searchQuery, setSearchQuery] = useState('')
|
|
38
101
|
const credList = Object.values(credentials)
|
|
39
102
|
const patchSettings = updateSettings
|
|
103
|
+
const sectionProps = { appSettings, patchSettings, inputClass }
|
|
104
|
+
|
|
105
|
+
const matchingTabIds = searchQuery
|
|
106
|
+
? new Set(TABS.filter((t) => {
|
|
107
|
+
const q = searchQuery.toLowerCase()
|
|
108
|
+
return t.label.toLowerCase().includes(q) || t.keywords.some((k) => k.includes(q))
|
|
109
|
+
}).map((t) => t.id))
|
|
110
|
+
: null
|
|
111
|
+
|
|
112
|
+
// Auto-switch to first matching tab when searching
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (matchingTabIds && matchingTabIds.size > 0 && !matchingTabIds.has(activeTab)) {
|
|
115
|
+
const first = TABS.find((t) => matchingTabIds.has(t.id))
|
|
116
|
+
if (first) setActiveTab(first.id)
|
|
117
|
+
}
|
|
118
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
119
|
+
}, [searchQuery])
|
|
40
120
|
|
|
41
121
|
return (
|
|
42
|
-
<div className="flex-1 flex
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
122
|
+
<div className="flex-1 flex h-full min-w-0">
|
|
123
|
+
{/* Tab sidebar */}
|
|
124
|
+
<div className="w-[200px] shrink-0 border-r border-white/[0.04] py-6 px-3 flex flex-col gap-1">
|
|
125
|
+
<h2 className="font-display text-[14px] font-700 text-text px-3 mb-3 tracking-[-0.01em]">Settings</h2>
|
|
126
|
+
<div className="px-2 mb-3">
|
|
127
|
+
<div className="relative">
|
|
128
|
+
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="absolute left-2.5 top-1/2 -translate-y-1/2 text-text-3/50">
|
|
129
|
+
<circle cx="11" cy="11" r="8" />
|
|
130
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
131
|
+
</svg>
|
|
132
|
+
<input
|
|
133
|
+
type="text"
|
|
134
|
+
value={searchQuery}
|
|
135
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
136
|
+
placeholder="Search settings..."
|
|
137
|
+
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
|
+
style={{ fontFamily: 'inherit' }}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
47
141
|
</div>
|
|
142
|
+
{TABS.map((tab) => {
|
|
143
|
+
const dimmed = matchingTabIds && !matchingTabIds.has(tab.id)
|
|
144
|
+
return (
|
|
145
|
+
<button
|
|
146
|
+
key={tab.id}
|
|
147
|
+
onClick={() => setActiveTab(tab.id)}
|
|
148
|
+
className={`w-full flex items-center gap-2.5 px-3 py-2 rounded-[10px] text-[13px] font-500 cursor-pointer transition-all border-none text-left
|
|
149
|
+
${dimmed ? 'opacity-30' : ''}
|
|
150
|
+
${activeTab === tab.id
|
|
151
|
+
? 'bg-accent-soft text-accent-bright'
|
|
152
|
+
: 'bg-transparent text-text-3 hover:text-text hover:bg-white/[0.04]'}`}
|
|
153
|
+
style={{ fontFamily: 'inherit' }}
|
|
154
|
+
>
|
|
155
|
+
<span className="shrink-0">{tab.icon}</span>
|
|
156
|
+
{tab.label}
|
|
157
|
+
</button>
|
|
158
|
+
)
|
|
159
|
+
})}
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{/* Content */}
|
|
163
|
+
<div ref={contentRef} className="flex-1 overflow-y-auto">
|
|
164
|
+
<div className="max-w-2xl px-8 py-8">
|
|
165
|
+
{/* Tab header */}
|
|
166
|
+
<div className="mb-8">
|
|
167
|
+
<h3 className="font-display text-[22px] font-700 tracking-[-0.02em] text-text">
|
|
168
|
+
{TABS.find((t) => t.id === activeTab)?.label}
|
|
169
|
+
</h3>
|
|
170
|
+
<p className="text-[13px] text-text-3 mt-1">
|
|
171
|
+
{activeTab === 'general' && 'User preferences and global behavior settings.'}
|
|
172
|
+
{activeTab === 'appearance' && 'Customize the look and feel of the interface.'}
|
|
173
|
+
{activeTab === 'agents' && 'Orchestrator, runtime loops, capabilities and heartbeat.'}
|
|
174
|
+
{activeTab === 'memory' && 'Embedding, memory governance, voice and web search.'}
|
|
175
|
+
{activeTab === 'integrations' && 'Providers, secrets and plugins.'}
|
|
176
|
+
</p>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
{activeTab === 'general' && (
|
|
180
|
+
<>
|
|
181
|
+
<UserPreferencesSection {...sectionProps} />
|
|
182
|
+
<CapabilityPolicySection {...sectionProps} />
|
|
183
|
+
</>
|
|
184
|
+
)}
|
|
185
|
+
|
|
186
|
+
{activeTab === 'appearance' && (
|
|
187
|
+
<ThemeSection {...sectionProps} />
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
{activeTab === 'agents' && (
|
|
191
|
+
<>
|
|
192
|
+
<OrchestratorSection {...sectionProps} />
|
|
193
|
+
<RuntimeLoopSection {...sectionProps} />
|
|
194
|
+
<HeartbeatSection {...sectionProps} />
|
|
195
|
+
</>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
{activeTab === 'memory' && (
|
|
199
|
+
<>
|
|
200
|
+
<EmbeddingSection {...sectionProps} credList={credList} />
|
|
201
|
+
<MemorySection {...sectionProps} />
|
|
202
|
+
<VoiceSection {...sectionProps} />
|
|
203
|
+
<WebSearchSection {...sectionProps} />
|
|
204
|
+
</>
|
|
205
|
+
)}
|
|
48
206
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<p className="text-[12px] text-text-3 mb-5">
|
|
66
|
-
Extend agent behavior with hooks. Install from the marketplace, a URL, or drop .js files into <code className="text-[11px] font-mono text-text-2">data/plugins/</code>.
|
|
67
|
-
<span className="text-text-3/70 ml-1">OpenClaw plugins are also supported.</span>
|
|
68
|
-
</p>
|
|
69
|
-
<PluginManager />
|
|
207
|
+
{activeTab === 'integrations' && (
|
|
208
|
+
<>
|
|
209
|
+
<ProvidersSection {...sectionProps} />
|
|
210
|
+
<SecretsSection {...sectionProps} />
|
|
211
|
+
<div className="mb-10">
|
|
212
|
+
<h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
|
|
213
|
+
Plugins
|
|
214
|
+
</h3>
|
|
215
|
+
<p className="text-[12px] text-text-3 mb-5">
|
|
216
|
+
Extend agent behavior with hooks. Install from the marketplace, a URL, or drop .js files into <code className="text-[11px] font-mono text-text-2">data/plugins/</code>.
|
|
217
|
+
<span className="text-text-3/70 ml-1">OpenClaw plugins are also supported.</span>
|
|
218
|
+
</p>
|
|
219
|
+
<PluginManager />
|
|
220
|
+
</div>
|
|
221
|
+
</>
|
|
222
|
+
)}
|
|
70
223
|
</div>
|
|
71
224
|
</div>
|
|
72
225
|
</div>
|