@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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from 'react'
|
|
4
|
+
import { useWs } from '@/hooks/use-ws'
|
|
5
|
+
import { api } from '@/lib/api-client'
|
|
6
|
+
|
|
7
|
+
interface CanvasPanelProps {
|
|
8
|
+
sessionId: string
|
|
9
|
+
agentName?: string
|
|
10
|
+
onClose: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function CanvasPanel({ sessionId, agentName, onClose }: CanvasPanelProps) {
|
|
14
|
+
const [content, setContent] = useState<string | null>(null)
|
|
15
|
+
|
|
16
|
+
const loadCanvas = useCallback(async () => {
|
|
17
|
+
try {
|
|
18
|
+
const res = await api<{ content: string | null }>('GET', `/canvas/${sessionId}`)
|
|
19
|
+
setContent(res.content)
|
|
20
|
+
} catch { /* ignore */ }
|
|
21
|
+
}, [sessionId])
|
|
22
|
+
|
|
23
|
+
useEffect(() => { loadCanvas() }, [loadCanvas]) // eslint-disable-line react-hooks/set-state-in-effect
|
|
24
|
+
useWs(`canvas:${sessionId}`, loadCanvas, 10_000)
|
|
25
|
+
|
|
26
|
+
if (!content) return (
|
|
27
|
+
<div className="flex flex-col h-full border-l border-white/[0.06] bg-bg min-w-[400px]">
|
|
28
|
+
<div className="flex items-center gap-3 px-4 py-3 border-b border-white/[0.06] shrink-0">
|
|
29
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright shrink-0">
|
|
30
|
+
<rect x="2" y="3" width="20" height="14" rx="2" /><path d="M8 21h8" /><path d="M12 17v4" />
|
|
31
|
+
</svg>
|
|
32
|
+
<span className="text-[13px] font-600 text-text flex-1 truncate">
|
|
33
|
+
Canvas{agentName ? ` — ${agentName}` : ''}
|
|
34
|
+
</span>
|
|
35
|
+
<button
|
|
36
|
+
onClick={onClose}
|
|
37
|
+
className="p-1.5 rounded-[6px] hover:bg-white/[0.06] transition-colors cursor-pointer border-none bg-transparent text-text-3 hover:text-text-2"
|
|
38
|
+
title="Close canvas"
|
|
39
|
+
aria-label="Close canvas"
|
|
40
|
+
>
|
|
41
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
42
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
43
|
+
</svg>
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
<div className="flex-1 flex items-center justify-center">
|
|
47
|
+
<div className="text-center">
|
|
48
|
+
<div className="w-8 h-8 rounded-full border-2 border-text-3/20 border-t-accent-bright animate-spin mx-auto mb-3" />
|
|
49
|
+
<span className="text-[13px] text-text-3">Loading canvas...</span>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="flex flex-col h-full border-l border-white/[0.06] bg-bg min-w-[400px]">
|
|
57
|
+
{/* Toolbar */}
|
|
58
|
+
<div className="flex items-center gap-3 px-4 py-3 border-b border-white/[0.06] shrink-0">
|
|
59
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright shrink-0">
|
|
60
|
+
<rect x="2" y="3" width="20" height="14" rx="2" /><path d="M8 21h8" /><path d="M12 17v4" />
|
|
61
|
+
</svg>
|
|
62
|
+
<span className="text-[13px] font-600 text-text flex-1 truncate">
|
|
63
|
+
Canvas{agentName ? ` — ${agentName}` : ''}
|
|
64
|
+
</span>
|
|
65
|
+
<button
|
|
66
|
+
onClick={loadCanvas}
|
|
67
|
+
className="p-1.5 rounded-[6px] hover:bg-white/[0.06] transition-colors cursor-pointer border-none bg-transparent text-text-3 hover:text-text-2"
|
|
68
|
+
title="Refresh"
|
|
69
|
+
>
|
|
70
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
71
|
+
<polyline points="23 4 23 10 17 10" /><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
|
|
72
|
+
</svg>
|
|
73
|
+
</button>
|
|
74
|
+
<button
|
|
75
|
+
onClick={onClose}
|
|
76
|
+
className="p-1.5 rounded-[6px] hover:bg-white/[0.06] transition-colors cursor-pointer border-none bg-transparent text-text-3 hover:text-text-2"
|
|
77
|
+
title="Close canvas"
|
|
78
|
+
>
|
|
79
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
80
|
+
<path d="M18 6L6 18M6 6l12 12" />
|
|
81
|
+
</svg>
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{/* Sandboxed iframe */}
|
|
86
|
+
<div className="flex-1 overflow-hidden">
|
|
87
|
+
<iframe
|
|
88
|
+
sandbox="allow-scripts allow-same-origin"
|
|
89
|
+
srcDoc={content}
|
|
90
|
+
className="w-full h-full border-none bg-white"
|
|
91
|
+
title="Agent Canvas"
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
@@ -11,6 +11,8 @@ const NOTABLE_TOOLS: Record<string, { label: string; color: string; icon: 'brain
|
|
|
11
11
|
delegate_to_claude_code: { label: 'Delegated to Claude Code', color: '#38BDF8', icon: 'delegate' },
|
|
12
12
|
delegate_to_codex_cli: { label: 'Delegated to Codex', color: '#38BDF8', icon: 'delegate' },
|
|
13
13
|
delegate_to_opencode_cli: { label: 'Delegated to OpenCode', color: '#38BDF8', icon: 'delegate' },
|
|
14
|
+
delegate_to_agent: { label: 'Delegating task', color: '#6366F1', icon: 'delegate' },
|
|
15
|
+
check_delegation_status: { label: 'Checking delegation', color: '#6366F1', icon: 'delegate' },
|
|
14
16
|
web_search: { label: 'Searched the web', color: '#22C55E', icon: 'search' },
|
|
15
17
|
connector_message_tool: { label: 'Sent a message', color: '#F97316', icon: 'message' },
|
|
16
18
|
}
|
|
@@ -23,6 +25,8 @@ function extractSnippet(toolName: string, toolInput: string): string | null {
|
|
|
23
25
|
if (toolName === 'manage_tasks' && parsed.title) return parsed.title
|
|
24
26
|
if (toolName === 'manage_schedules' && parsed.name) return parsed.name
|
|
25
27
|
if (toolName === 'manage_agents' && parsed.name) return parsed.name
|
|
28
|
+
if (toolName === 'delegate_to_agent' && (parsed.agentName || parsed.agentId)) return parsed.agentName || parsed.agentId
|
|
29
|
+
if (toolName === 'check_delegation_status' && parsed.agentName) return parsed.agentName
|
|
26
30
|
if (toolName.startsWith('delegate_to_') && parsed.task) return parsed.task
|
|
27
31
|
if (toolName === 'web_search' && parsed.query) return parsed.query
|
|
28
32
|
if (toolName === 'connector_message_tool' && parsed.to) return parsed.to
|
|
@@ -104,8 +108,8 @@ export function ActivityMoment({ toolName, toolInput, onDismiss }: Props) {
|
|
|
104
108
|
<div
|
|
105
109
|
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] shadow-lg whitespace-nowrap"
|
|
106
110
|
style={{
|
|
107
|
-
background:
|
|
108
|
-
border: `1px solid ${config.color}
|
|
111
|
+
background: 'var(--card)',
|
|
112
|
+
border: `1px solid ${config.color}40`,
|
|
109
113
|
}}
|
|
110
114
|
>
|
|
111
115
|
<MomentIcon icon={config.icon} color={config.color} />
|
|
@@ -153,8 +157,8 @@ export function HeartbeatMoment({ onDismiss }: { onDismiss: () => void }) {
|
|
|
153
157
|
<div
|
|
154
158
|
className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] shadow-lg whitespace-nowrap"
|
|
155
159
|
style={{
|
|
156
|
-
background: '
|
|
157
|
-
border: '1px solid rgba(34,197,94,0.
|
|
160
|
+
background: 'var(--card)',
|
|
161
|
+
border: '1px solid rgba(34,197,94,0.3)',
|
|
158
162
|
}}
|
|
159
163
|
>
|
|
160
164
|
<svg width="11" height="11" viewBox="0 0 24 24" fill="#22c55e">
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useCallback, useState, useRef } from 'react'
|
|
3
|
+
import { useEffect, useCallback, useState, useRef, useMemo } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { useWs } from '@/hooks/use-ws'
|
|
6
6
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
7
|
-
import { fetchMessages, clearMessages, deleteSession, devServer, checkBrowser, stopBrowser } from '@/lib/sessions'
|
|
7
|
+
import { fetchMessages, fetchMessagesPaginated, clearMessages, deleteSession, devServer, checkBrowser, stopBrowser } from '@/lib/sessions'
|
|
8
8
|
import { uploadImage } from '@/lib/upload'
|
|
9
9
|
import { deleteAgent } from '@/lib/agents'
|
|
10
10
|
import { useMediaQuery } from '@/hooks/use-media-query'
|
|
@@ -17,6 +17,7 @@ import { useVoiceConversation } from '@/hooks/use-voice-conversation'
|
|
|
17
17
|
import { ChatInput } from '@/components/input/chat-input'
|
|
18
18
|
import { ChatPreviewPanel } from './chat-preview-panel'
|
|
19
19
|
import { InspectorPanel } from '@/components/agents/inspector-panel'
|
|
20
|
+
import { HeartbeatHistoryPanel } from './heartbeat-history-panel'
|
|
20
21
|
import { Dropdown, DropdownItem } from '@/components/shared/dropdown'
|
|
21
22
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
22
23
|
import { speak } from '@/lib/tts'
|
|
@@ -47,6 +48,8 @@ export function ChatArea() {
|
|
|
47
48
|
const setEditingAgentId = useAppStore((s) => s.setEditingAgentId)
|
|
48
49
|
const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
|
|
49
50
|
const inspectorOpen = useAppStore((s) => s.inspectorOpen)
|
|
51
|
+
const sidebarOpen = useAppStore((s) => s.sidebarOpen)
|
|
52
|
+
const setSidebarOpen = useAppStore((s) => s.setSidebarOpen)
|
|
50
53
|
const currentAgent = session?.agentId ? agents[session.agentId] ?? null : null
|
|
51
54
|
|
|
52
55
|
const voice = useVoiceConversation()
|
|
@@ -60,6 +63,28 @@ export function ChatArea() {
|
|
|
60
63
|
const [confirmClear, setConfirmClear] = useState(false)
|
|
61
64
|
const [confirmDeleteAgent, setConfirmDeleteAgent] = useState(false)
|
|
62
65
|
const [browserActive, setBrowserActive] = useState(false)
|
|
66
|
+
const [heartbeatHistoryOpen, setHeartbeatHistoryOpen] = useState(false)
|
|
67
|
+
const [messagesLoading, setMessagesLoading] = useState(true)
|
|
68
|
+
const [connectorFilter, setConnectorFilter] = useState<string | null>(null)
|
|
69
|
+
|
|
70
|
+
// Collect unique connector sources from messages for filter UI
|
|
71
|
+
const { connectorSources, hasDirectMessages } = useMemo(() => {
|
|
72
|
+
const sources = new Map<string, { platform: string; connectorName: string }>()
|
|
73
|
+
let hasDirect = false
|
|
74
|
+
for (const msg of messages) {
|
|
75
|
+
if (msg.source?.connectorId && !sources.has(msg.source.connectorId)) {
|
|
76
|
+
sources.set(msg.source.connectorId, {
|
|
77
|
+
platform: msg.source.platform,
|
|
78
|
+
connectorName: msg.source.connectorName,
|
|
79
|
+
})
|
|
80
|
+
} else if (!msg.source?.connectorId && msg.role === 'user') {
|
|
81
|
+
hasDirect = true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { connectorSources: sources, hasDirectMessages: hasDirect }
|
|
85
|
+
}, [messages])
|
|
86
|
+
// Show source filter when there are genuinely multiple sources (2+ connectors, or connector + direct)
|
|
87
|
+
const hasMultipleSources = connectorSources.size > 1 || (connectorSources.size > 0 && hasDirectMessages)
|
|
63
88
|
const [isDragging, setIsDragging] = useState(false)
|
|
64
89
|
const dragCounter = useRef(0)
|
|
65
90
|
const setPendingImage = useChatStore((s) => s.setPendingImage)
|
|
@@ -69,13 +94,19 @@ export function ChatArea() {
|
|
|
69
94
|
const chatState = useChatStore.getState()
|
|
70
95
|
const preserveLocalStream = chatState.streaming && chatState.streamingSessionId === sessionId
|
|
71
96
|
// Clear stale state from the previous session, but keep active local stream state for this session.
|
|
97
|
+
setMessagesLoading(true)
|
|
72
98
|
setMessages([])
|
|
73
99
|
if (!preserveLocalStream) {
|
|
74
100
|
useChatStore.setState({ streaming: false, streamingSessionId: null, streamText: '', toolEvents: [] })
|
|
75
101
|
}
|
|
76
|
-
|
|
102
|
+
fetchMessagesPaginated(sessionId, 100).then((data) => {
|
|
103
|
+
setMessages(data.messages)
|
|
104
|
+
useChatStore.setState({ hasMoreMessages: data.hasMore, totalMessages: data.total })
|
|
105
|
+
}).catch((err) => {
|
|
77
106
|
console.error('Failed to load messages:', err)
|
|
78
107
|
setMessages(session?.messages || [])
|
|
108
|
+
}).finally(() => {
|
|
109
|
+
setMessagesLoading(false)
|
|
79
110
|
})
|
|
80
111
|
// If server reports session is still active, show streaming state
|
|
81
112
|
if (session?.active) {
|
|
@@ -125,12 +156,13 @@ export function ChatArea() {
|
|
|
125
156
|
return !isHeartbeat && !!m.text?.trim()
|
|
126
157
|
})
|
|
127
158
|
if (latestAssistant?.text) {
|
|
128
|
-
void speak(latestAssistant.text)
|
|
159
|
+
void speak(latestAssistant.text, currentAgent?.elevenLabsVoiceId)
|
|
129
160
|
}
|
|
130
161
|
}
|
|
131
162
|
}
|
|
132
163
|
if (isServerActiveRef.current) await loadSessions()
|
|
133
164
|
} catch (err) { console.error('Failed to refresh messages:', err) }
|
|
165
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
134
166
|
}, [sessionId])
|
|
135
167
|
|
|
136
168
|
// Subscribe to WS messages for this session — always subscribe when session exists,
|
|
@@ -198,10 +230,6 @@ export function ChatArea() {
|
|
|
198
230
|
setCurrentSession(null)
|
|
199
231
|
}, [sessionId])
|
|
200
232
|
|
|
201
|
-
const handleBack = useCallback(() => {
|
|
202
|
-
setCurrentSession(null)
|
|
203
|
-
}, [])
|
|
204
|
-
|
|
205
233
|
const handlePrompt = useCallback((text: string) => {
|
|
206
234
|
sendMessage(text)
|
|
207
235
|
}, [sendMessage])
|
|
@@ -244,7 +272,7 @@ export function ChatArea() {
|
|
|
244
272
|
|
|
245
273
|
const streamingForThisSession = streaming && (!streamingSessionId || streamingSessionId === session.id)
|
|
246
274
|
const isMainChat = session.name === '__main__'
|
|
247
|
-
const isEmpty = !messages.length && !streamingForThisSession
|
|
275
|
+
const isEmpty = !messages.length && !streamingForThisSession && !messagesLoading
|
|
248
276
|
|
|
249
277
|
return (
|
|
250
278
|
<div className="flex-1 flex h-full min-h-0 min-w-0">
|
|
@@ -261,12 +289,18 @@ export function ChatArea() {
|
|
|
261
289
|
streaming={streamingForThisSession}
|
|
262
290
|
onStop={stopStreaming}
|
|
263
291
|
onMenuToggle={() => setMenuOpen(!menuOpen)}
|
|
264
|
-
onBack={
|
|
292
|
+
onBack={sidebarOpen ? () => setSidebarOpen(false) : undefined}
|
|
265
293
|
browserActive={browserActive}
|
|
266
294
|
onStopBrowser={handleStopBrowser}
|
|
267
295
|
voiceActive={voice.active}
|
|
268
296
|
voiceSupported={voice.supported}
|
|
269
297
|
onVoiceToggle={handleVoiceToggle}
|
|
298
|
+
heartbeatHistoryOpen={heartbeatHistoryOpen}
|
|
299
|
+
onToggleHeartbeatHistory={() => setHeartbeatHistoryOpen((v) => !v)}
|
|
300
|
+
connectorSources={connectorSources}
|
|
301
|
+
connectorFilter={connectorFilter}
|
|
302
|
+
onConnectorFilterChange={setConnectorFilter}
|
|
303
|
+
hasMultipleSources={hasMultipleSources}
|
|
270
304
|
/>
|
|
271
305
|
)}
|
|
272
306
|
{!isDesktop && (
|
|
@@ -281,11 +315,25 @@ export function ChatArea() {
|
|
|
281
315
|
voiceActive={voice.active}
|
|
282
316
|
voiceSupported={voice.supported}
|
|
283
317
|
onVoiceToggle={handleVoiceToggle}
|
|
318
|
+
connectorSources={connectorSources}
|
|
319
|
+
connectorFilter={connectorFilter}
|
|
320
|
+
onConnectorFilterChange={setConnectorFilter}
|
|
321
|
+
hasMultipleSources={hasMultipleSources}
|
|
284
322
|
/>
|
|
285
323
|
)}
|
|
286
324
|
<DevServerBar status={devServerStatus} onStop={handleStopDevServer} />
|
|
287
325
|
|
|
288
|
-
{
|
|
326
|
+
{messagesLoading && !messages.length ? (
|
|
327
|
+
<div className="flex-1 flex items-center justify-center">
|
|
328
|
+
<div className="flex flex-col items-center gap-3" style={{ animation: 'fade-in 0.2s ease' }}>
|
|
329
|
+
<div className="relative w-10 h-10">
|
|
330
|
+
<div className="absolute inset-0 rounded-full border-2 border-white/[0.06]" />
|
|
331
|
+
<div className="absolute inset-0 rounded-full border-2 border-transparent border-t-accent-bright animate-spin" />
|
|
332
|
+
</div>
|
|
333
|
+
<span className="text-[13px] text-text-3/50 font-500">Loading messages...</span>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
) : isEmpty ? (
|
|
289
337
|
<div className="flex-1 flex flex-col items-center justify-center px-6 pb-4 relative">
|
|
290
338
|
{/* Atmospheric background glow */}
|
|
291
339
|
<div className="absolute inset-0 pointer-events-none overflow-hidden">
|
|
@@ -336,7 +384,7 @@ export function ChatArea() {
|
|
|
336
384
|
</div>
|
|
337
385
|
</div>
|
|
338
386
|
) : (
|
|
339
|
-
<MessageList messages={messages} streaming={streamingForThisSession} />
|
|
387
|
+
<MessageList messages={messages} streaming={streamingForThisSession} connectorFilter={connectorFilter} />
|
|
340
388
|
)}
|
|
341
389
|
|
|
342
390
|
{voice.active && (
|
|
@@ -361,19 +409,9 @@ export function ChatArea() {
|
|
|
361
409
|
/>
|
|
362
410
|
|
|
363
411
|
<Dropdown open={menuOpen} onClose={() => setMenuOpen(false)}>
|
|
364
|
-
{session.agentId && agents[session.agentId] && (
|
|
365
|
-
<DropdownItem onClick={() => { setMenuOpen(false); setEditingAgentId(session.agentId!); setAgentSheetOpen(true) }}>
|
|
366
|
-
Edit Agent
|
|
367
|
-
</DropdownItem>
|
|
368
|
-
)}
|
|
369
412
|
<DropdownItem onClick={() => { setMenuOpen(false); setConfirmClear(true) }}>
|
|
370
413
|
Clear History
|
|
371
414
|
</DropdownItem>
|
|
372
|
-
{session.agentId && agents[session.agentId] && !isMainChat && (
|
|
373
|
-
<DropdownItem danger onClick={() => { setMenuOpen(false); setConfirmDeleteAgent(true) }}>
|
|
374
|
-
Delete Agent
|
|
375
|
-
</DropdownItem>
|
|
376
|
-
)}
|
|
377
415
|
{!isMainChat && (
|
|
378
416
|
<DropdownItem danger onClick={() => { setMenuOpen(false); setConfirmDelete(true) }}>
|
|
379
417
|
Delete Chat
|
|
@@ -432,7 +470,21 @@ export function ChatArea() {
|
|
|
432
470
|
<ChatPreviewPanel content={previewContent} onClose={() => setPreviewContent(null)} />
|
|
433
471
|
)}
|
|
434
472
|
{isDesktop && inspectorOpen && currentAgent && (
|
|
435
|
-
<InspectorPanel
|
|
473
|
+
<InspectorPanel
|
|
474
|
+
agent={currentAgent}
|
|
475
|
+
onEditAgent={() => { setEditingAgentId(session.agentId!); setAgentSheetOpen(true) }}
|
|
476
|
+
onClearHistory={() => setConfirmClear(true)}
|
|
477
|
+
onDeleteAgent={!isMainChat ? () => setConfirmDeleteAgent(true) : undefined}
|
|
478
|
+
onDeleteChat={!isMainChat ? () => setConfirmDelete(true) : undefined}
|
|
479
|
+
isMainChat={isMainChat}
|
|
480
|
+
/>
|
|
481
|
+
)}
|
|
482
|
+
{isDesktop && heartbeatHistoryOpen && currentAgent?.heartbeatEnabled && (
|
|
483
|
+
<HeartbeatHistoryPanel
|
|
484
|
+
messages={messages}
|
|
485
|
+
agentHeartbeatGoal={currentAgent.heartbeatGoal ?? undefined}
|
|
486
|
+
onClose={() => setHeartbeatHistoryOpen(false)}
|
|
487
|
+
/>
|
|
436
488
|
)}
|
|
437
489
|
</div>
|
|
438
490
|
)
|
|
@@ -442,7 +494,7 @@ function PromptIcon({ type }: { type: string }) {
|
|
|
442
494
|
const cls = "w-5 h-5"
|
|
443
495
|
switch (type) {
|
|
444
496
|
case 'book':
|
|
445
|
-
return <svg className={cls} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" style={{ color: '
|
|
497
|
+
return <svg className={cls} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" style={{ color: 'var(--color-accent-bright)' }}><rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="7" rx="1" /><rect x="3" y="14" width="7" height="7" rx="1" /><rect x="14" y="14" width="7" height="7" rx="1" /></svg>
|
|
446
498
|
case 'link':
|
|
447
499
|
return <svg className={cls} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" style={{ color: '#F472B6' }}><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3" /><line x1="8" y1="12" x2="16" y2="12" /></svg>
|
|
448
500
|
case 'bot':
|