@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
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useCallback, useRef, useState } from 'react'
|
|
4
|
-
import { useChatStore
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
4
|
+
import { useChatStore } from '@/stores/use-chat-store'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { uploadImage } from '@/lib/upload'
|
|
7
7
|
import { useAutoResize } from '@/hooks/use-auto-resize'
|
|
8
8
|
import { useSpeechRecognition } from '@/hooks/use-speech-recognition'
|
|
9
|
+
import { FilePreview } from '@/components/shared/file-preview'
|
|
10
|
+
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
|
|
9
11
|
|
|
10
12
|
interface Props {
|
|
11
13
|
streaming: boolean
|
|
@@ -13,36 +15,7 @@ interface Props {
|
|
|
13
15
|
onStop: () => void
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
const isImage = file.file.type.startsWith('image/')
|
|
18
|
-
return (
|
|
19
|
-
<div className="relative">
|
|
20
|
-
{isImage ? (
|
|
21
|
-
<img
|
|
22
|
-
src={URL.createObjectURL(file.file)}
|
|
23
|
-
alt="Preview"
|
|
24
|
-
className="h-16 rounded-[10px] object-cover border border-white/[0.06]"
|
|
25
|
-
/>
|
|
26
|
-
) : (
|
|
27
|
-
<div className="flex items-center gap-2.5 px-3 py-2.5 rounded-[10px] border border-white/[0.06] bg-white/[0.03]">
|
|
28
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3 shrink-0">
|
|
29
|
-
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
|
30
|
-
<polyline points="14 2 14 8 20 8" />
|
|
31
|
-
</svg>
|
|
32
|
-
<span className="text-[13px] text-text-2 font-500 truncate max-w-[180px]">{file.file.name}</span>
|
|
33
|
-
</div>
|
|
34
|
-
)}
|
|
35
|
-
<button
|
|
36
|
-
onClick={onRemove}
|
|
37
|
-
className="absolute -top-1.5 -right-1.5 w-5 h-5 rounded-full border border-white/10 bg-raised
|
|
38
|
-
text-text-2 text-[10px] cursor-pointer flex items-center justify-center
|
|
39
|
-
hover:bg-danger-soft hover:text-danger hover:border-danger/20 transition-colors"
|
|
40
|
-
>
|
|
41
|
-
×
|
|
42
|
-
</button>
|
|
43
|
-
</div>
|
|
44
|
-
)
|
|
45
|
-
}
|
|
18
|
+
// FilePreview is now imported from @/components/shared/file-preview
|
|
46
19
|
|
|
47
20
|
export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
48
21
|
const [value, setValue] = useState('')
|
|
@@ -53,16 +26,51 @@ export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
|
53
26
|
const addPendingFile = useChatStore((s) => s.addPendingFile)
|
|
54
27
|
const removePendingFile = useChatStore((s) => s.removePendingFile)
|
|
55
28
|
const speechRecognitionLang = useAppStore((s) => s.appSettings.speechRecognitionLang)
|
|
29
|
+
const sessionId = useAppStore((s) => s.currentSessionId)
|
|
30
|
+
|
|
31
|
+
const queuedMessages = useChatStore((s) => s.queuedMessages)
|
|
32
|
+
const addQueuedMessage = useChatStore((s) => s.addQueuedMessage)
|
|
33
|
+
const removeQueuedMessage = useChatStore((s) => s.removeQueuedMessage)
|
|
34
|
+
|
|
35
|
+
// Draft persistence: restore on session change
|
|
36
|
+
const draftTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!sessionId) return
|
|
39
|
+
const draft = localStorage.getItem(`sc_draft_${sessionId}`)
|
|
40
|
+
setValue(draft || '')
|
|
41
|
+
}, [sessionId])
|
|
42
|
+
|
|
43
|
+
// Debounced save to localStorage
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!sessionId) return
|
|
46
|
+
if (draftTimerRef.current) clearTimeout(draftTimerRef.current)
|
|
47
|
+
draftTimerRef.current = setTimeout(() => {
|
|
48
|
+
if (value) localStorage.setItem(`sc_draft_${sessionId}`, value)
|
|
49
|
+
else localStorage.removeItem(`sc_draft_${sessionId}`)
|
|
50
|
+
}, 300)
|
|
51
|
+
return () => { if (draftTimerRef.current) clearTimeout(draftTimerRef.current) }
|
|
52
|
+
}, [value, sessionId])
|
|
56
53
|
|
|
57
54
|
const handleSend = useCallback(() => {
|
|
58
55
|
const text = value.trim()
|
|
59
|
-
if (
|
|
56
|
+
if (!text && !pendingFiles.length) return
|
|
57
|
+
// If streaming, queue the message instead of blocking
|
|
58
|
+
if (streaming) {
|
|
59
|
+
if (text) {
|
|
60
|
+
addQueuedMessage(text)
|
|
61
|
+
setValue('')
|
|
62
|
+
if (textareaRef.current) textareaRef.current.style.height = 'auto'
|
|
63
|
+
}
|
|
64
|
+
return
|
|
65
|
+
}
|
|
60
66
|
onSend(text || 'See attached file(s).')
|
|
61
67
|
setValue('')
|
|
68
|
+
if (sessionId) localStorage.removeItem(`sc_draft_${sessionId}`)
|
|
62
69
|
if (textareaRef.current) {
|
|
63
70
|
textareaRef.current.style.height = 'auto'
|
|
64
71
|
}
|
|
65
|
-
|
|
72
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
73
|
+
}, [value, streaming, onSend, pendingFiles.length, sessionId])
|
|
66
74
|
|
|
67
75
|
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
68
76
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
@@ -84,8 +92,8 @@ export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
|
84
92
|
try {
|
|
85
93
|
const result = await uploadImage(file)
|
|
86
94
|
addPendingFile({ file, path: result.path, url: result.url })
|
|
87
|
-
} catch {
|
|
88
|
-
|
|
95
|
+
} catch (err: unknown) {
|
|
96
|
+
console.error('File upload failed:', err instanceof Error ? err.message : String(err))
|
|
89
97
|
}
|
|
90
98
|
}, [addPendingFile])
|
|
91
99
|
|
|
@@ -131,6 +139,27 @@ export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
|
131
139
|
</div>
|
|
132
140
|
)}
|
|
133
141
|
|
|
142
|
+
{queuedMessages.length > 0 && (
|
|
143
|
+
<div className="flex flex-wrap items-center gap-1.5 mb-2">
|
|
144
|
+
<span className="label-mono text-amber-400/70">Queued</span>
|
|
145
|
+
{queuedMessages.map((msg, i) => (
|
|
146
|
+
<span key={i} className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-amber-500/10 border border-amber-500/15 text-[12px] text-amber-300 font-mono max-w-[200px]">
|
|
147
|
+
<span className="truncate">{msg}</span>
|
|
148
|
+
<button
|
|
149
|
+
type="button"
|
|
150
|
+
onClick={() => removeQueuedMessage(i)}
|
|
151
|
+
className="shrink-0 text-amber-400/60 hover:text-amber-300 border-none bg-transparent cursor-pointer p-0"
|
|
152
|
+
>
|
|
153
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
154
|
+
<line x1="18" y1="6" x2="6" y2="18" />
|
|
155
|
+
<line x1="6" y1="6" x2="18" y2="18" />
|
|
156
|
+
</svg>
|
|
157
|
+
</button>
|
|
158
|
+
</span>
|
|
159
|
+
))}
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
|
|
134
163
|
<div className="glass rounded-[20px] overflow-hidden
|
|
135
164
|
shadow-[0_4px_32px_rgba(0,0,0,0.3)] focus-within:border-border-focus focus-within:shadow-[0_4px_32px_rgba(99,102,241,0.08)] transition-all duration-300">
|
|
136
165
|
|
|
@@ -198,6 +227,31 @@ export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
|
198
227
|
</button>
|
|
199
228
|
)}
|
|
200
229
|
|
|
230
|
+
<Tooltip>
|
|
231
|
+
<TooltipTrigger asChild>
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
onClick={() => { useChatStore.getState().clearContext() }}
|
|
235
|
+
disabled={streaming}
|
|
236
|
+
className="flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-transparent
|
|
237
|
+
text-text-3 text-[13px] cursor-pointer hover:text-amber-400 hover:bg-amber-400/10 transition-all duration-200 disabled:opacity-30 disabled:pointer-events-none"
|
|
238
|
+
style={{ fontFamily: 'inherit' }}
|
|
239
|
+
>
|
|
240
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
|
|
241
|
+
<line x1="2" y1="12" x2="22" y2="12" />
|
|
242
|
+
<polyline points="8 8 4 12 8 16" />
|
|
243
|
+
<polyline points="16 8 20 12 16 16" />
|
|
244
|
+
</svg>
|
|
245
|
+
<span className="hidden sm:inline">New context</span>
|
|
246
|
+
</button>
|
|
247
|
+
</TooltipTrigger>
|
|
248
|
+
<TooltipContent side="top" sideOffset={8}
|
|
249
|
+
className="bg-raised border border-white/[0.08] text-text shadow-[0_8px_32px_rgba(0,0,0,0.5)] rounded-[10px] px-3.5 py-2.5 max-w-[220px]">
|
|
250
|
+
<div className="font-display text-[12px] font-600 mb-0.5">New context window</div>
|
|
251
|
+
<div className="text-[11px] text-text-3 leading-[1.4]">Adds a marker — messages above it won't be sent to the AI. Nothing is deleted.</div>
|
|
252
|
+
</TooltipContent>
|
|
253
|
+
</Tooltip>
|
|
254
|
+
|
|
201
255
|
<div className="flex-1" />
|
|
202
256
|
|
|
203
257
|
<span className="text-[11px] text-text-3/60 tabular-nums mr-2 font-mono">
|
|
@@ -206,17 +260,27 @@ export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
|
206
260
|
|
|
207
261
|
<button
|
|
208
262
|
onClick={handleSend}
|
|
209
|
-
disabled={!hasContent
|
|
263
|
+
disabled={!hasContent}
|
|
210
264
|
className={`w-9 h-9 rounded-[11px] border-none flex items-center justify-center
|
|
211
265
|
shrink-0 cursor-pointer transition-all duration-250
|
|
212
|
-
${hasContent
|
|
213
|
-
?
|
|
266
|
+
${hasContent
|
|
267
|
+
? streaming
|
|
268
|
+
? 'bg-amber-500/20 text-amber-400 active:scale-90 border border-amber-500/30'
|
|
269
|
+
: 'bg-accent-bright text-white active:scale-90 shadow-[0_4px_16px_rgba(99,102,241,0.3)]'
|
|
214
270
|
: 'bg-white/[0.04] text-text-3 pointer-events-none'}`}
|
|
271
|
+
title={streaming ? 'Queue message' : 'Send message'}
|
|
215
272
|
>
|
|
216
|
-
|
|
217
|
-
<
|
|
218
|
-
|
|
219
|
-
|
|
273
|
+
{streaming && hasContent ? (
|
|
274
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
275
|
+
<line x1="12" y1="5" x2="12" y2="19" />
|
|
276
|
+
<line x1="5" y1="12" x2="19" y2="12" />
|
|
277
|
+
</svg>
|
|
278
|
+
) : (
|
|
279
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
280
|
+
<line x1="12" y1="19" x2="12" y2="5" />
|
|
281
|
+
<polyline points="5 12 12 5 19 12" />
|
|
282
|
+
</svg>
|
|
283
|
+
)}
|
|
220
284
|
</button>
|
|
221
285
|
</div>
|
|
222
286
|
</div>
|
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
4
4
|
import { api } from '@/lib/api-client'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { Badge } from '@/components/ui/badge'
|
|
7
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
7
8
|
import type { MemoryEntry } from '@/types'
|
|
8
9
|
|
|
9
10
|
export function KnowledgeList() {
|
|
@@ -13,6 +14,8 @@ export function KnowledgeList() {
|
|
|
13
14
|
const [error, setError] = useState<string | null>(null)
|
|
14
15
|
const [activeTag, setActiveTag] = useState<string | null>(null)
|
|
15
16
|
const searchRef = useRef(search)
|
|
17
|
+
const agents = useAppStore((s) => s.agents)
|
|
18
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
16
19
|
const setKnowledgeSheetOpen = useAppStore((s) => s.setKnowledgeSheetOpen)
|
|
17
20
|
const setEditingKnowledgeId = useAppStore((s) => s.setEditingKnowledgeId)
|
|
18
21
|
|
|
@@ -40,8 +43,10 @@ export function KnowledgeList() {
|
|
|
40
43
|
|
|
41
44
|
// Initial load
|
|
42
45
|
useEffect(() => {
|
|
46
|
+
loadAgents()
|
|
43
47
|
const timer = setTimeout(() => { void load(searchRef.current, activeTag) }, 0)
|
|
44
48
|
return () => clearTimeout(timer)
|
|
49
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
45
50
|
}, [load, activeTag])
|
|
46
51
|
|
|
47
52
|
// Debounced search
|
|
@@ -120,8 +125,14 @@ export function KnowledgeList() {
|
|
|
120
125
|
{entries.length > 0 ? (
|
|
121
126
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 px-5 pb-6">
|
|
122
127
|
{entries.map((entry) => {
|
|
123
|
-
const meta = entry.metadata as { tags?: string[] } | undefined
|
|
128
|
+
const meta = entry.metadata as { tags?: string[]; scope?: 'global' | 'agent'; agentIds?: string[] } | undefined
|
|
124
129
|
const tags = meta?.tags || []
|
|
130
|
+
const entryScope = meta?.scope || 'global'
|
|
131
|
+
const entryAgentIds = meta?.agentIds || []
|
|
132
|
+
const scopeLabel = entryScope === 'global' ? 'Global' : `${entryAgentIds.length} agent(s)`
|
|
133
|
+
const scopedAgents = entryScope === 'agent'
|
|
134
|
+
? entryAgentIds.map((id) => agents[id]).filter(Boolean)
|
|
135
|
+
: []
|
|
125
136
|
return (
|
|
126
137
|
<div
|
|
127
138
|
key={entry.id}
|
|
@@ -162,6 +173,25 @@ export function KnowledgeList() {
|
|
|
162
173
|
))}
|
|
163
174
|
</div>
|
|
164
175
|
)}
|
|
176
|
+
<div className="flex items-center gap-2 mt-1.5">
|
|
177
|
+
<span className={`text-[10px] font-600 ${
|
|
178
|
+
entryScope === 'global' ? 'text-emerald-400' : 'text-amber-400'
|
|
179
|
+
}`}>
|
|
180
|
+
{scopeLabel}
|
|
181
|
+
</span>
|
|
182
|
+
{scopedAgents.length > 0 && (
|
|
183
|
+
<div className="flex items-center gap-1.5">
|
|
184
|
+
<div className="flex items-center -space-x-1.5">
|
|
185
|
+
{scopedAgents.slice(0, 5).map((agent) => (
|
|
186
|
+
<AgentAvatar key={agent.id} seed={agent.avatarSeed} name={agent.name} size={16} className="ring-1 ring-surface" />
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
{scopedAgents.length > 5 && (
|
|
190
|
+
<span className="text-[10px] font-600 text-text-3/60 ml-0.5">+{scopedAgents.length - 5}</span>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
165
195
|
</div>
|
|
166
196
|
)
|
|
167
197
|
})}
|
|
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
|
|
4
4
|
import { api } from '@/lib/api-client'
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
7
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
7
8
|
import type { MemoryEntry } from '@/types'
|
|
8
9
|
|
|
9
10
|
const ACCEPTED_TYPES = '.txt,.md,.csv,.json,.jsonl,.html,.xml,.yaml,.yml,.toml,.py,.js,.ts,.tsx,.jsx,.go,.rs,.java,.c,.cpp,.h,.rb,.php,.sh,.sql,.log,.pdf'
|
|
@@ -21,16 +22,26 @@ export function KnowledgeSheet() {
|
|
|
21
22
|
const open = useAppStore((s) => s.knowledgeSheetOpen)
|
|
22
23
|
const setOpen = useAppStore((s) => s.setKnowledgeSheetOpen)
|
|
23
24
|
const editingId = useAppStore((s) => s.editingKnowledgeId)
|
|
25
|
+
const agents = useAppStore((s) => s.agents)
|
|
26
|
+
const loadAgents = useAppStore((s) => s.loadAgents)
|
|
24
27
|
|
|
25
28
|
const [title, setTitle] = useState('')
|
|
26
29
|
const [content, setContent] = useState('')
|
|
27
30
|
const [tags, setTags] = useState('')
|
|
31
|
+
const [scope, setScope] = useState<'global' | 'agent'>('global')
|
|
32
|
+
const [agentIds, setAgentIds] = useState<string[]>([])
|
|
28
33
|
const [saving, setSaving] = useState(false)
|
|
29
34
|
const [uploadedFile, setUploadedFile] = useState<{ name: string; url: string; size: number } | null>(null)
|
|
30
35
|
const [uploading, setUploading] = useState(false)
|
|
31
36
|
const [isDragging, setIsDragging] = useState(false)
|
|
32
37
|
const dragCounter = useRef(0)
|
|
33
38
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
39
|
+
const agentList = Object.values(agents)
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (open) loadAgents()
|
|
43
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
|
+
}, [open])
|
|
34
45
|
|
|
35
46
|
useEffect(() => {
|
|
36
47
|
if (!open) return
|
|
@@ -38,8 +49,10 @@ export function KnowledgeSheet() {
|
|
|
38
49
|
void api<MemoryEntry>('GET', `/knowledge/${editingId}`).then((entry) => {
|
|
39
50
|
setTitle(entry.title)
|
|
40
51
|
setContent(entry.content)
|
|
41
|
-
const meta = entry.metadata as { tags?: string[] } | undefined
|
|
52
|
+
const meta = entry.metadata as { tags?: string[]; scope?: 'global' | 'agent'; agentIds?: string[] } | undefined
|
|
42
53
|
setTags(meta?.tags?.join(', ') || '')
|
|
54
|
+
setScope(meta?.scope || 'global')
|
|
55
|
+
setAgentIds(meta?.agentIds || [])
|
|
43
56
|
}).catch(() => {
|
|
44
57
|
setOpen(false)
|
|
45
58
|
})
|
|
@@ -47,6 +60,8 @@ export function KnowledgeSheet() {
|
|
|
47
60
|
setTitle('')
|
|
48
61
|
setContent('')
|
|
49
62
|
setTags('')
|
|
63
|
+
setScope('global')
|
|
64
|
+
setAgentIds([])
|
|
50
65
|
setUploadedFile(null)
|
|
51
66
|
}
|
|
52
67
|
}, [open, editingId, setOpen])
|
|
@@ -56,6 +71,8 @@ export function KnowledgeSheet() {
|
|
|
56
71
|
setTitle('')
|
|
57
72
|
setContent('')
|
|
58
73
|
setTags('')
|
|
74
|
+
setScope('global')
|
|
75
|
+
setAgentIds([])
|
|
59
76
|
setUploadedFile(null)
|
|
60
77
|
setIsDragging(false)
|
|
61
78
|
dragCounter.current = 0
|
|
@@ -128,6 +145,16 @@ export function KnowledgeSheet() {
|
|
|
128
145
|
if (file) void handleUpload(file)
|
|
129
146
|
}, [handleUpload])
|
|
130
147
|
|
|
148
|
+
const toggleAgent = (id: string) => {
|
|
149
|
+
setAgentIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id])
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const scopeHelperText = scope === 'global'
|
|
153
|
+
? 'This knowledge will be accessible to all agents'
|
|
154
|
+
: agentIds.length === 0
|
|
155
|
+
? 'Select which agents can access this knowledge'
|
|
156
|
+
: `${agentIds.length} agent(s) selected`
|
|
157
|
+
|
|
131
158
|
const handleSave = async () => {
|
|
132
159
|
setSaving(true)
|
|
133
160
|
try {
|
|
@@ -135,6 +162,8 @@ export function KnowledgeSheet() {
|
|
|
135
162
|
title: title.trim() || 'Untitled',
|
|
136
163
|
content,
|
|
137
164
|
tags: parseTags(tags),
|
|
165
|
+
scope,
|
|
166
|
+
agentIds: scope === 'agent' ? agentIds : [],
|
|
138
167
|
}
|
|
139
168
|
|
|
140
169
|
if (editingId) {
|
|
@@ -294,6 +323,58 @@ export function KnowledgeSheet() {
|
|
|
294
323
|
/>
|
|
295
324
|
</div>
|
|
296
325
|
|
|
326
|
+
<div className="mb-8">
|
|
327
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Scope</label>
|
|
328
|
+
<div className="flex p-1 rounded-[12px] bg-bg border border-white/[0.06]">
|
|
329
|
+
{(['global', 'agent'] as const).map((s) => (
|
|
330
|
+
<button
|
|
331
|
+
key={s}
|
|
332
|
+
onClick={() => setScope(s)}
|
|
333
|
+
className={`flex-1 py-2.5 rounded-[10px] text-center cursor-pointer transition-all text-[13px] font-600 border-none ${
|
|
334
|
+
scope === s ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'
|
|
335
|
+
}`}
|
|
336
|
+
style={{ fontFamily: 'inherit' }}
|
|
337
|
+
>
|
|
338
|
+
{s === 'global' ? 'Global' : 'Specific'}
|
|
339
|
+
</button>
|
|
340
|
+
))}
|
|
341
|
+
</div>
|
|
342
|
+
<p className="text-[11px] text-text-3/60 mt-1.5 pl-1">{scopeHelperText}</p>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
{scope === 'agent' && (
|
|
346
|
+
<div className="mb-8">
|
|
347
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Agents</label>
|
|
348
|
+
<div className="max-h-[240px] overflow-y-auto rounded-[12px] border border-white/[0.06] bg-white/[0.03]">
|
|
349
|
+
{agentList.length === 0 ? (
|
|
350
|
+
<p className="p-3 text-[12px] text-text-3">No agents available</p>
|
|
351
|
+
) : (
|
|
352
|
+
agentList.map((agent) => {
|
|
353
|
+
const selected = agentIds.includes(agent.id)
|
|
354
|
+
return (
|
|
355
|
+
<button
|
|
356
|
+
key={agent.id}
|
|
357
|
+
onClick={() => toggleAgent(agent.id)}
|
|
358
|
+
className={`w-full flex items-center gap-2.5 px-3 py-2 text-left transition-all cursor-pointer ${
|
|
359
|
+
selected ? 'bg-accent-soft/40' : 'hover:bg-white/[0.04]'
|
|
360
|
+
}`}
|
|
361
|
+
style={{ fontFamily: 'inherit' }}
|
|
362
|
+
>
|
|
363
|
+
<AgentAvatar seed={agent.avatarSeed} name={agent.name} size={24} />
|
|
364
|
+
<span className="text-[13px] text-text flex-1 truncate">{agent.name}</span>
|
|
365
|
+
{selected && (
|
|
366
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="text-accent-bright shrink-0">
|
|
367
|
+
<polyline points="20 6 9 17 4 12" />
|
|
368
|
+
</svg>
|
|
369
|
+
)}
|
|
370
|
+
</button>
|
|
371
|
+
)
|
|
372
|
+
})
|
|
373
|
+
)}
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
)}
|
|
377
|
+
|
|
297
378
|
<div className="flex gap-3 pt-2 border-t border-white/[0.04]">
|
|
298
379
|
<button
|
|
299
380
|
onClick={onClose}
|
|
@@ -305,7 +386,7 @@ export function KnowledgeSheet() {
|
|
|
305
386
|
<button
|
|
306
387
|
onClick={() => { void handleSave() }}
|
|
307
388
|
disabled={!title.trim() || saving}
|
|
308
|
-
className="flex-1 py-3.5 rounded-[14px] border-none bg-
|
|
389
|
+
className="flex-1 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110"
|
|
309
390
|
style={{ fontFamily: 'inherit' }}
|
|
310
391
|
>
|
|
311
392
|
{saving ? 'Saving...' : 'Save'}
|