@swarmclawai/swarmclaw 0.5.2 → 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 +42 -7
- package/bin/swarmclaw.js +76 -16
- package/next.config.ts +11 -1
- package/package.json +4 -2
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- 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/[id]/route.ts +27 -0
- package/src/app/api/notifications/route.ts +68 -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 +155 -0
- 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 +20 -0
- package/src/app/api/tasks/bulk/route.ts +100 -0
- package/src/app/api/tasks/route.ts +1 -0
- package/src/app/api/usage/route.ts +45 -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 +42 -0
- package/src/cli/index.test.js +30 -0
- package/src/cli/spec.js +32 -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 +209 -83
- package/src/components/layout/mobile-header.tsx +2 -0
- 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 +3 -2
- 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 +223 -0
- 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 +296 -0
- 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/metrics-dashboard.tsx +78 -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/cron-human.ts +114 -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 +42 -0
- package/src/lib/server/daemon-state.ts +165 -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 +80 -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/proxy.ts +79 -2
- package/src/stores/use-app-store.ts +94 -3
- package/src/stores/use-chat-store.ts +48 -3
- package/src/stores/use-chatroom-store.ts +276 -0
- package/src/types/index.ts +69 -2
|
@@ -11,6 +11,9 @@ import type { ProviderType, ClaudeSkill } from '@/types'
|
|
|
11
11
|
import { AVAILABLE_TOOLS, PLATFORM_TOOLS } from '@/lib/tool-definitions'
|
|
12
12
|
import { NATIVE_CAPABILITY_PROVIDER_IDS, NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
|
|
13
13
|
import { AgentAvatar } from './agent-avatar'
|
|
14
|
+
import { AgentPickerList } from '@/components/shared/agent-picker-list'
|
|
15
|
+
import { randomSoul } from '@/lib/soul-suggestions'
|
|
16
|
+
import { SectionLabel } from '@/components/shared/section-label'
|
|
14
17
|
|
|
15
18
|
const HB_PRESETS = [30, 60, 120, 300, 600, 1800, 3600] as const
|
|
16
19
|
|
|
@@ -92,7 +95,7 @@ export function AgentSheet() {
|
|
|
92
95
|
const [mcpTools, setMcpTools] = useState<Record<string, { name: string; description: string }[]>>({})
|
|
93
96
|
const [mcpToolsLoading, setMcpToolsLoading] = useState(false)
|
|
94
97
|
const [fallbackCredentialIds, setFallbackCredentialIds] = useState<string[]>([])
|
|
95
|
-
|
|
98
|
+
// platformAssignScope is derived from isOrchestrator — no separate state needed
|
|
96
99
|
const [capabilities, setCapabilities] = useState<string[]>([])
|
|
97
100
|
const [capInput, setCapInput] = useState('')
|
|
98
101
|
const [ollamaMode, setOllamaMode] = useState<'local' | 'cloud'>('local')
|
|
@@ -167,13 +170,13 @@ export function AgentSheet() {
|
|
|
167
170
|
setMcpServerIds(editing.mcpServerIds || [])
|
|
168
171
|
setMcpDisabledTools(editing.mcpDisabledTools || [])
|
|
169
172
|
setFallbackCredentialIds(editing.fallbackCredentialIds || [])
|
|
170
|
-
|
|
173
|
+
// platformAssignScope derived from isOrchestrator — no separate state
|
|
171
174
|
setCapabilities(editing.capabilities || [])
|
|
172
175
|
setCapInput('')
|
|
173
176
|
setOllamaMode(editing.credentialId && editing.provider === 'ollama' ? 'cloud' : 'local')
|
|
174
177
|
setOpenclawEnabled(editing.provider === 'openclaw')
|
|
175
178
|
setProjectId(editing.projectId)
|
|
176
|
-
setAvatarSeed(editing.avatarSeed ||
|
|
179
|
+
setAvatarSeed(editing.avatarSeed || crypto.randomUUID().slice(0, 8))
|
|
177
180
|
setThinkingLevel(editing.thinkingLevel || '')
|
|
178
181
|
setHeartbeatEnabled(editing.heartbeatEnabled || false)
|
|
179
182
|
setHeartbeatIntervalSec(parseDurationToSec(editing.heartbeatInterval, editing.heartbeatIntervalSec))
|
|
@@ -182,7 +185,7 @@ export function AgentSheet() {
|
|
|
182
185
|
} else {
|
|
183
186
|
setName('')
|
|
184
187
|
setDescription('')
|
|
185
|
-
setSoul(
|
|
188
|
+
setSoul(randomSoul())
|
|
186
189
|
setSystemPrompt('')
|
|
187
190
|
setProvider('claude-cli')
|
|
188
191
|
setModel('')
|
|
@@ -195,7 +198,6 @@ export function AgentSheet() {
|
|
|
195
198
|
setSkillIds([])
|
|
196
199
|
setMcpDisabledTools([])
|
|
197
200
|
setFallbackCredentialIds([])
|
|
198
|
-
setPlatformAssignScope('self')
|
|
199
201
|
setCapabilities([])
|
|
200
202
|
setCapInput('')
|
|
201
203
|
setOllamaMode('local')
|
|
@@ -292,7 +294,7 @@ export function AgentSheet() {
|
|
|
292
294
|
mcpServerIds,
|
|
293
295
|
mcpDisabledTools: mcpDisabledTools.length ? mcpDisabledTools : undefined,
|
|
294
296
|
fallbackCredentialIds,
|
|
295
|
-
platformAssignScope,
|
|
297
|
+
platformAssignScope: (isOrchestrator ? 'all' : 'self') as 'all' | 'self',
|
|
296
298
|
capabilities,
|
|
297
299
|
projectId: projectId || undefined,
|
|
298
300
|
avatarSeed: avatarSeed.trim() || undefined,
|
|
@@ -305,8 +307,10 @@ export function AgentSheet() {
|
|
|
305
307
|
}
|
|
306
308
|
if (editing) {
|
|
307
309
|
await updateAgent(editing.id, data)
|
|
310
|
+
toast.success('Agent saved')
|
|
308
311
|
} else {
|
|
309
312
|
await createAgent(data)
|
|
313
|
+
toast.success('Agent created')
|
|
310
314
|
}
|
|
311
315
|
await loadAgents()
|
|
312
316
|
onClose()
|
|
@@ -315,6 +319,7 @@ export function AgentSheet() {
|
|
|
315
319
|
const handleDelete = async () => {
|
|
316
320
|
if (editing) {
|
|
317
321
|
await deleteAgent(editing.id)
|
|
322
|
+
toast.success('Agent moved to trash')
|
|
318
323
|
await loadAgents()
|
|
319
324
|
onClose()
|
|
320
325
|
}
|
|
@@ -452,12 +457,12 @@ export function AgentSheet() {
|
|
|
452
457
|
</div>
|
|
453
458
|
|
|
454
459
|
<div className="mb-8">
|
|
455
|
-
<
|
|
460
|
+
<SectionLabel>Name</SectionLabel>
|
|
456
461
|
<input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. SEO Researcher" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
457
462
|
</div>
|
|
458
463
|
|
|
459
464
|
<div className="mb-8">
|
|
460
|
-
<
|
|
465
|
+
<SectionLabel>Avatar</SectionLabel>
|
|
461
466
|
<div className="flex items-center gap-3">
|
|
462
467
|
<AgentAvatar seed={avatarSeed || null} name={name || 'A'} size={40} />
|
|
463
468
|
<input
|
|
@@ -470,25 +475,29 @@ export function AgentSheet() {
|
|
|
470
475
|
/>
|
|
471
476
|
<button
|
|
472
477
|
type="button"
|
|
473
|
-
onClick={() => setAvatarSeed(
|
|
474
|
-
className="px-3 py-2 rounded-[10px] 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"
|
|
478
|
+
onClick={() => setAvatarSeed(crypto.randomUUID().slice(0, 8))}
|
|
479
|
+
className="inline-flex items-center gap-1.5 px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-3 text-[12px] font-600 cursor-pointer transition-all hover:bg-white/[0.04] hover:text-text-2 active:scale-95 shrink-0"
|
|
475
480
|
style={{ fontFamily: 'inherit' }}
|
|
481
|
+
title="Shuffle avatar"
|
|
476
482
|
>
|
|
477
|
-
|
|
483
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
484
|
+
<rect x="4" y="4" width="16" height="16" rx="2" />
|
|
485
|
+
<circle cx="9" cy="9" r="1" fill="currentColor" />
|
|
486
|
+
<circle cx="15" cy="15" r="1" fill="currentColor" />
|
|
487
|
+
</svg>
|
|
488
|
+
Shuffle
|
|
478
489
|
</button>
|
|
479
490
|
</div>
|
|
480
491
|
</div>
|
|
481
492
|
|
|
482
493
|
<div className="mb-8">
|
|
483
|
-
<
|
|
494
|
+
<SectionLabel>Description</SectionLabel>
|
|
484
495
|
<input type="text" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="What does this agent do?" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
485
496
|
</div>
|
|
486
497
|
|
|
487
498
|
{/* Capabilities — hidden for OpenClaw (gateway manages its own capabilities) */}
|
|
488
499
|
{!openclawEnabled && <div className="mb-8">
|
|
489
|
-
<
|
|
490
|
-
Capabilities <span className="normal-case tracking-normal font-normal text-text-3">(for agent delegation)</span>
|
|
491
|
-
</label>
|
|
500
|
+
<SectionLabel>Capabilities <span className="normal-case tracking-normal font-normal text-text-3">(for agent delegation)</span></SectionLabel>
|
|
492
501
|
<div className="flex flex-wrap gap-1.5 mb-2">
|
|
493
502
|
{capabilities.map((cap) => (
|
|
494
503
|
<span
|
|
@@ -629,6 +638,20 @@ export function AgentSheet() {
|
|
|
629
638
|
</label>
|
|
630
639
|
<div className="flex items-center gap-2 mb-3">
|
|
631
640
|
<p className="text-[12px] text-text-3/60">Define the agent's voice, tone, and personality. Injected before the system prompt.</p>
|
|
641
|
+
<button
|
|
642
|
+
type="button"
|
|
643
|
+
onClick={() => setSoul(randomSoul())}
|
|
644
|
+
className="inline-flex items-center gap-1.5 shrink-0 px-2 py-1 rounded-[8px] border border-white/[0.08] bg-transparent text-[11px] text-text-3 hover:text-text-2 cursor-pointer transition-colors"
|
|
645
|
+
style={{ fontFamily: 'inherit' }}
|
|
646
|
+
title="Randomize personality"
|
|
647
|
+
>
|
|
648
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
649
|
+
<rect x="4" y="4" width="16" height="16" rx="2" />
|
|
650
|
+
<circle cx="9" cy="9" r="1" fill="currentColor" />
|
|
651
|
+
<circle cx="15" cy="15" r="1" fill="currentColor" />
|
|
652
|
+
</svg>
|
|
653
|
+
Shuffle
|
|
654
|
+
</button>
|
|
632
655
|
<button onClick={() => soulFileRef.current?.click()} className="shrink-0 px-2 py-1 rounded-[8px] border border-white/[0.08] bg-surface text-[11px] text-text-3 hover:text-text-2 cursor-pointer transition-colors" style={{ fontFamily: 'inherit' }}>Upload .md</button>
|
|
633
656
|
<input ref={soulFileRef} type="file" accept=".md,.txt,.markdown" onChange={handleFileUpload(setSoul)} className="hidden" />
|
|
634
657
|
</div>
|
|
@@ -854,7 +877,7 @@ export function AgentSheet() {
|
|
|
854
877
|
)}
|
|
855
878
|
|
|
856
879
|
{!openclawEnabled && <div className="mb-8">
|
|
857
|
-
<
|
|
880
|
+
<SectionLabel>Provider</SectionLabel>
|
|
858
881
|
<div className="grid grid-cols-3 gap-3">
|
|
859
882
|
{providers.filter((p) => !isOrchestrator || p.id !== 'claude-cli').map((p) => {
|
|
860
883
|
const isConnected = !p.requiresApiKey || Object.values(credentials).some((c) => c.provider === p.id)
|
|
@@ -883,7 +906,7 @@ export function AgentSheet() {
|
|
|
883
906
|
|
|
884
907
|
{!openclawEnabled && currentProvider && currentProvider.models.length > 0 && (
|
|
885
908
|
<div className="mb-8">
|
|
886
|
-
<
|
|
909
|
+
<SectionLabel>Model</SectionLabel>
|
|
887
910
|
<ModelCombobox
|
|
888
911
|
providerId={currentProvider.id}
|
|
889
912
|
value={model}
|
|
@@ -900,7 +923,7 @@ export function AgentSheet() {
|
|
|
900
923
|
{/* Ollama Mode Toggle */}
|
|
901
924
|
{!openclawEnabled && provider === 'ollama' && (
|
|
902
925
|
<div className="mb-8">
|
|
903
|
-
<
|
|
926
|
+
<SectionLabel>Mode</SectionLabel>
|
|
904
927
|
<div className="flex p-1 rounded-[14px] bg-surface border border-white/[0.06]">
|
|
905
928
|
{(['local', 'cloud'] as const).map((mode) => (
|
|
906
929
|
<button
|
|
@@ -931,9 +954,7 @@ export function AgentSheet() {
|
|
|
931
954
|
|
|
932
955
|
{!openclawEnabled && (currentProvider?.requiresApiKey || currentProvider?.optionalApiKey || (provider === 'ollama' && ollamaMode === 'cloud')) && (
|
|
933
956
|
<div className="mb-8">
|
|
934
|
-
<
|
|
935
|
-
API Key{currentProvider?.optionalApiKey && !currentProvider?.requiresApiKey && <span className="normal-case tracking-normal font-normal text-text-3"> (optional)</span>}
|
|
936
|
-
</label>
|
|
957
|
+
<SectionLabel>API Key{currentProvider?.optionalApiKey && !currentProvider?.requiresApiKey && <span className="normal-case tracking-normal font-normal text-text-3"> (optional)</span>}</SectionLabel>
|
|
937
958
|
{providerCredentials.length > 0 && !addingKey ? (
|
|
938
959
|
<div className="flex gap-2">
|
|
939
960
|
<select value={credentialId || ''} onChange={(e) => {
|
|
@@ -1037,9 +1058,7 @@ export function AgentSheet() {
|
|
|
1037
1058
|
|
|
1038
1059
|
{currentProvider?.requiresEndpoint && (provider === 'openclaw' || (provider === 'ollama' && ollamaMode === 'local')) && (
|
|
1039
1060
|
<div className="mb-8">
|
|
1040
|
-
<
|
|
1041
|
-
{provider === 'openclaw' ? 'OpenClaw Endpoint' : 'Endpoint'}
|
|
1042
|
-
</label>
|
|
1061
|
+
<SectionLabel>{provider === 'openclaw' ? 'OpenClaw Endpoint' : 'Endpoint'}</SectionLabel>
|
|
1043
1062
|
<input type="text" value={apiEndpoint || ''} onChange={(e) => setApiEndpoint(e.target.value || null)} placeholder={currentProvider.defaultEndpoint || 'http://localhost:11434'} className={`${inputClass} font-mono text-[14px]`} />
|
|
1044
1063
|
{provider === 'openclaw' && (
|
|
1045
1064
|
<p className="text-[13px] text-text-3/70 mt-2">The URL of your OpenClaw gateway</p>
|
|
@@ -1058,7 +1077,7 @@ export function AgentSheet() {
|
|
|
1058
1077
|
<div
|
|
1059
1078
|
onClick={() => setTools((prev) => prev.includes(t.id) ? prev.filter((x) => x !== t.id) : [...prev, t.id])}
|
|
1060
1079
|
className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
|
|
1061
|
-
${tools.includes(t.id) ? 'bg-
|
|
1080
|
+
${tools.includes(t.id) ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
|
|
1062
1081
|
>
|
|
1063
1082
|
<div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
|
|
1064
1083
|
${tools.includes(t.id) ? 'left-[22px]' : 'left-0.5'}`} />
|
|
@@ -1082,7 +1101,7 @@ export function AgentSheet() {
|
|
|
1082
1101
|
<div
|
|
1083
1102
|
onClick={() => setTools((prev) => prev.includes(t.id) ? prev.filter((x) => x !== t.id) : [...prev, t.id])}
|
|
1084
1103
|
className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
|
|
1085
|
-
${tools.includes(t.id) ? 'bg-
|
|
1104
|
+
${tools.includes(t.id) ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
|
|
1086
1105
|
>
|
|
1087
1106
|
<div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
|
|
1088
1107
|
${tools.includes(t.id) ? 'left-[22px]' : 'left-0.5'}`} />
|
|
@@ -1092,22 +1111,6 @@ export function AgentSheet() {
|
|
|
1092
1111
|
</label>
|
|
1093
1112
|
))}
|
|
1094
1113
|
</div>
|
|
1095
|
-
{(tools.includes('manage_tasks') || tools.includes('manage_schedules')) && (
|
|
1096
|
-
<div className="mt-4 ml-1 pt-3 border-t border-white/[0.04]">
|
|
1097
|
-
<label className="flex items-center gap-3 cursor-pointer">
|
|
1098
|
-
<div
|
|
1099
|
-
onClick={() => setPlatformAssignScope((prev) => prev === 'all' ? 'self' : 'all')}
|
|
1100
|
-
className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
|
|
1101
|
-
${platformAssignScope === 'all' ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
|
|
1102
|
-
>
|
|
1103
|
-
<div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
|
|
1104
|
-
${platformAssignScope === 'all' ? 'left-[22px]' : 'left-0.5'}`} />
|
|
1105
|
-
</div>
|
|
1106
|
-
<span className="font-display text-[14px] font-600 text-text-2">Assign to Other Agents</span>
|
|
1107
|
-
<span className="text-[12px] text-text-3">Allow this agent to assign tasks and schedules to other agents</span>
|
|
1108
|
-
</label>
|
|
1109
|
-
</div>
|
|
1110
|
-
)}
|
|
1111
1114
|
</div>
|
|
1112
1115
|
)}
|
|
1113
1116
|
|
|
@@ -1259,7 +1262,7 @@ export function AgentSheet() {
|
|
|
1259
1262
|
enabled ? [...prev, fullName] : prev.filter((x) => x !== fullName)
|
|
1260
1263
|
)}
|
|
1261
1264
|
className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
|
|
1262
|
-
${enabled ? 'bg-
|
|
1265
|
+
${enabled ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
|
|
1263
1266
|
>
|
|
1264
1267
|
<div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
|
|
1265
1268
|
${enabled ? 'left-[22px]' : 'left-0.5'}`} />
|
|
@@ -1287,35 +1290,25 @@ export function AgentSheet() {
|
|
|
1287
1290
|
if (next && provider === 'claude-cli') setProvider('anthropic')
|
|
1288
1291
|
}}
|
|
1289
1292
|
className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer
|
|
1290
|
-
${isOrchestrator ? 'bg-
|
|
1293
|
+
${isOrchestrator ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
|
|
1291
1294
|
>
|
|
1292
1295
|
<div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
|
|
1293
1296
|
${isOrchestrator ? 'left-[22px]' : 'left-0.5'}`} />
|
|
1294
1297
|
</div>
|
|
1295
|
-
<span className="font-display text-[14px] font-600 text-text-2">
|
|
1296
|
-
<span className="text-[12px] text-text-3">
|
|
1298
|
+
<span className="font-display text-[14px] font-600 text-text-2">Can Delegate to Other Agents</span>
|
|
1299
|
+
<span className="text-[12px] text-text-3">Route work to specialized agents and coordinate multi-agent tasks</span>
|
|
1297
1300
|
</label>
|
|
1298
1301
|
</div>
|
|
1299
1302
|
)}
|
|
1300
1303
|
|
|
1301
1304
|
{provider !== 'openclaw' && isOrchestrator && agentOptions.length > 0 && (
|
|
1302
1305
|
<div className="mb-8">
|
|
1303
|
-
<
|
|
1304
|
-
<
|
|
1305
|
-
{agentOptions
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
className={`px-3 py-2 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all border
|
|
1310
|
-
${subAgentIds.includes(a.id)
|
|
1311
|
-
? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
|
|
1312
|
-
: 'bg-surface border-white/[0.06] text-text-3 hover:text-text-2'}`}
|
|
1313
|
-
style={{ fontFamily: 'inherit' }}
|
|
1314
|
-
>
|
|
1315
|
-
{a.name}
|
|
1316
|
-
</button>
|
|
1317
|
-
))}
|
|
1318
|
-
</div>
|
|
1306
|
+
<SectionLabel>Available Agents</SectionLabel>
|
|
1307
|
+
<AgentPickerList
|
|
1308
|
+
agents={agentOptions}
|
|
1309
|
+
selected={subAgentIds}
|
|
1310
|
+
onSelect={(id) => toggleAgent(id)}
|
|
1311
|
+
/>
|
|
1319
1312
|
</div>
|
|
1320
1313
|
)}
|
|
1321
1314
|
|
|
@@ -1366,7 +1359,7 @@ export function AgentSheet() {
|
|
|
1366
1359
|
onClick={handleTestAndSave}
|
|
1367
1360
|
disabled={!name.trim() || providerNeedsKey || testStatus === 'testing' || saving || (!openclawEnabled && testStatus === 'pass')}
|
|
1368
1361
|
className={`flex-1 py-3.5 rounded-[14px] border-none text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-60 transition-all hover:brightness-110
|
|
1369
|
-
${testStatus === 'pass' ? 'bg-emerald-600 shadow-[0_4px_20px_rgba(16,185,129,0.25)]' : 'bg-
|
|
1362
|
+
${testStatus === 'pass' ? 'bg-emerald-600 shadow-[0_4px_20px_rgba(16,185,129,0.25)]' : 'bg-accent-bright shadow-[0_4px_20px_rgba(99,102,241,0.25)]'}`}
|
|
1370
1363
|
style={{ fontFamily: 'inherit' }}
|
|
1371
1364
|
>
|
|
1372
1365
|
{openclawEnabled
|
|
@@ -88,7 +88,14 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
if (checking) return
|
|
91
|
+
if (checking) return (
|
|
92
|
+
<div className="h-full flex items-center justify-center bg-bg">
|
|
93
|
+
<div
|
|
94
|
+
className="h-6 w-6 rounded-full border-2 border-white/[0.08] border-t-accent-bright"
|
|
95
|
+
style={{ animation: 'spin 0.8s linear infinite' }}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
92
99
|
|
|
93
100
|
return (
|
|
94
101
|
<div className="h-full flex flex-col items-center justify-center px-8 bg-bg relative overflow-hidden">
|
|
@@ -192,7 +199,7 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
192
199
|
<button
|
|
193
200
|
onClick={handleClaimKey}
|
|
194
201
|
disabled={loading}
|
|
195
|
-
className="px-12 py-4 rounded-[16px] border-none bg-
|
|
202
|
+
className="px-12 py-4 rounded-[16px] border-none bg-accent-bright text-white text-[16px] font-display font-600
|
|
196
203
|
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
197
204
|
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
198
205
|
>
|
|
@@ -233,7 +240,7 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
233
240
|
<button
|
|
234
241
|
type="submit"
|
|
235
242
|
disabled={!key.trim() || loading}
|
|
236
|
-
className="px-12 py-4 rounded-[16px] border-none bg-
|
|
243
|
+
className="px-12 py-4 rounded-[16px] border-none bg-accent-bright text-white text-[16px] font-display font-600
|
|
237
244
|
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
238
245
|
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
239
246
|
>
|
|
@@ -819,7 +819,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
|
|
|
819
819
|
<button
|
|
820
820
|
onClick={saveProviderAndContinue}
|
|
821
821
|
disabled={(requiresKey && !apiKey.trim()) || saving}
|
|
822
|
-
className="px-8 py-3.5 rounded-[14px] border-none bg-
|
|
822
|
+
className="px-8 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-display font-600
|
|
823
823
|
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
824
824
|
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
825
825
|
>
|
|
@@ -923,7 +923,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
|
|
|
923
923
|
<button
|
|
924
924
|
onClick={createStarterAgent}
|
|
925
925
|
disabled={!agentName.trim() || saving}
|
|
926
|
-
className="px-10 py-3.5 rounded-[14px] border-none bg-
|
|
926
|
+
className="px-10 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-display font-600
|
|
927
927
|
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
928
928
|
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
929
929
|
>
|
|
@@ -2,22 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
5
6
|
import { api } from '@/lib/api-client'
|
|
6
7
|
|
|
7
8
|
export function UserPicker() {
|
|
8
9
|
const setUser = useAppStore((s) => s.setUser)
|
|
10
|
+
const loadSettings = useAppStore((s) => s.loadSettings)
|
|
9
11
|
const [name, setName] = useState('')
|
|
12
|
+
const [avatarSeed, setAvatarSeed] = useState(() => Math.random().toString(36).slice(2, 10))
|
|
10
13
|
|
|
11
14
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
12
15
|
e.preventDefault()
|
|
13
16
|
const trimmed = name.trim()
|
|
14
17
|
if (!trimmed) return
|
|
15
18
|
const userName = trimmed.toLowerCase()
|
|
16
|
-
// Save server-side so it persists across devices
|
|
17
19
|
try {
|
|
18
|
-
await api('PUT', '/settings', { userName })
|
|
20
|
+
await api('PUT', '/settings', { userName, userAvatarSeed: avatarSeed.trim() || undefined })
|
|
19
21
|
} catch { /* still set locally */ }
|
|
20
22
|
setUser(userName)
|
|
23
|
+
loadSettings()
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
return (
|
|
@@ -71,10 +74,35 @@ export function UserPicker() {
|
|
|
71
74
|
focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
|
|
72
75
|
style={{ fontFamily: 'inherit' }}
|
|
73
76
|
/>
|
|
77
|
+
|
|
78
|
+
{/* Avatar picker */}
|
|
79
|
+
<div className="flex flex-col items-center gap-3">
|
|
80
|
+
<AgentAvatar seed={avatarSeed || null} name={name || '?'} size={64} />
|
|
81
|
+
<div className="flex items-center gap-2">
|
|
82
|
+
<input
|
|
83
|
+
type="text"
|
|
84
|
+
value={avatarSeed}
|
|
85
|
+
onChange={(e) => setAvatarSeed(e.target.value)}
|
|
86
|
+
placeholder="Avatar seed"
|
|
87
|
+
className="w-[160px] px-3 py-2 rounded-[10px] border border-white/[0.08] bg-surface
|
|
88
|
+
text-text text-[13px] text-center outline-none transition-all
|
|
89
|
+
focus:border-accent-bright/30"
|
|
90
|
+
/>
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
onClick={() => setAvatarSeed(Math.random().toString(36).slice(2, 10))}
|
|
94
|
+
className="px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-3 text-[12px] font-600
|
|
95
|
+
cursor-pointer transition-all hover:bg-white/[0.04] shrink-0"
|
|
96
|
+
>
|
|
97
|
+
Randomize
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
74
102
|
<button
|
|
75
103
|
type="submit"
|
|
76
104
|
disabled={!name.trim()}
|
|
77
|
-
className="px-12 py-4 rounded-[16px] border-none bg-
|
|
105
|
+
className="px-12 py-4 rounded-[16px] border-none bg-accent-bright text-white text-[16px] font-display font-600
|
|
78
106
|
cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
|
|
79
107
|
shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
|
|
80
108
|
style={{ fontFamily: 'inherit' }}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
const NOTABLE_TOOLS: Record<string, { label: string; color: string; icon: 'brain' | 'clipboard' | 'delegate' | 'search' | 'message' }> = {
|
|
6
|
+
memory: { label: 'Committed to memory', color: '#A855F7', icon: 'brain' },
|
|
7
|
+
memory_tool: { label: 'Committed to memory', color: '#A855F7', icon: 'brain' },
|
|
8
|
+
manage_tasks: { label: 'Created a task', color: '#EC4899', icon: 'clipboard' },
|
|
9
|
+
manage_schedules: { label: 'Scheduled something', color: '#EC4899', icon: 'clipboard' },
|
|
10
|
+
manage_agents: { label: 'Created an agent', color: '#EC4899', icon: 'clipboard' },
|
|
11
|
+
delegate_to_claude_code: { label: 'Delegated to Claude Code', color: '#38BDF8', icon: 'delegate' },
|
|
12
|
+
delegate_to_codex_cli: { label: 'Delegated to Codex', color: '#38BDF8', icon: 'delegate' },
|
|
13
|
+
delegate_to_opencode_cli: { label: 'Delegated to OpenCode', color: '#38BDF8', icon: 'delegate' },
|
|
14
|
+
web_search: { label: 'Searched the web', color: '#22C55E', icon: 'search' },
|
|
15
|
+
connector_message_tool: { label: 'Sent a message', color: '#F97316', icon: 'message' },
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function extractSnippet(toolName: string, toolInput: string): string | null {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(toolInput)
|
|
21
|
+
if ((toolName === 'memory' || toolName === 'memory_tool') && parsed.title) return parsed.title
|
|
22
|
+
if ((toolName === 'memory' || toolName === 'memory_tool') && parsed.key) return parsed.key
|
|
23
|
+
if (toolName === 'manage_tasks' && parsed.title) return parsed.title
|
|
24
|
+
if (toolName === 'manage_schedules' && parsed.name) return parsed.name
|
|
25
|
+
if (toolName === 'manage_agents' && parsed.name) return parsed.name
|
|
26
|
+
if (toolName.startsWith('delegate_to_') && parsed.task) return parsed.task
|
|
27
|
+
if (toolName === 'web_search' && parsed.query) return parsed.query
|
|
28
|
+
if (toolName === 'connector_message_tool' && parsed.to) return parsed.to
|
|
29
|
+
} catch { /* ignore parse errors */ }
|
|
30
|
+
return null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function MomentIcon({ icon, color }: { icon: string; color: string }) {
|
|
34
|
+
switch (icon) {
|
|
35
|
+
case 'brain':
|
|
36
|
+
return (
|
|
37
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round">
|
|
38
|
+
<path d="M12 2a7 7 0 0 1 7 7c0 2.38-1.19 4.47-3 5.74V17a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-2.26C6.19 13.47 5 11.38 5 9a7 7 0 0 1 7-7z" />
|
|
39
|
+
<line x1="10" y1="22" x2="14" y2="22" />
|
|
40
|
+
</svg>
|
|
41
|
+
)
|
|
42
|
+
case 'delegate':
|
|
43
|
+
return (
|
|
44
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round">
|
|
45
|
+
<path d="M7 17l9.2-9.2M17 17V7H7" />
|
|
46
|
+
</svg>
|
|
47
|
+
)
|
|
48
|
+
case 'search':
|
|
49
|
+
return (
|
|
50
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round">
|
|
51
|
+
<circle cx="11" cy="11" r="8" />
|
|
52
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
53
|
+
</svg>
|
|
54
|
+
)
|
|
55
|
+
case 'message':
|
|
56
|
+
return (
|
|
57
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round">
|
|
58
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
59
|
+
</svg>
|
|
60
|
+
)
|
|
61
|
+
default: // clipboard
|
|
62
|
+
return (
|
|
63
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round">
|
|
64
|
+
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
|
|
65
|
+
<rect x="8" y="2" width="8" height="4" rx="1" ry="1" />
|
|
66
|
+
</svg>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface Props {
|
|
72
|
+
toolName: string
|
|
73
|
+
toolInput: string
|
|
74
|
+
onDismiss: () => void
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function ActivityMoment({ toolName, toolInput, onDismiss }: Props) {
|
|
78
|
+
const config = NOTABLE_TOOLS[toolName]
|
|
79
|
+
const [phase, setPhase] = useState<'in' | 'out'>('in')
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const holdTimer = setTimeout(() => setPhase('out'), 2000)
|
|
83
|
+
const dismissTimer = setTimeout(onDismiss, 2500)
|
|
84
|
+
return () => {
|
|
85
|
+
clearTimeout(holdTimer)
|
|
86
|
+
clearTimeout(dismissTimer)
|
|
87
|
+
}
|
|
88
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
89
|
+
}, [])
|
|
90
|
+
|
|
91
|
+
if (!config) return null
|
|
92
|
+
|
|
93
|
+
const snippet = extractSnippet(toolName, toolInput)
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div
|
|
97
|
+
className="absolute bottom-full left-0 z-10 pointer-events-none mb-1.5"
|
|
98
|
+
style={{
|
|
99
|
+
animation: phase === 'in'
|
|
100
|
+
? 'activity-moment-in 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards'
|
|
101
|
+
: 'activity-moment-out 0.4s cubic-bezier(0.4, 0, 1, 1) forwards',
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<div
|
|
105
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] shadow-lg whitespace-nowrap"
|
|
106
|
+
style={{
|
|
107
|
+
background: `${config.color}18`,
|
|
108
|
+
border: `1px solid ${config.color}30`,
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<MomentIcon icon={config.icon} color={config.color} />
|
|
112
|
+
<span className="text-[10px] font-600" style={{ color: config.color }}>
|
|
113
|
+
{config.label}
|
|
114
|
+
</span>
|
|
115
|
+
{snippet && (
|
|
116
|
+
<span className="text-[10px] text-text-3/60 max-w-[120px] truncate">
|
|
117
|
+
{snippet}
|
|
118
|
+
</span>
|
|
119
|
+
)}
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function isNotableTool(name: string): boolean {
|
|
126
|
+
return name in NOTABLE_TOOLS
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const HEART_PATH = 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z'
|
|
130
|
+
|
|
131
|
+
export function HeartbeatMoment({ onDismiss }: { onDismiss: () => void }) {
|
|
132
|
+
const [phase, setPhase] = useState<'in' | 'out'>('in')
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
const holdTimer = setTimeout(() => setPhase('out'), 2000)
|
|
136
|
+
const dismissTimer = setTimeout(onDismiss, 2500)
|
|
137
|
+
return () => {
|
|
138
|
+
clearTimeout(holdTimer)
|
|
139
|
+
clearTimeout(dismissTimer)
|
|
140
|
+
}
|
|
141
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
142
|
+
}, [])
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<div
|
|
146
|
+
className="absolute bottom-full left-0 z-10 pointer-events-none mb-1.5"
|
|
147
|
+
style={{
|
|
148
|
+
animation: phase === 'in'
|
|
149
|
+
? 'activity-moment-in 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards'
|
|
150
|
+
: 'activity-moment-out 0.4s cubic-bezier(0.4, 0, 1, 1) forwards',
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
<div
|
|
154
|
+
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] shadow-lg whitespace-nowrap"
|
|
155
|
+
style={{
|
|
156
|
+
background: 'rgba(34,197,94,0.1)',
|
|
157
|
+
border: '1px solid rgba(34,197,94,0.2)',
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
160
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="#22c55e">
|
|
161
|
+
<path d={HEART_PATH} />
|
|
162
|
+
</svg>
|
|
163
|
+
<span className="text-[10px] font-600" style={{ color: '#22c55e' }}>
|
|
164
|
+
Heartbeat OK
|
|
165
|
+
</span>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getSessionConnector,
|
|
15
15
|
} from '@/components/shared/connector-platform-icon'
|
|
16
16
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
17
|
+
import { toast } from 'sonner'
|
|
17
18
|
|
|
18
19
|
function shortPath(p: string): string {
|
|
19
20
|
return (p || '').replace(/^\/Users\/\w+/, '~')
|
|
@@ -196,6 +197,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
196
197
|
await api('PUT', `/sessions/${session.id}`, { heartbeatEnabled: next })
|
|
197
198
|
await loadSessions()
|
|
198
199
|
}
|
|
200
|
+
toast.success(`Heartbeat ${next ? 'enabled' : 'disabled'}`)
|
|
199
201
|
} finally {
|
|
200
202
|
setHeartbeatSaving(false)
|
|
201
203
|
}
|
|
@@ -80,7 +80,7 @@ export function ChatToolToggles({ session }: Props) {
|
|
|
80
80
|
<div
|
|
81
81
|
onClick={() => toggleTool(tool.id)}
|
|
82
82
|
className={`w-8 h-[18px] rounded-full transition-all duration-200 relative cursor-pointer shrink-0
|
|
83
|
-
${enabled ? 'bg-
|
|
83
|
+
${enabled ? 'bg-accent-bright' : 'bg-white/[0.12]'}`}
|
|
84
84
|
>
|
|
85
85
|
<div className={`absolute top-[2px] w-[14px] h-[14px] rounded-full bg-white transition-all duration-200
|
|
86
86
|
${enabled ? 'left-[16px]' : 'left-[2px]'}`} />
|