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