@swarmclawai/swarmclaw 0.5.3 → 0.6.2
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 +53 -9
- package/bin/server-cmd.js +1 -0
- package/bin/swarmclaw.js +76 -16
- package/next.config.ts +11 -1
- package/package.json +5 -2
- package/scripts/postinstall.mjs +18 -0
- package/src/app/api/canvas/[sessionId]/route.ts +31 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +284 -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/connectors/[id]/route.ts +1 -0
- package/src/app/api/connectors/route.ts +2 -1
- package/src/app/api/credentials/route.ts +2 -3
- package/src/app/api/files/open/route.ts +43 -0
- 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 +53 -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/[id]/messages/route.ts +70 -2
- package/src/app/api/sessions/[id]/route.ts +4 -0
- 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/metrics/route.ts +101 -0
- package/src/app/api/tasks/route.ts +18 -2
- package/src/app/api/tts/route.ts +3 -2
- package/src/app/api/tts/stream/route.ts +3 -2
- package/src/app/api/uploads/[filename]/route.ts +19 -34
- package/src/app/api/uploads/route.ts +94 -0
- package/src/app/api/webhooks/[id]/route.ts +15 -1
- package/src/app/globals.css +63 -15
- package/src/app/page.tsx +142 -13
- package/src/cli/index.js +40 -1
- package/src/cli/index.test.js +30 -0
- package/src/cli/spec.js +42 -0
- package/src/components/agents/agent-avatar.tsx +57 -10
- package/src/components/agents/agent-card.tsx +50 -17
- package/src/components/agents/agent-chat-list.tsx +148 -12
- package/src/components/agents/agent-list.tsx +50 -19
- package/src/components/agents/agent-sheet.tsx +120 -65
- package/src/components/agents/inspector-panel.tsx +81 -6
- package/src/components/agents/openclaw-skills-panel.tsx +32 -3
- package/src/components/agents/personality-builder.tsx +42 -14
- package/src/components/agents/soul-library-picker.tsx +89 -0
- 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/canvas/canvas-panel.tsx +96 -0
- package/src/components/chat/activity-moment.tsx +173 -0
- package/src/components/chat/chat-area.tsx +46 -22
- package/src/components/chat/chat-header.tsx +457 -286
- package/src/components/chat/chat-preview-panel.tsx +1 -2
- package/src/components/chat/chat-tool-toggles.tsx +1 -1
- package/src/components/chat/delegation-banner.tsx +371 -0
- package/src/components/chat/file-path-chip.tsx +146 -0
- package/src/components/chat/heartbeat-history-panel.tsx +269 -0
- package/src/components/chat/markdown-utils.ts +9 -0
- package/src/components/chat/message-bubble.tsx +356 -315
- package/src/components/chat/message-list.tsx +230 -8
- package/src/components/chat/streaming-bubble.tsx +104 -47
- 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 +111 -73
- 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 +130 -0
- package/src/components/chatrooms/chatroom-message.tsx +432 -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-list.tsx +168 -90
- package/src/components/connectors/connector-sheet.tsx +95 -56
- package/src/components/home/home-view.tsx +501 -0
- package/src/components/input/chat-input.tsx +107 -43
- 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 +194 -97
- 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 +259 -126
- 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/settings/gateway-disconnect-overlay.tsx +80 -0
- 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/chatroom-picker-list.tsx +61 -0
- package/src/components/shared/check-icon.tsx +12 -0
- package/src/components/shared/confirm-dialog.tsx +1 -1
- package/src/components/shared/connector-platform-icon.tsx +51 -4
- 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/icon-button.tsx +16 -2
- 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 +31 -15
- 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-embedding.tsx +48 -13
- package/src/components/shared/settings/section-orchestrator.tsx +46 -15
- 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-storage.tsx +206 -0
- package/src/components/shared/settings/section-theme.tsx +95 -0
- package/src/components/shared/settings/section-user-preferences.tsx +57 -0
- package/src/components/shared/settings/section-voice.tsx +42 -21
- package/src/components/shared/settings/section-web-search.tsx +30 -6
- package/src/components/shared/settings/settings-page.tsx +182 -27
- package/src/components/shared/settings/settings-sheet.tsx +9 -73
- package/src/components/shared/settings/storage-browser.tsx +259 -0
- 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 +59 -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 +416 -74
- package/src/components/ui/hover-card.tsx +52 -0
- package/src/components/usage/metrics-dashboard.tsx +90 -6
- package/src/components/usage/usage-list.tsx +1 -1
- package/src/components/webhooks/webhook-sheet.tsx +1 -1
- package/src/hooks/use-continuous-speech.ts +10 -4
- package/src/hooks/use-view-router.ts +69 -19
- package/src/hooks/use-voice-conversation.ts +53 -10
- package/src/hooks/use-ws.ts +4 -2
- package/src/instrumentation.ts +15 -1
- package/src/lib/chat.ts +2 -0
- package/src/lib/memory.ts +3 -0
- package/src/lib/providers/anthropic.ts +13 -7
- package/src/lib/providers/index.ts +1 -0
- package/src/lib/providers/openai.ts +13 -7
- package/src/lib/server/chat-execution.ts +75 -15
- package/src/lib/server/chatroom-helpers.ts +146 -0
- package/src/lib/server/connectors/manager.ts +229 -7
- 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 +48 -6
- package/src/lib/server/heartbeat-wake.ts +110 -0
- package/src/lib/server/langgraph-checkpoint.ts +1 -0
- package/src/lib/server/main-agent-loop.ts +1 -1
- package/src/lib/server/memory-consolidation.ts +105 -0
- package/src/lib/server/memory-db.ts +183 -10
- package/src/lib/server/mime.ts +51 -0
- package/src/lib/server/openclaw-gateway.ts +9 -1
- package/src/lib/server/orchestrator-lg.ts +2 -0
- package/src/lib/server/orchestrator.ts +5 -2
- package/src/lib/server/playwright-proxy.mjs +2 -3
- package/src/lib/server/prompt-runtime-context.ts +53 -0
- package/src/lib/server/provider-health.ts +125 -0
- package/src/lib/server/queue.ts +56 -10
- package/src/lib/server/scheduler.ts +8 -0
- package/src/lib/server/session-run-manager.ts +4 -0
- package/src/lib/server/session-tools/canvas.ts +67 -0
- package/src/lib/server/session-tools/chatroom.ts +136 -0
- package/src/lib/server/session-tools/connector.ts +83 -9
- package/src/lib/server/session-tools/context-mgmt.ts +36 -18
- package/src/lib/server/session-tools/crud.ts +21 -0
- package/src/lib/server/session-tools/delegate.ts +68 -4
- package/src/lib/server/session-tools/git.ts +71 -0
- package/src/lib/server/session-tools/http.ts +57 -0
- package/src/lib/server/session-tools/index.ts +10 -0
- package/src/lib/server/session-tools/memory.ts +7 -1
- package/src/lib/server/session-tools/search-providers.ts +16 -8
- package/src/lib/server/session-tools/subagent.ts +106 -0
- package/src/lib/server/session-tools/web.ts +115 -4
- package/src/lib/server/storage.ts +53 -29
- package/src/lib/server/stream-agent-chat.ts +185 -57
- package/src/lib/server/system-events.ts +49 -0
- package/src/lib/server/task-mention.ts +41 -0
- package/src/lib/server/ws-hub.ts +11 -0
- package/src/lib/sessions.ts +10 -0
- package/src/lib/soul-library.ts +103 -0
- package/src/lib/soul-suggestions.ts +109 -0
- package/src/lib/task-dedupe.ts +26 -0
- package/src/lib/tasks.ts +4 -1
- package/src/lib/tool-definitions.ts +2 -0
- package/src/lib/tts.ts +2 -2
- package/src/lib/view-routes.ts +36 -1
- package/src/lib/ws-client.ts +14 -4
- package/src/stores/use-app-store.ts +41 -3
- package/src/stores/use-chat-store.ts +113 -5
- package/src/stores/use-chatroom-store.ts +276 -0
- package/src/types/index.ts +88 -4
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
getSessionConnector,
|
|
15
15
|
} from '@/components/shared/connector-platform-icon'
|
|
16
16
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
17
|
+
import { ModelCombobox } from '@/components/shared/model-combobox'
|
|
18
|
+
import { toast } from 'sonner'
|
|
19
|
+
import type { ProviderType } from '@/types'
|
|
17
20
|
|
|
18
21
|
function shortPath(p: string): string {
|
|
19
22
|
return (p || '').replace(/^\/Users\/\w+/, '~')
|
|
@@ -48,9 +51,11 @@ interface Props {
|
|
|
48
51
|
onVoiceToggle?: () => void
|
|
49
52
|
voiceActive?: boolean
|
|
50
53
|
voiceSupported?: boolean
|
|
54
|
+
heartbeatHistoryOpen?: boolean
|
|
55
|
+
onToggleHeartbeatHistory?: () => void
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, mobile, browserActive, onStopBrowser, onVoiceToggle, voiceActive, voiceSupported }: Props) {
|
|
58
|
+
export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, mobile, browserActive, onStopBrowser, onVoiceToggle, voiceActive, voiceSupported, heartbeatHistoryOpen, onToggleHeartbeatHistory }: Props) {
|
|
54
59
|
const ttsEnabled = useChatStore((s) => s.ttsEnabled)
|
|
55
60
|
const toggleTts = useChatStore((s) => s.toggleTts)
|
|
56
61
|
const soundEnabled = useChatStore((s) => s.soundEnabled)
|
|
@@ -71,12 +76,15 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
71
76
|
const setInspectorOpen = useAppStore((s) => s.setInspectorOpen)
|
|
72
77
|
const connectors = useAppStore((s) => s.connectors)
|
|
73
78
|
const loadConnectors = useAppStore((s) => s.loadConnectors)
|
|
74
|
-
const providerLabel = PROVIDER_LABELS[session.provider] || session.provider
|
|
75
79
|
const agent = session.agentId ? agents[session.agentId] : null
|
|
76
80
|
const connector = getSessionConnector(session, connectors)
|
|
77
81
|
const connectorMeta = connector ? CONNECTOR_PLATFORM_META[connector.platform] : null
|
|
78
82
|
const connectorPresence = connector?.presence
|
|
83
|
+
const providers = useAppStore((s) => s.providers)
|
|
84
|
+
const loadProviders = useAppStore((s) => s.loadProviders)
|
|
79
85
|
const modelName = session.model || agent?.model || ''
|
|
86
|
+
const [modelSwitcherOpen, setModelSwitcherOpen] = useState(false)
|
|
87
|
+
const modelSwitcherRef = useRef<HTMLDivElement>(null)
|
|
80
88
|
const [copied, setCopied] = useState(false)
|
|
81
89
|
const [heartbeatSaving, setHeartbeatSaving] = useState(false)
|
|
82
90
|
const [hbDropdownOpen, setHbDropdownOpen] = useState(false)
|
|
@@ -86,6 +94,12 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
86
94
|
const [mainLoopNotice, setMainLoopNotice] = useState('')
|
|
87
95
|
const [syncingHistory, setSyncingHistory] = useState(false)
|
|
88
96
|
const [syncResult, setSyncResult] = useState('')
|
|
97
|
+
const [renaming, setRenaming] = useState(false)
|
|
98
|
+
const [renameDraft, setRenameDraft] = useState('')
|
|
99
|
+
const [renameSaving, setRenameSaving] = useState(false)
|
|
100
|
+
const [renameError, setRenameError] = useState('')
|
|
101
|
+
const renameInputRef = useRef<HTMLInputElement>(null)
|
|
102
|
+
const renameContainerRef = useRef<HTMLSpanElement>(null)
|
|
89
103
|
|
|
90
104
|
// Find linked task for this session
|
|
91
105
|
const linkedTask = useMemo(() => {
|
|
@@ -127,6 +141,19 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
127
141
|
setTimeout(() => setCopied(false), 2000)
|
|
128
142
|
}
|
|
129
143
|
|
|
144
|
+
const handleDismissResumeHandle = async (e: React.MouseEvent) => {
|
|
145
|
+
e.stopPropagation()
|
|
146
|
+
try {
|
|
147
|
+
await api('PUT', `/sessions/${session.id}`, {
|
|
148
|
+
claudeSessionId: null,
|
|
149
|
+
codexThreadId: null,
|
|
150
|
+
opencodeSessionId: null,
|
|
151
|
+
delegateResumeIds: { claudeCode: null, codex: null, opencode: null },
|
|
152
|
+
})
|
|
153
|
+
await loadSessions()
|
|
154
|
+
} catch { /* best-effort */ }
|
|
155
|
+
}
|
|
156
|
+
|
|
130
157
|
const heartbeatSupported = (session.tools?.length ?? 0) > 0
|
|
131
158
|
const loopIsOngoing = appSettings.loopMode === 'ongoing'
|
|
132
159
|
const { heartbeatEnabled, heartbeatIntervalSec, heartbeatExplicitOptIn } = useMemo(() => {
|
|
@@ -196,6 +223,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
196
223
|
await api('PUT', `/sessions/${session.id}`, { heartbeatEnabled: next })
|
|
197
224
|
await loadSessions()
|
|
198
225
|
}
|
|
226
|
+
toast.success(`Heartbeat ${next ? 'enabled' : 'disabled'}`)
|
|
199
227
|
} finally {
|
|
200
228
|
setHeartbeatSaving(false)
|
|
201
229
|
}
|
|
@@ -314,6 +342,54 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
314
342
|
return () => clearTimeout(timer)
|
|
315
343
|
}, [syncResult])
|
|
316
344
|
|
|
345
|
+
const startRename = () => {
|
|
346
|
+
if (!agent) return
|
|
347
|
+
setRenameDraft(agent.name)
|
|
348
|
+
setRenameError('')
|
|
349
|
+
setRenaming(true)
|
|
350
|
+
requestAnimationFrame(() => {
|
|
351
|
+
renameInputRef.current?.focus()
|
|
352
|
+
renameInputRef.current?.select()
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const cancelRename = () => {
|
|
357
|
+
setRenaming(false)
|
|
358
|
+
setRenameDraft('')
|
|
359
|
+
setRenameError('')
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const commitRename = async () => {
|
|
363
|
+
if (!agent || renameSaving) return
|
|
364
|
+
const trimmed = renameDraft.trim()
|
|
365
|
+
if (!trimmed || trimmed === agent.name) {
|
|
366
|
+
cancelRename()
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
setRenameSaving(true)
|
|
370
|
+
setRenameError('')
|
|
371
|
+
try {
|
|
372
|
+
await api('PUT', `/agents/${agent.id}`, { name: trimmed })
|
|
373
|
+
await loadAgents()
|
|
374
|
+
setRenaming(false)
|
|
375
|
+
} catch (err: unknown) {
|
|
376
|
+
setRenameError(err instanceof Error ? err.message : 'Rename failed')
|
|
377
|
+
} finally {
|
|
378
|
+
setRenameSaving(false)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
useEffect(() => {
|
|
383
|
+
if (!renaming) return
|
|
384
|
+
const handler = (e: PointerEvent) => {
|
|
385
|
+
if (renameContainerRef.current && !renameContainerRef.current.contains(e.target as Node)) {
|
|
386
|
+
cancelRename()
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
document.addEventListener('pointerdown', handler, true)
|
|
390
|
+
return () => document.removeEventListener('pointerdown', handler, true)
|
|
391
|
+
}, [renaming])
|
|
392
|
+
|
|
317
393
|
useEffect(() => {
|
|
318
394
|
if (!hbDropdownOpen) return
|
|
319
395
|
const handler = (e: MouseEvent) => {
|
|
@@ -323,6 +399,28 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
323
399
|
return () => document.removeEventListener('mousedown', handler)
|
|
324
400
|
}, [hbDropdownOpen])
|
|
325
401
|
|
|
402
|
+
useEffect(() => {
|
|
403
|
+
if (!modelSwitcherOpen) return
|
|
404
|
+
const handler = (e: MouseEvent) => {
|
|
405
|
+
if (modelSwitcherRef.current && !modelSwitcherRef.current.contains(e.target as Node)) setModelSwitcherOpen(false)
|
|
406
|
+
}
|
|
407
|
+
document.addEventListener('mousedown', handler)
|
|
408
|
+
return () => document.removeEventListener('mousedown', handler)
|
|
409
|
+
}, [modelSwitcherOpen])
|
|
410
|
+
|
|
411
|
+
const handleModelSwitch = async (nextProvider: ProviderType, nextModel: string) => {
|
|
412
|
+
setModelSwitcherOpen(false)
|
|
413
|
+
try {
|
|
414
|
+
await api('PUT', `/sessions/${session.id}`, { provider: nextProvider, model: nextModel })
|
|
415
|
+
await loadSessions()
|
|
416
|
+
} catch (err: unknown) {
|
|
417
|
+
toast.error(err instanceof Error ? err.message : 'Failed to switch model')
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const currentProviderInfo = providers.find((p) => p.id === session.provider)
|
|
422
|
+
const currentModels = currentProviderInfo?.models || []
|
|
423
|
+
|
|
326
424
|
useEffect(() => {
|
|
327
425
|
if (session.name.startsWith('connector:')) {
|
|
328
426
|
void loadConnectors()
|
|
@@ -332,6 +430,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
332
430
|
useEffect(() => {
|
|
333
431
|
setMainLoopError('')
|
|
334
432
|
setMainLoopNotice('')
|
|
433
|
+
setModelSwitcherOpen(false)
|
|
335
434
|
}, [session.id])
|
|
336
435
|
|
|
337
436
|
useEffect(() => {
|
|
@@ -340,53 +439,122 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
340
439
|
return () => clearTimeout(timer)
|
|
341
440
|
}, [mainLoopNotice])
|
|
342
441
|
|
|
442
|
+
// Context bar shows for tools, mission controls, memories, task links, resume handles, browser
|
|
443
|
+
const hasToolToggles = ((agent?.tools?.length ?? 0) > 0) || ((session.tools?.length ?? 0) > 0)
|
|
444
|
+
const hasMemoryLink = !!(agent && session.tools?.includes('memory'))
|
|
445
|
+
const hasContextBar = !!(hasToolToggles || isMainSession || hasMemoryLink || linkedTask || resumeHandle || (isOpenClawAgent && openclawSessionKey) || browserActive)
|
|
446
|
+
|
|
343
447
|
return (
|
|
344
|
-
<header
|
|
345
|
-
|
|
346
|
-
|
|
448
|
+
<header
|
|
449
|
+
className="relative z-20 border-b border-white/[0.06] shrink-0"
|
|
450
|
+
style={{
|
|
451
|
+
background: 'linear-gradient(180deg, rgba(var(--rgb-bg, 15,15,26), 0.95) 0%, rgba(var(--rgb-bg, 15,15,26), 0.88) 100%)',
|
|
452
|
+
backdropFilter: 'blur(20px) saturate(1.4)',
|
|
453
|
+
WebkitBackdropFilter: 'blur(20px) saturate(1.4)',
|
|
454
|
+
...(mobile ? { paddingTop: 'max(12px, env(safe-area-inset-top))' } : {}),
|
|
455
|
+
}}
|
|
456
|
+
>
|
|
457
|
+
{/* Main row */}
|
|
458
|
+
<div className="flex items-center gap-2 px-3.5 py-1.5 min-h-[48px]">
|
|
459
|
+
{/* Back button */}
|
|
347
460
|
{onBack && (
|
|
348
|
-
<IconButton onClick={onBack} aria-label="Go back">
|
|
349
|
-
<svg width="
|
|
461
|
+
<IconButton onClick={onBack} aria-label="Go back" size="sm">
|
|
462
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
350
463
|
<polyline points="15 18 9 12 15 6" />
|
|
351
464
|
</svg>
|
|
352
465
|
</IconButton>
|
|
353
466
|
)}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
467
|
+
|
|
468
|
+
{/* Avatar */}
|
|
469
|
+
{agent && (
|
|
470
|
+
<div className="relative shrink-0">
|
|
471
|
+
{streaming && (
|
|
472
|
+
<div
|
|
473
|
+
className="absolute -inset-[3px] rounded-full opacity-40"
|
|
474
|
+
style={{
|
|
475
|
+
background: 'conic-gradient(from 0deg, var(--color-accent-bright), transparent 120deg, transparent 240deg, var(--color-accent-bright))',
|
|
476
|
+
animation: 'spin 2.5s linear infinite',
|
|
477
|
+
filter: 'blur(3px)',
|
|
478
|
+
}}
|
|
479
|
+
/>
|
|
480
|
+
)}
|
|
481
|
+
<div
|
|
482
|
+
className="relative rounded-full"
|
|
483
|
+
style={{
|
|
484
|
+
padding: 2,
|
|
485
|
+
background: streaming
|
|
486
|
+
? 'conic-gradient(from 0deg, var(--color-accent-bright), transparent 120deg, transparent 240deg, var(--color-accent-bright))'
|
|
487
|
+
: 'linear-gradient(135deg, rgba(255,255,255,0.10), rgba(255,255,255,0.03))',
|
|
488
|
+
animation: streaming ? 'spin 2.5s linear infinite' : undefined,
|
|
489
|
+
}}
|
|
490
|
+
>
|
|
491
|
+
<div className="rounded-full bg-bg">
|
|
492
|
+
<AgentAvatar seed={agent.avatarSeed} name={agent.name} size={hasContextBar ? 44 : 34} />
|
|
493
|
+
</div>
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
)}
|
|
497
|
+
|
|
498
|
+
{/* Identity + metadata — fills center */}
|
|
499
|
+
<div className="flex-1 min-w-0 flex items-center gap-3">
|
|
500
|
+
{/* Name + inline badges */}
|
|
501
|
+
<div className="flex items-center gap-2 min-w-0 shrink">
|
|
502
|
+
{renaming && agent ? (
|
|
503
|
+
<span ref={renameContainerRef} className="inline-flex items-center gap-2">
|
|
504
|
+
<input
|
|
505
|
+
ref={renameInputRef}
|
|
506
|
+
value={renameDraft}
|
|
507
|
+
onChange={(e) => setRenameDraft(e.target.value)}
|
|
508
|
+
onKeyDown={(e) => {
|
|
509
|
+
if (e.key === 'Enter') void commitRename()
|
|
510
|
+
if (e.key === 'Escape') cancelRename()
|
|
511
|
+
}}
|
|
512
|
+
disabled={renameSaving}
|
|
513
|
+
className="font-display text-[15px] font-700 tracking-[-0.02em] bg-transparent border-b border-accent-bright/40 outline-none text-text px-0 py-0 w-[180px]"
|
|
514
|
+
style={{ fontFamily: 'inherit' }}
|
|
515
|
+
/>
|
|
516
|
+
{renameSaving && <span className="w-3 h-3 rounded-full border-2 border-text-3/30 border-t-accent-bright animate-spin shrink-0" />}
|
|
517
|
+
{renameError && <span className="text-[10px] text-red-400 shrink-0">{renameError}</span>}
|
|
518
|
+
</span>
|
|
519
|
+
) : (
|
|
520
|
+
<span
|
|
521
|
+
className={`font-display text-[15px] font-700 truncate tracking-[-0.02em] text-text${agent ? ' cursor-pointer hover:text-accent-bright transition-colors duration-200' : ''}`}
|
|
522
|
+
onClick={agent ? startRename : undefined}
|
|
523
|
+
title={agent ? 'Click to rename' : undefined}
|
|
524
|
+
>{
|
|
525
|
+
session.name === '__main__' ? 'Main Chat'
|
|
526
|
+
: session.name.startsWith('agent-thread:') ? (agent?.name || session.name)
|
|
527
|
+
: session.name
|
|
528
|
+
}</span>
|
|
529
|
+
)}
|
|
362
530
|
{connector && connectorMeta && (
|
|
363
531
|
<span
|
|
364
|
-
className="
|
|
532
|
+
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-[5px] border text-[9px] font-700 uppercase tracking-wider shrink-0"
|
|
365
533
|
style={{
|
|
366
534
|
color: connectorMeta.color,
|
|
367
|
-
backgroundColor: `${connectorMeta.color}
|
|
368
|
-
borderColor: `${connectorMeta.color}
|
|
535
|
+
backgroundColor: `${connectorMeta.color}10`,
|
|
536
|
+
borderColor: `${connectorMeta.color}20`,
|
|
369
537
|
}}
|
|
370
538
|
title={`${connector.name} connector`}
|
|
371
539
|
>
|
|
372
|
-
<ConnectorPlatformIcon platform={connector.platform} size={
|
|
540
|
+
<ConnectorPlatformIcon platform={connector.platform} size={10} />
|
|
373
541
|
{connectorMeta.label}
|
|
374
542
|
</span>
|
|
375
543
|
)}
|
|
376
544
|
{connector && connectorPresence && (() => {
|
|
377
545
|
const lastAt = connectorPresence.lastMessageAt
|
|
378
546
|
if (!lastAt) return (
|
|
379
|
-
<span className="shrink-0 inline-flex items-center gap-1 text-[10px] text-text-3/
|
|
380
|
-
<span className="w-1.5 h-1.5 rounded-full bg-text-3/
|
|
381
|
-
|
|
547
|
+
<span className="shrink-0 inline-flex items-center gap-1 text-[10px] text-text-3/40">
|
|
548
|
+
<span className="w-1.5 h-1.5 rounded-full bg-text-3/30" />
|
|
549
|
+
Idle
|
|
382
550
|
</span>
|
|
383
551
|
)
|
|
384
552
|
const ago = Date.now() - lastAt
|
|
385
553
|
const isActive = ago < 5 * 60_000
|
|
386
554
|
const isRecent = ago < 30 * 60_000
|
|
387
|
-
const label = isActive ? 'Active' : isRecent ? `${Math.floor(ago / 60_000)}m ago` : '
|
|
388
|
-
const dotColor = isActive ? 'bg-emerald-400' : isRecent ? 'bg-amber-400' : 'bg-text-3/
|
|
389
|
-
const textColor = isActive ? 'text-emerald-400' : isRecent ? 'text-amber-300' : 'text-text-3/
|
|
555
|
+
const label = isActive ? 'Active' : isRecent ? `${Math.floor(ago / 60_000)}m ago` : 'Idle'
|
|
556
|
+
const dotColor = isActive ? 'bg-emerald-400' : isRecent ? 'bg-amber-400' : 'bg-text-3/30'
|
|
557
|
+
const textColor = isActive ? 'text-emerald-400' : isRecent ? 'text-amber-300' : 'text-text-3/40'
|
|
390
558
|
return (
|
|
391
559
|
<span className={`shrink-0 inline-flex items-center gap-1 text-[10px] ${textColor}`}>
|
|
392
560
|
<span className={`w-1.5 h-1.5 rounded-full ${dotColor}`} />
|
|
@@ -394,290 +562,305 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
394
562
|
</span>
|
|
395
563
|
)
|
|
396
564
|
})()}
|
|
397
|
-
{session.provider && session.provider !== 'claude-cli' && (
|
|
398
|
-
<span className="shrink-0 px-2.5 py-0.5 rounded-[7px] bg-accent-soft text-accent-bright text-[10px] font-700 uppercase tracking-wider">
|
|
399
|
-
{providerLabel}
|
|
400
|
-
</span>
|
|
401
|
-
)}
|
|
402
565
|
{agent?.isOrchestrator && (
|
|
403
|
-
<span className="
|
|
404
|
-
Orchestrator
|
|
405
|
-
</span>
|
|
566
|
+
<span className="px-1.5 py-0.5 rounded-[5px] bg-amber-500/10 text-amber-500 text-[9px] font-700 uppercase tracking-wider shrink-0">Orch</span>
|
|
406
567
|
)}
|
|
407
|
-
{session.tools?.length ? (
|
|
408
|
-
<span className="shrink-0 px-2.5 py-0.5 rounded-[7px] bg-emerald-500/10 text-emerald-400 text-[10px] font-700 uppercase tracking-wider">
|
|
409
|
-
Tools
|
|
410
|
-
</span>
|
|
411
|
-
) : null}
|
|
412
568
|
{streaming && (
|
|
413
569
|
<span className="shrink-0 w-2 h-2 rounded-full bg-accent-bright" style={{ animation: 'pulse 1.5s ease infinite' }} />
|
|
414
570
|
)}
|
|
415
571
|
</div>
|
|
416
|
-
|
|
417
|
-
|
|
572
|
+
|
|
573
|
+
{/* Metadata tray: model · usage · path · status */}
|
|
574
|
+
<div className="flex items-center gap-1.5 min-w-0 overflow-hidden">
|
|
575
|
+
<span className="text-text-3/10 text-[10px] select-none shrink-0">/</span>
|
|
418
576
|
{modelName && (
|
|
419
|
-
|
|
420
|
-
<
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
577
|
+
<div className="relative shrink-0" ref={modelSwitcherRef}>
|
|
578
|
+
<button
|
|
579
|
+
type="button"
|
|
580
|
+
onClick={() => {
|
|
581
|
+
if (streaming) return
|
|
582
|
+
setModelSwitcherOpen((o) => { if (!o) void loadProviders(); return !o })
|
|
583
|
+
}}
|
|
584
|
+
disabled={streaming}
|
|
585
|
+
className="inline-flex items-center gap-1 text-[11px] text-text-3/45 font-mono shrink-0 cursor-pointer bg-transparent border-none px-1 py-0.5 rounded-[5px] hover:bg-white/[0.04] hover:text-text-3/70 transition-colors disabled:cursor-default disabled:hover:text-text-3/45"
|
|
586
|
+
title="Switch model"
|
|
587
|
+
>
|
|
588
|
+
{modelName}
|
|
589
|
+
<svg width="7" height="7" viewBox="0 0 16 16" fill="none" className="shrink-0 opacity-30">
|
|
590
|
+
<path d="M4 6l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
591
|
+
</svg>
|
|
592
|
+
</button>
|
|
593
|
+
{modelSwitcherOpen && (
|
|
594
|
+
<div className="absolute z-50 top-full left-0 mt-2 w-[280px] rounded-[12px] border border-white/[0.08] bg-surface backdrop-blur-md shadow-xl p-3">
|
|
595
|
+
<div className="text-[10px] font-600 text-text-3/50 uppercase tracking-wider mb-2">Provider</div>
|
|
596
|
+
<div className="flex flex-wrap gap-1.5 mb-3">
|
|
597
|
+
{providers.map((p) => (
|
|
598
|
+
<button
|
|
599
|
+
key={p.id}
|
|
600
|
+
type="button"
|
|
601
|
+
onClick={() => { if (p.id !== session.provider) void handleModelSwitch(p.id, p.models[0] || '') }}
|
|
602
|
+
className={`px-2.5 py-1 rounded-[7px] text-[11px] font-600 border-none cursor-pointer transition-colors
|
|
603
|
+
${p.id === session.provider ? 'bg-accent-bright/15 text-accent-bright' : 'bg-white/[0.04] text-text-3 hover:bg-white/[0.08]'}`}
|
|
604
|
+
>
|
|
605
|
+
{PROVIDER_LABELS[p.id] || p.id}
|
|
606
|
+
</button>
|
|
607
|
+
))}
|
|
608
|
+
</div>
|
|
609
|
+
<div className="text-[10px] font-600 text-text-3/50 uppercase tracking-wider mb-2">Model</div>
|
|
610
|
+
<ModelCombobox
|
|
611
|
+
providerId={session.provider}
|
|
612
|
+
value={modelName}
|
|
613
|
+
onChange={(m) => void handleModelSwitch(session.provider, m)}
|
|
614
|
+
models={currentModels}
|
|
615
|
+
defaultModels={currentProviderInfo?.defaultModels}
|
|
616
|
+
className="px-2.5 py-1.5 rounded-[7px] text-[12px] font-mono bg-white/[0.04] hover:bg-white/[0.06] transition-colors"
|
|
434
617
|
/>
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
618
|
+
</div>
|
|
619
|
+
)}
|
|
620
|
+
</div>
|
|
438
621
|
)}
|
|
439
622
|
{lastUsage && !streaming && (
|
|
440
623
|
<>
|
|
441
|
-
<span className="text-[
|
|
624
|
+
<span className="text-text-3/10 text-[10px] select-none shrink-0">·</span>
|
|
442
625
|
<UsageBadge {...lastUsage} />
|
|
443
626
|
</>
|
|
444
627
|
)}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
<>
|
|
481
|
-
<span className="text-[10px] text-text-3/40">→</span>
|
|
482
|
-
<span className="text-[10px] text-text-3/50 font-mono truncate max-w-[200px]" title={liveStatus.nextAction}>
|
|
483
|
-
{liveStatus.nextAction}
|
|
628
|
+
<button
|
|
629
|
+
type="button"
|
|
630
|
+
onClick={() => { api('POST', '/files/open', { path: session.cwd }).catch(() => {}) }}
|
|
631
|
+
className="inline-flex items-center shrink-0 bg-transparent border-none p-0.5 rounded-[4px] cursor-pointer text-text-3/20 hover:text-text-3/50 hover:bg-white/[0.04] transition-colors"
|
|
632
|
+
title={shortPath(session.cwd)}
|
|
633
|
+
>
|
|
634
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
635
|
+
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
|
636
|
+
</svg>
|
|
637
|
+
</button>
|
|
638
|
+
{/* Live agent status */}
|
|
639
|
+
{(() => {
|
|
640
|
+
const liveStatus = agentStatus || (missionState.status ? {
|
|
641
|
+
goal: missionState.goal ?? undefined,
|
|
642
|
+
status: missionState.status ?? undefined,
|
|
643
|
+
summary: missionState.summary ?? undefined,
|
|
644
|
+
nextAction: missionState.nextAction ?? undefined,
|
|
645
|
+
} : null)
|
|
646
|
+
if (!liveStatus) return null
|
|
647
|
+
const statusColors: Record<string, string> = {
|
|
648
|
+
idle: 'bg-text-3/40', progress: 'bg-blue-500', blocked: 'bg-amber-400', ok: 'bg-emerald-400',
|
|
649
|
+
}
|
|
650
|
+
const dotColor = statusColors[liveStatus.status || ''] || 'bg-text-3/40'
|
|
651
|
+
return (
|
|
652
|
+
<>
|
|
653
|
+
<span className="text-text-3/10 text-[10px] select-none shrink-0">·</span>
|
|
654
|
+
{liveStatus.status && (
|
|
655
|
+
<span className={`inline-flex items-center gap-1 px-1.5 py-0.5 rounded-[4px] text-[9px] font-700 uppercase tracking-wider ${
|
|
656
|
+
liveStatus.status === 'blocked' ? 'bg-amber-400/12 text-amber-300'
|
|
657
|
+
: liveStatus.status === 'ok' ? 'bg-emerald-400/12 text-emerald-400'
|
|
658
|
+
: liveStatus.status === 'progress' ? 'bg-blue-500/12 text-blue-400'
|
|
659
|
+
: 'bg-white/[0.03] text-text-3/50'
|
|
660
|
+
}`}>
|
|
661
|
+
<span className={`w-1 h-1 rounded-full ${dotColor}`} />
|
|
662
|
+
{liveStatus.status}
|
|
484
663
|
</span>
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
664
|
+
)}
|
|
665
|
+
{liveStatus.goal && (
|
|
666
|
+
<span className="text-[10px] text-text-3/40 font-mono truncate max-w-[180px]" title={liveStatus.goal}>
|
|
667
|
+
{liveStatus.goal}
|
|
668
|
+
</span>
|
|
669
|
+
)}
|
|
670
|
+
{liveStatus.nextAction && (
|
|
671
|
+
<>
|
|
672
|
+
<span className="text-[9px] text-text-3/20 shrink-0">→</span>
|
|
673
|
+
<span className="text-[10px] text-text-3/35 font-mono truncate max-w-[140px]" title={liveStatus.nextAction}>
|
|
674
|
+
{liveStatus.nextAction}
|
|
675
|
+
</span>
|
|
676
|
+
</>
|
|
677
|
+
)}
|
|
678
|
+
</>
|
|
679
|
+
)
|
|
680
|
+
})()}
|
|
681
|
+
</div>
|
|
490
682
|
</div>
|
|
491
|
-
|
|
683
|
+
|
|
684
|
+
{/* Heartbeat compound control */}
|
|
685
|
+
{heartbeatSupported && (
|
|
686
|
+
<div className="flex items-center rounded-[8px] shrink-0" style={{ background: 'rgba(255,255,255,0.025)' }}>
|
|
687
|
+
<button
|
|
688
|
+
onClick={handleToggleHeartbeat}
|
|
689
|
+
disabled={heartbeatSaving}
|
|
690
|
+
className={`flex items-center gap-1.5 pl-2.5 pr-1.5 py-1 transition-colors cursor-pointer border-none text-[11px] font-600
|
|
691
|
+
${heartbeatWillRun ? 'text-emerald-400 hover:bg-emerald-500/10' : 'text-text-3/60 hover:bg-white/[0.04]'}`}
|
|
692
|
+
title={heartbeatWillRun ? 'Disable heartbeat' : 'Enable heartbeat'}
|
|
693
|
+
>
|
|
694
|
+
<span className={`w-1.5 h-1.5 rounded-full transition-colors ${heartbeatWillRun ? 'bg-emerald-400' : 'bg-text-3/30'}`} />
|
|
695
|
+
HB
|
|
696
|
+
{heartbeatEnabled && !loopIsOngoing && !heartbeatExplicitOptIn && (
|
|
697
|
+
<span className="text-[9px] text-text-3/40">(bounded)</span>
|
|
698
|
+
)}
|
|
699
|
+
</button>
|
|
700
|
+
<div className="relative" ref={hbDropdownRef}>
|
|
701
|
+
<button
|
|
702
|
+
onClick={() => setHbDropdownOpen((o) => !o)}
|
|
703
|
+
disabled={heartbeatSaving}
|
|
704
|
+
className="flex items-center gap-0.5 pl-1 pr-2 py-1 text-text-3/50 hover:text-text-3/70 hover:bg-white/[0.04] transition-colors cursor-pointer border-none"
|
|
705
|
+
title="Set heartbeat interval"
|
|
706
|
+
>
|
|
707
|
+
<span className="text-[11px] font-600">{formatDuration(heartbeatIntervalSec)}</span>
|
|
708
|
+
<svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="opacity-40">
|
|
709
|
+
<polyline points="6 9 12 15 18 9" />
|
|
710
|
+
</svg>
|
|
711
|
+
</button>
|
|
712
|
+
{hbDropdownOpen && (
|
|
713
|
+
<div className="absolute top-full right-0 mt-1 py-1 rounded-[10px] border border-white/[0.06] bg-bg/95 backdrop-blur-md shadow-lg z-50 min-w-[80px]">
|
|
714
|
+
{[1800, 3600, 7200, 21600, 43200].map((sec) => (
|
|
715
|
+
<button
|
|
716
|
+
key={sec}
|
|
717
|
+
onClick={() => handleSelectHeartbeatInterval(sec)}
|
|
718
|
+
className={`w-full text-left px-3 py-1.5 text-[11px] font-600 transition-colors cursor-pointer border-none
|
|
719
|
+
${sec === heartbeatIntervalSec ? 'bg-accent-soft text-accent-bright' : 'text-text-3 hover:bg-white/[0.06]'}`}
|
|
720
|
+
>
|
|
721
|
+
{formatDuration(sec)}
|
|
722
|
+
</button>
|
|
723
|
+
))}
|
|
724
|
+
</div>
|
|
725
|
+
)}
|
|
726
|
+
</div>
|
|
727
|
+
</div>
|
|
728
|
+
)}
|
|
729
|
+
|
|
730
|
+
{/* Action buttons */}
|
|
731
|
+
<div className="flex items-center shrink-0">
|
|
492
732
|
{streaming && (
|
|
493
|
-
|
|
494
|
-
<
|
|
495
|
-
<
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
502
|
-
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
|
|
503
|
-
<circle cx="12" cy="12" r="3" />
|
|
504
|
-
</svg>
|
|
505
|
-
</IconButton>
|
|
733
|
+
<>
|
|
734
|
+
<IconButton onClick={onStop} variant="danger" tooltip="Stop" aria-label="Stop generation" size="sm">
|
|
735
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
|
|
736
|
+
<rect x="6" y="6" width="12" height="12" rx="2" />
|
|
737
|
+
</svg>
|
|
738
|
+
</IconButton>
|
|
739
|
+
<div className="w-px h-3.5 bg-white/[0.06] mx-0.5" />
|
|
740
|
+
</>
|
|
506
741
|
)}
|
|
507
|
-
<IconButton onClick={
|
|
508
|
-
<svg width="
|
|
509
|
-
<path d="
|
|
510
|
-
<path d="
|
|
511
|
-
<path d="M6 20v-4" />
|
|
742
|
+
<IconButton onClick={toggleSound} active={soundEnabled} tooltip="Notifications" aria-label="Toggle sound" size="sm">
|
|
743
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
744
|
+
<path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" />
|
|
745
|
+
<path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" />
|
|
512
746
|
</svg>
|
|
513
747
|
</IconButton>
|
|
514
|
-
<IconButton onClick={
|
|
515
|
-
<svg width="
|
|
516
|
-
<path d="M18 8A6 6 0 0 1 18 16" />
|
|
517
|
-
<path d="M13 2L8 7H4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h4l5 5V2z" />
|
|
518
|
-
</svg>
|
|
519
|
-
</IconButton>
|
|
520
|
-
<IconButton onClick={toggleTts} active={ttsEnabled} aria-label="Toggle text-to-speech">
|
|
521
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
748
|
+
<IconButton onClick={toggleTts} active={ttsEnabled} tooltip="Read aloud" aria-label="Toggle TTS" size="sm">
|
|
749
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
522
750
|
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
|
|
523
751
|
<path d="M15.54 8.46a5 5 0 0 1 0 7.07" />
|
|
524
752
|
</svg>
|
|
525
753
|
</IconButton>
|
|
526
754
|
{voiceSupported && onVoiceToggle && (
|
|
527
|
-
<IconButton onClick={onVoiceToggle} active={voiceActive} aria-label="Toggle voice
|
|
528
|
-
<svg width="
|
|
755
|
+
<IconButton onClick={onVoiceToggle} active={voiceActive} tooltip="Voice mode" aria-label="Toggle voice" size="sm">
|
|
756
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
529
757
|
<path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" />
|
|
530
758
|
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
|
531
759
|
<line x1="12" x2="12" y1="19" y2="22" />
|
|
532
760
|
</svg>
|
|
533
761
|
</IconButton>
|
|
534
762
|
)}
|
|
535
|
-
|
|
536
|
-
<
|
|
537
|
-
<
|
|
538
|
-
|
|
539
|
-
|
|
763
|
+
{agent?.heartbeatEnabled && onToggleHeartbeatHistory && (
|
|
764
|
+
<IconButton onClick={onToggleHeartbeatHistory} active={heartbeatHistoryOpen} tooltip="Heartbeat history" aria-label="Toggle heartbeat history" size="sm">
|
|
765
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill={heartbeatHistoryOpen ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
766
|
+
<path d="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" />
|
|
767
|
+
</svg>
|
|
768
|
+
</IconButton>
|
|
769
|
+
)}
|
|
770
|
+
<div className="w-px h-3.5 bg-white/[0.06] mx-0.5" />
|
|
771
|
+
<IconButton onClick={() => setDebugOpen(!debugOpen)} active={debugOpen} tooltip="Debug" aria-label="Toggle debug panel" size="sm">
|
|
772
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
773
|
+
<path d="M12 20V10" /><path d="M18 20V4" /><path d="M6 20v-4" />
|
|
540
774
|
</svg>
|
|
541
775
|
</IconButton>
|
|
776
|
+
{(!agent || mobile) && (
|
|
777
|
+
<IconButton onClick={(e) => { e.stopPropagation(); onMenuToggle() }} tooltip="Menu" aria-label="Chat menu" size="sm">
|
|
778
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
779
|
+
<circle cx="12" cy="6" r="1" /><circle cx="12" cy="12" r="1" /><circle cx="12" cy="18" r="1" />
|
|
780
|
+
</svg>
|
|
781
|
+
</IconButton>
|
|
782
|
+
)}
|
|
783
|
+
{agent && (
|
|
784
|
+
<IconButton onClick={() => setInspectorOpen(!inspectorOpen)} active={inspectorOpen} tooltip="Settings" aria-label="Toggle inspector" size="sm">
|
|
785
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
786
|
+
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
|
|
787
|
+
<circle cx="12" cy="12" r="3" />
|
|
788
|
+
</svg>
|
|
789
|
+
</IconButton>
|
|
790
|
+
)}
|
|
542
791
|
</div>
|
|
543
792
|
</div>
|
|
544
793
|
|
|
545
|
-
{/*
|
|
546
|
-
{
|
|
547
|
-
<div className="flex items-center gap-
|
|
548
|
-
{
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
{heartbeatSupported && (
|
|
552
|
-
<>
|
|
553
|
-
<button
|
|
554
|
-
onClick={handleToggleHeartbeat}
|
|
555
|
-
disabled={heartbeatSaving}
|
|
556
|
-
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] transition-colors cursor-pointer border-none
|
|
557
|
-
${heartbeatWillRun ? 'bg-emerald-500/10 hover:bg-emerald-500/15 text-emerald-400' : 'bg-white/[0.04] hover:bg-white/[0.07] text-text-3'}`}
|
|
558
|
-
title={heartbeatWillRun ? 'Toggle heartbeat' : !heartbeatEnabled ? 'Heartbeat disabled — click to enable' : 'Heartbeat enabled but paused (bounded loop mode, no explicit opt-in)'}
|
|
559
|
-
>
|
|
560
|
-
<span className={`w-1.5 h-1.5 rounded-full ${heartbeatWillRun ? 'bg-emerald-400' : 'bg-text-3/40'}`} />
|
|
561
|
-
<span className="text-[11px] font-600">
|
|
562
|
-
HB {heartbeatWillRun ? 'On' : 'Off'}
|
|
563
|
-
</span>
|
|
564
|
-
{heartbeatEnabled && !loopIsOngoing && !heartbeatExplicitOptIn && (
|
|
565
|
-
<span className="text-[10px] text-text-3/50">(bounded)</span>
|
|
566
|
-
)}
|
|
567
|
-
</button>
|
|
568
|
-
<div className="relative" ref={hbDropdownRef}>
|
|
569
|
-
<button
|
|
570
|
-
onClick={() => setHbDropdownOpen((o) => !o)}
|
|
571
|
-
disabled={heartbeatSaving}
|
|
572
|
-
className="flex items-center gap-1 px-2.5 py-1 rounded-[8px] bg-white/[0.04] hover:bg-white/[0.07] text-text-3 transition-colors cursor-pointer border-none"
|
|
573
|
-
title="Set heartbeat interval"
|
|
574
|
-
>
|
|
575
|
-
<span className="text-[11px] font-600">{formatDuration(heartbeatIntervalSec)}</span>
|
|
576
|
-
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-text-3/50">
|
|
577
|
-
<polyline points="6 9 12 15 18 9" />
|
|
578
|
-
</svg>
|
|
579
|
-
</button>
|
|
580
|
-
{hbDropdownOpen && (
|
|
581
|
-
<div className="absolute top-full left-0 mt-1 py-1 rounded-[10px] border border-white/[0.06] bg-bg/95 backdrop-blur-md shadow-lg z-50 min-w-[80px]">
|
|
582
|
-
{[30, 60, 120, 300, 600, 1800, 3600].map((sec) => (
|
|
583
|
-
<button
|
|
584
|
-
key={sec}
|
|
585
|
-
onClick={() => handleSelectHeartbeatInterval(sec)}
|
|
586
|
-
className={`w-full text-left px-3 py-1.5 text-[11px] font-600 transition-colors cursor-pointer border-none
|
|
587
|
-
${sec === heartbeatIntervalSec ? 'bg-accent-soft text-accent-bright' : 'text-text-3 hover:bg-white/[0.06]'}`}
|
|
588
|
-
>
|
|
589
|
-
{formatDuration(sec)}
|
|
590
|
-
</button>
|
|
591
|
-
))}
|
|
592
|
-
</div>
|
|
593
|
-
)}
|
|
594
|
-
</div>
|
|
595
|
-
</>
|
|
794
|
+
{/* Context bar: tools, mission controls, links */}
|
|
795
|
+
{hasContextBar && (
|
|
796
|
+
<div className="flex items-center gap-1.5 px-3.5 pb-1.5 overflow-x-auto scrollbar-none">
|
|
797
|
+
{hasToolToggles && <ChatToolToggles session={session} />}
|
|
798
|
+
{hasToolToggles && (hasMemoryLink || isMainSession || linkedTask || resumeHandle || isOpenClawAgent || browserActive) && (
|
|
799
|
+
<div className="w-px h-4 bg-white/[0.05] shrink-0" />
|
|
596
800
|
)}
|
|
597
801
|
{isMainSession && (
|
|
598
802
|
<>
|
|
599
803
|
<button
|
|
600
804
|
onClick={handleToggleMissionPause}
|
|
601
805
|
disabled={mainLoopSaving}
|
|
602
|
-
className={`flex items-center gap-1.5 px-2
|
|
603
|
-
${missionPaused ? 'bg-amber-500/
|
|
604
|
-
title={missionPaused ? 'Resume
|
|
806
|
+
className={`flex items-center gap-1.5 px-2 py-1 rounded-[7px] transition-colors cursor-pointer border-none text-[10px] font-600
|
|
807
|
+
${missionPaused ? 'bg-amber-500/10 hover:bg-amber-500/18 text-amber-300' : 'bg-emerald-500/8 hover:bg-emerald-500/12 text-emerald-400'}`}
|
|
808
|
+
title={missionPaused ? 'Resume mission' : 'Pause mission'}
|
|
605
809
|
>
|
|
606
810
|
<span className={`w-1.5 h-1.5 rounded-full ${missionPaused ? 'bg-amber-300' : 'bg-emerald-400'}`} />
|
|
607
|
-
|
|
608
|
-
Mission {missionPaused ? 'Paused' : 'Live'}
|
|
609
|
-
</span>
|
|
811
|
+
{missionPaused ? 'Paused' : 'Live'}
|
|
610
812
|
</button>
|
|
611
813
|
<button
|
|
612
814
|
onClick={handleToggleMissionMode}
|
|
613
815
|
disabled={mainLoopSaving}
|
|
614
|
-
className={`flex items-center gap-1
|
|
615
|
-
${missionMode === 'autonomous'
|
|
616
|
-
|
|
617
|
-
: 'bg-white/[0.04] hover:bg-white/[0.07] text-text-3'
|
|
618
|
-
}`}
|
|
619
|
-
title="Toggle mission autonomy mode"
|
|
816
|
+
className={`flex items-center gap-1 px-2 py-1 rounded-[7px] transition-colors cursor-pointer border-none text-[10px] font-600
|
|
817
|
+
${missionMode === 'autonomous' ? 'bg-indigo-500/12 hover:bg-indigo-500/20 text-indigo-300' : 'bg-white/[0.03] hover:bg-white/[0.06] text-text-3/60'}`}
|
|
818
|
+
title="Toggle autonomy mode"
|
|
620
819
|
>
|
|
621
|
-
|
|
622
|
-
Mode {missionMode === 'autonomous' ? 'Auto' : 'Assist'}
|
|
623
|
-
</span>
|
|
820
|
+
{missionMode === 'autonomous' ? 'Auto' : 'Assist'}
|
|
624
821
|
</button>
|
|
625
822
|
<button
|
|
626
823
|
onClick={handleNudgeMission}
|
|
627
824
|
disabled={mainLoopSaving || missionPaused}
|
|
628
|
-
className="
|
|
629
|
-
title="Run one
|
|
825
|
+
className="px-2 py-1 rounded-[7px] bg-blue-500/8 hover:bg-blue-500/15 text-blue-400 transition-colors cursor-pointer border-none disabled:opacity-50 text-[10px] font-600"
|
|
826
|
+
title="Run one tick"
|
|
630
827
|
>
|
|
631
|
-
|
|
828
|
+
Nudge
|
|
632
829
|
</button>
|
|
633
830
|
<button
|
|
634
831
|
onClick={handleSetMissionGoal}
|
|
635
832
|
disabled={mainLoopSaving}
|
|
636
|
-
className="
|
|
637
|
-
title="Set
|
|
833
|
+
className="px-2 py-1 rounded-[7px] bg-fuchsia-500/8 hover:bg-fuchsia-500/15 text-fuchsia-300 transition-colors cursor-pointer border-none text-[10px] font-600"
|
|
834
|
+
title="Set mission goal"
|
|
638
835
|
>
|
|
639
|
-
|
|
836
|
+
Goal
|
|
640
837
|
</button>
|
|
641
838
|
{missionEventsCount > 0 && (
|
|
642
839
|
<button
|
|
643
840
|
onClick={handleClearMissionEvents}
|
|
644
841
|
disabled={mainLoopSaving}
|
|
645
|
-
className="
|
|
646
|
-
title="Clear pending
|
|
842
|
+
className="px-2 py-1 rounded-[7px] bg-white/[0.03] hover:bg-white/[0.06] text-text-3/60 transition-colors cursor-pointer border-none text-[10px] font-600"
|
|
843
|
+
title="Clear pending events"
|
|
647
844
|
>
|
|
648
|
-
|
|
845
|
+
Events {missionEventsCount}
|
|
649
846
|
</button>
|
|
650
847
|
)}
|
|
651
|
-
<span className="text-[
|
|
652
|
-
{
|
|
848
|
+
<span className="text-[9px] text-text-3/40 uppercase tracking-wider shrink-0">
|
|
849
|
+
{missionStatus}{missionMomentum !== null ? ` · ${missionMomentum}` : ''}
|
|
653
850
|
</span>
|
|
654
|
-
{mainLoopError &&
|
|
655
|
-
|
|
656
|
-
{mainLoopError}
|
|
657
|
-
</span>
|
|
658
|
-
)}
|
|
659
|
-
{mainLoopNotice && (
|
|
660
|
-
<span className="text-[10px] text-emerald-300/90 truncate max-w-[220px]" title={mainLoopNotice}>
|
|
661
|
-
{mainLoopNotice}
|
|
662
|
-
</span>
|
|
663
|
-
)}
|
|
851
|
+
{mainLoopError && <span className="text-[9px] text-red-300/80 truncate max-w-[240px]" title={mainLoopError}>{mainLoopError}</span>}
|
|
852
|
+
{mainLoopNotice && <span className="text-[9px] text-emerald-300/80 truncate max-w-[200px]" title={mainLoopNotice}>{mainLoopNotice}</span>}
|
|
664
853
|
</>
|
|
665
854
|
)}
|
|
666
|
-
{
|
|
855
|
+
{hasMemoryLink && (
|
|
667
856
|
<button
|
|
668
|
-
onClick={() => {
|
|
669
|
-
|
|
670
|
-
setActiveView('memory')
|
|
671
|
-
setSidebarOpen(true)
|
|
672
|
-
}}
|
|
673
|
-
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-accent-soft/50 hover:bg-accent-soft transition-colors cursor-pointer"
|
|
857
|
+
onClick={() => { setMemoryAgentFilter(session.agentId!); setActiveView('memory'); setSidebarOpen(true) }}
|
|
858
|
+
className="flex items-center gap-1 px-2 py-1 rounded-[7px] bg-accent-soft/40 hover:bg-accent-soft/70 transition-colors cursor-pointer text-[10px] font-600 text-accent-bright/55 hover:text-accent-bright/80 shrink-0"
|
|
674
859
|
>
|
|
675
|
-
<svg width="
|
|
860
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
676
861
|
<ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" /><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
|
677
862
|
</svg>
|
|
678
|
-
|
|
679
|
-
{agent.name} Memories
|
|
680
|
-
</span>
|
|
863
|
+
Memories
|
|
681
864
|
</button>
|
|
682
865
|
)}
|
|
683
866
|
{isOpenClawAgent && openclawSessionKey && (
|
|
@@ -685,78 +868,66 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
685
868
|
<button
|
|
686
869
|
onClick={handleSyncHistory}
|
|
687
870
|
disabled={syncingHistory}
|
|
688
|
-
className="flex items-center gap-1
|
|
689
|
-
title="Sync
|
|
871
|
+
className="flex items-center gap-1 px-2 py-1 rounded-[7px] bg-indigo-500/8 hover:bg-indigo-500/12 transition-colors cursor-pointer border-none disabled:opacity-50 text-[10px] font-600 text-indigo-400 shrink-0"
|
|
872
|
+
title="Sync from gateway"
|
|
690
873
|
>
|
|
691
|
-
<svg width="
|
|
692
|
-
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" />
|
|
693
|
-
<path d="M3
|
|
694
|
-
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" />
|
|
695
|
-
<path d="M16 16h5v5" />
|
|
874
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
875
|
+
<path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path d="M3 3v5h5" />
|
|
876
|
+
<path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" /><path d="M16 16h5v5" />
|
|
696
877
|
</svg>
|
|
697
|
-
|
|
698
|
-
{syncingHistory ? 'Syncing...' : 'Sync History'}
|
|
699
|
-
</span>
|
|
878
|
+
{syncingHistory ? 'Syncing...' : 'Sync'}
|
|
700
879
|
</button>
|
|
701
|
-
{syncResult &&
|
|
702
|
-
<span className="text-[10px] text-emerald-300/90">{syncResult}</span>
|
|
703
|
-
)}
|
|
880
|
+
{syncResult && <span className="text-[9px] text-emerald-300/80 shrink-0">{syncResult}</span>}
|
|
704
881
|
</>
|
|
705
882
|
)}
|
|
706
883
|
{linkedTask && (
|
|
707
884
|
<button
|
|
708
885
|
onClick={() => setActiveView('tasks')}
|
|
709
|
-
className="flex items-center gap-1
|
|
886
|
+
className="flex items-center gap-1 px-2 py-1 rounded-[7px] bg-amber-500/8 hover:bg-amber-500/12 transition-colors cursor-pointer text-[10px] font-600 text-amber-500 shrink-0"
|
|
710
887
|
>
|
|
711
|
-
<svg width="
|
|
712
|
-
<path d="M9 11l3 3L22 4" />
|
|
713
|
-
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
|
|
888
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
889
|
+
<path d="M9 11l3 3L22 4" /><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
|
|
714
890
|
</svg>
|
|
715
|
-
<span className="
|
|
716
|
-
Task: {linkedTask.title}
|
|
717
|
-
</span>
|
|
891
|
+
<span className="truncate max-w-[160px]">{linkedTask.title}</span>
|
|
718
892
|
</button>
|
|
719
893
|
)}
|
|
720
894
|
{resumeHandle && (
|
|
721
|
-
<
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
<
|
|
728
|
-
|
|
729
|
-
<path d="M4 17l10 -10" />
|
|
730
|
-
</svg>
|
|
731
|
-
<span className="text-[11px] font-mono text-text-3/50 group-hover:text-text-3/70 truncate max-w-[220px]">
|
|
732
|
-
{copied ? 'Copied!' : `${resumeHandle.label}: ${resumeHandle.id}`}
|
|
733
|
-
</span>
|
|
734
|
-
{!copied && (
|
|
735
|
-
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/60 shrink-0">
|
|
736
|
-
<rect x="9" y="9" width="13" height="13" rx="2" />
|
|
737
|
-
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
895
|
+
<div className="flex items-center rounded-[7px] bg-white/[0.03] group/resume shrink-0">
|
|
896
|
+
<button
|
|
897
|
+
onClick={handleCopySessionId}
|
|
898
|
+
className="flex items-center gap-1 px-2 py-1 rounded-l-[7px] hover:bg-white/[0.06] transition-colors cursor-pointer"
|
|
899
|
+
title="Copy resume command"
|
|
900
|
+
>
|
|
901
|
+
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/40 shrink-0">
|
|
902
|
+
<path d="M4 17l6 0l0 -6" /><path d="M20 7l-6 0l0 6" /><path d="M4 17l10 -10" />
|
|
738
903
|
</svg>
|
|
739
|
-
|
|
740
|
-
|
|
904
|
+
<span className="text-[10px] font-mono text-text-3/40 group-hover/resume:text-text-3/60 truncate max-w-[180px]">
|
|
905
|
+
{copied ? 'Copied!' : `${resumeHandle.label}: ${resumeHandle.id}`}
|
|
906
|
+
</span>
|
|
907
|
+
</button>
|
|
908
|
+
<button
|
|
909
|
+
onClick={handleDismissResumeHandle}
|
|
910
|
+
className="px-1 py-1 rounded-r-[7px] hover:bg-white/[0.06] transition-colors cursor-pointer opacity-0 group-hover/resume:opacity-100"
|
|
911
|
+
title="Dismiss"
|
|
912
|
+
>
|
|
913
|
+
<svg width="8" height="8" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3/40 hover:text-text-3">
|
|
914
|
+
<path d="M4 4l8 8M12 4l-8 8" />
|
|
915
|
+
</svg>
|
|
916
|
+
</button>
|
|
917
|
+
</div>
|
|
741
918
|
)}
|
|
742
919
|
{browserActive && (
|
|
743
920
|
<button
|
|
744
921
|
onClick={onStopBrowser}
|
|
745
|
-
className="flex items-center gap-1
|
|
922
|
+
className="flex items-center gap-1 px-2 py-1 rounded-[7px] bg-accent-bright/8 hover:bg-red-500/12 transition-colors cursor-pointer group text-[10px] font-600 shrink-0"
|
|
746
923
|
title="Stop browser"
|
|
747
924
|
>
|
|
748
|
-
<svg width="
|
|
749
|
-
<rect x="3" y="3" width="18" height="14" rx="2" />
|
|
750
|
-
<path d="M3 9h18" />
|
|
751
|
-
<circle cx="7" cy="6" r="0.5" fill="currentColor" />
|
|
752
|
-
<circle cx="10" cy="6" r="0.5" fill="currentColor" />
|
|
925
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-accent-bright group-hover:text-red-400">
|
|
926
|
+
<rect x="3" y="3" width="18" height="14" rx="2" /><path d="M3 9h18" />
|
|
753
927
|
</svg>
|
|
754
|
-
<span className="text-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-text-3/60 group-hover:text-[#F43F5E] shrink-0">
|
|
758
|
-
<line x1="18" y1="6" x2="6" y2="18" />
|
|
759
|
-
<line x1="6" y1="6" x2="18" y2="18" />
|
|
928
|
+
<span className="text-accent-bright group-hover:text-red-400">Browser</span>
|
|
929
|
+
<svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-text-3/40 group-hover:text-red-400">
|
|
930
|
+
<line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" />
|
|
760
931
|
</svg>
|
|
761
932
|
</button>
|
|
762
933
|
)}
|