@swarmclawai/swarmclaw 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -7
- package/bin/swarmclaw.js +76 -16
- package/next.config.ts +11 -1
- package/package.json +4 -2
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/scripts/postinstall.mjs +18 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +410 -0
- package/src/app/api/chatrooms/[id]/members/route.ts +82 -0
- package/src/app/api/chatrooms/[id]/pins/route.ts +39 -0
- package/src/app/api/chatrooms/[id]/reactions/route.ts +42 -0
- package/src/app/api/chatrooms/[id]/route.ts +84 -0
- package/src/app/api/chatrooms/route.ts +50 -0
- package/src/app/api/credentials/route.ts +2 -3
- package/src/app/api/knowledge/[id]/route.ts +13 -2
- package/src/app/api/knowledge/route.ts +8 -1
- package/src/app/api/memory/route.ts +8 -0
- package/src/app/api/notifications/[id]/route.ts +27 -0
- package/src/app/api/notifications/route.ts +68 -0
- package/src/app/api/orchestrator/run/route.ts +1 -1
- package/src/app/api/plugins/install/route.ts +2 -2
- package/src/app/api/search/route.ts +155 -0
- package/src/app/api/sessions/[id]/chat/route.ts +2 -0
- package/src/app/api/sessions/[id]/edit-resend/route.ts +1 -1
- package/src/app/api/sessions/[id]/fork/route.ts +1 -1
- package/src/app/api/sessions/route.ts +3 -3
- package/src/app/api/settings/route.ts +9 -0
- package/src/app/api/setup/check-provider/route.ts +3 -16
- package/src/app/api/skills/[id]/route.ts +6 -0
- package/src/app/api/skills/route.ts +6 -0
- package/src/app/api/tasks/[id]/route.ts +20 -0
- package/src/app/api/tasks/bulk/route.ts +100 -0
- package/src/app/api/tasks/route.ts +1 -0
- package/src/app/api/usage/route.ts +45 -0
- package/src/app/api/webhooks/[id]/route.ts +15 -1
- package/src/app/globals.css +58 -15
- package/src/app/page.tsx +142 -13
- package/src/cli/index.js +42 -0
- package/src/cli/index.test.js +30 -0
- package/src/cli/spec.js +32 -0
- package/src/components/agents/agent-avatar.tsx +57 -10
- package/src/components/agents/agent-card.tsx +48 -15
- package/src/components/agents/agent-chat-list.tsx +123 -10
- package/src/components/agents/agent-list.tsx +50 -19
- package/src/components/agents/agent-sheet.tsx +56 -63
- package/src/components/auth/access-key-gate.tsx +10 -3
- package/src/components/auth/setup-wizard.tsx +2 -2
- package/src/components/auth/user-picker.tsx +31 -3
- package/src/components/chat/activity-moment.tsx +169 -0
- package/src/components/chat/chat-header.tsx +2 -0
- package/src/components/chat/chat-tool-toggles.tsx +1 -1
- package/src/components/chat/file-path-chip.tsx +125 -0
- package/src/components/chat/markdown-utils.ts +9 -0
- package/src/components/chat/message-bubble.tsx +46 -295
- package/src/components/chat/message-list.tsx +50 -1
- package/src/components/chat/streaming-bubble.tsx +36 -46
- package/src/components/chat/suggestions-bar.tsx +1 -1
- package/src/components/chat/thinking-indicator.tsx +72 -10
- package/src/components/chat/tool-call-bubble.tsx +66 -70
- package/src/components/chat/tool-request-banner.tsx +31 -7
- package/src/components/chat/transfer-agent-picker.tsx +63 -0
- package/src/components/chatrooms/agent-hover-card.tsx +124 -0
- package/src/components/chatrooms/chatroom-input.tsx +320 -0
- package/src/components/chatrooms/chatroom-list.tsx +123 -0
- package/src/components/chatrooms/chatroom-message.tsx +427 -0
- package/src/components/chatrooms/chatroom-sheet.tsx +215 -0
- package/src/components/chatrooms/chatroom-tool-request-banner.tsx +134 -0
- package/src/components/chatrooms/chatroom-typing-bar.tsx +88 -0
- package/src/components/chatrooms/chatroom-view.tsx +344 -0
- package/src/components/chatrooms/reaction-picker.tsx +273 -0
- package/src/components/connectors/connector-sheet.tsx +34 -47
- package/src/components/home/home-view.tsx +501 -0
- package/src/components/input/chat-input.tsx +79 -41
- package/src/components/knowledge/knowledge-list.tsx +31 -1
- package/src/components/knowledge/knowledge-sheet.tsx +83 -2
- package/src/components/layout/app-layout.tsx +209 -83
- package/src/components/layout/mobile-header.tsx +2 -0
- package/src/components/layout/update-banner.tsx +2 -2
- package/src/components/logs/log-list.tsx +2 -2
- package/src/components/mcp-servers/mcp-server-sheet.tsx +1 -1
- package/src/components/memory/memory-agent-list.tsx +143 -0
- package/src/components/memory/memory-browser.tsx +205 -0
- package/src/components/memory/memory-card.tsx +34 -7
- package/src/components/memory/memory-detail.tsx +359 -120
- package/src/components/memory/memory-sheet.tsx +157 -23
- package/src/components/plugins/plugin-list.tsx +1 -1
- package/src/components/plugins/plugin-sheet.tsx +1 -1
- package/src/components/projects/project-detail.tsx +509 -0
- package/src/components/projects/project-list.tsx +195 -59
- package/src/components/providers/provider-list.tsx +2 -2
- package/src/components/providers/provider-sheet.tsx +3 -3
- package/src/components/schedules/schedule-card.tsx +3 -2
- package/src/components/schedules/schedule-list.tsx +1 -1
- package/src/components/schedules/schedule-sheet.tsx +25 -25
- package/src/components/secrets/secret-sheet.tsx +47 -24
- package/src/components/secrets/secrets-list.tsx +18 -8
- package/src/components/sessions/new-session-sheet.tsx +33 -65
- package/src/components/sessions/session-card.tsx +45 -14
- package/src/components/sessions/session-list.tsx +35 -18
- package/src/components/shared/agent-picker-list.tsx +90 -0
- package/src/components/shared/agent-switch-dialog.tsx +156 -0
- package/src/components/shared/attachment-chip.tsx +165 -0
- package/src/components/shared/avatar.tsx +10 -1
- package/src/components/shared/check-icon.tsx +12 -0
- package/src/components/shared/confirm-dialog.tsx +1 -1
- package/src/components/shared/empty-state.tsx +32 -0
- package/src/components/shared/file-preview.tsx +34 -0
- package/src/components/shared/form-styles.ts +2 -0
- package/src/components/shared/keyboard-shortcuts-dialog.tsx +116 -0
- package/src/components/shared/notification-center.tsx +223 -0
- package/src/components/shared/profile-sheet.tsx +115 -0
- package/src/components/shared/reply-quote.tsx +26 -0
- package/src/components/shared/search-dialog.tsx +296 -0
- package/src/components/shared/section-label.tsx +12 -0
- package/src/components/shared/settings/plugin-manager.tsx +1 -1
- package/src/components/shared/settings/section-providers.tsx +1 -1
- package/src/components/shared/settings/section-secrets.tsx +1 -1
- package/src/components/shared/settings/section-theme.tsx +95 -0
- package/src/components/shared/settings/section-user-preferences.tsx +39 -0
- package/src/components/shared/settings/settings-page.tsx +180 -27
- package/src/components/shared/settings/settings-sheet.tsx +9 -73
- package/src/components/shared/sheet-footer.tsx +33 -0
- package/src/components/skills/skill-list.tsx +61 -30
- package/src/components/skills/skill-sheet.tsx +81 -2
- package/src/components/tasks/task-board.tsx +448 -26
- package/src/components/tasks/task-card.tsx +46 -9
- package/src/components/tasks/task-column.tsx +62 -3
- package/src/components/tasks/task-list.tsx +12 -4
- package/src/components/tasks/task-sheet.tsx +89 -72
- package/src/components/ui/hover-card.tsx +52 -0
- package/src/components/usage/metrics-dashboard.tsx +78 -0
- package/src/components/usage/usage-list.tsx +1 -1
- package/src/components/webhooks/webhook-sheet.tsx +1 -1
- package/src/hooks/use-view-router.ts +69 -19
- package/src/instrumentation.ts +15 -1
- package/src/lib/chat.ts +2 -0
- package/src/lib/cron-human.ts +114 -0
- package/src/lib/memory.ts +3 -0
- package/src/lib/server/chat-execution.ts +24 -4
- package/src/lib/server/connectors/manager.ts +11 -0
- package/src/lib/server/context-manager.ts +225 -13
- package/src/lib/server/create-notification.ts +42 -0
- package/src/lib/server/daemon-state.ts +165 -10
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service.ts +40 -5
- package/src/lib/server/heartbeat-wake.ts +110 -0
- package/src/lib/server/langgraph-checkpoint.ts +1 -0
- package/src/lib/server/memory-consolidation.ts +92 -0
- package/src/lib/server/memory-db.ts +51 -6
- package/src/lib/server/openclaw-gateway.ts +9 -1
- package/src/lib/server/provider-health.ts +125 -0
- package/src/lib/server/queue.ts +5 -4
- package/src/lib/server/scheduler.ts +8 -0
- package/src/lib/server/session-run-manager.ts +4 -0
- package/src/lib/server/session-tools/chatroom.ts +136 -0
- package/src/lib/server/session-tools/context-mgmt.ts +36 -18
- package/src/lib/server/session-tools/index.ts +2 -0
- package/src/lib/server/session-tools/memory.ts +6 -1
- package/src/lib/server/storage.ts +80 -29
- package/src/lib/server/stream-agent-chat.ts +153 -47
- package/src/lib/server/system-events.ts +49 -0
- package/src/lib/server/ws-hub.ts +11 -0
- package/src/lib/soul-suggestions.ts +109 -0
- package/src/lib/tasks.ts +4 -1
- package/src/lib/view-routes.ts +36 -1
- package/src/lib/ws-client.ts +14 -4
- package/src/proxy.ts +79 -2
- package/src/stores/use-app-store.ts +94 -3
- package/src/stores/use-chat-store.ts +48 -3
- package/src/stores/use-chatroom-store.ts +276 -0
- package/src/types/index.ts +69 -2
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useEffect, useState, useCallback } from 'react'
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { getMemory, updateMemory, deleteMemory } from '@/lib/memory'
|
|
6
|
+
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
6
7
|
import type { MemoryEntry } from '@/types'
|
|
7
8
|
|
|
8
9
|
const CATEGORIES = ['note', 'fact', 'preference', 'finding', 'learning', 'general']
|
|
@@ -17,17 +18,23 @@ export function MemoryDetail() {
|
|
|
17
18
|
const setActiveView = useAppStore((s) => s.setActiveView)
|
|
18
19
|
|
|
19
20
|
const [entry, setEntry] = useState<MemoryEntry | null>(null)
|
|
21
|
+
const [editing, setEditing] = useState(false)
|
|
20
22
|
const [title, setTitle] = useState('')
|
|
21
23
|
const [content, setContent] = useState('')
|
|
22
24
|
const [category, setCategory] = useState('note')
|
|
23
|
-
const [
|
|
25
|
+
const [editAgentId, setEditAgentId] = useState<string | null>(null)
|
|
26
|
+
const [editSharedWith, setEditSharedWith] = useState<string[]>([])
|
|
24
27
|
const [saving, setSaving] = useState(false)
|
|
25
28
|
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
29
|
+
const [linkedTitles, setLinkedTitles] = useState<Record<string, string>>({})
|
|
30
|
+
const [refsExpanded, setRefsExpanded] = useState(false)
|
|
31
|
+
const [metaExpanded, setMetaExpanded] = useState(false)
|
|
26
32
|
|
|
27
33
|
// Load memory entry when selection changes
|
|
28
34
|
useEffect(() => {
|
|
29
35
|
if (!selectedId) {
|
|
30
36
|
setEntry(null)
|
|
37
|
+
setEditing(false)
|
|
31
38
|
return
|
|
32
39
|
}
|
|
33
40
|
|
|
@@ -46,7 +53,11 @@ export function MemoryDetail() {
|
|
|
46
53
|
setTitle(resolved.title)
|
|
47
54
|
setContent(resolved.content)
|
|
48
55
|
setCategory(resolved.category || 'note')
|
|
49
|
-
|
|
56
|
+
setEditAgentId(resolved.agentId || null)
|
|
57
|
+
setEditSharedWith(resolved.sharedWith || [])
|
|
58
|
+
setEditing(false)
|
|
59
|
+
setRefsExpanded(false)
|
|
60
|
+
setMetaExpanded(false)
|
|
50
61
|
})
|
|
51
62
|
.catch((err) => console.error('Memory operation failed:', err))
|
|
52
63
|
|
|
@@ -55,29 +66,69 @@ export function MemoryDetail() {
|
|
|
55
66
|
}
|
|
56
67
|
}, [selectedId])
|
|
57
68
|
|
|
69
|
+
// Resolve linked memory titles
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!entry?.linkedMemoryIds?.length) {
|
|
72
|
+
setLinkedTitles({})
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
let cancelled = false
|
|
76
|
+
Promise.all(
|
|
77
|
+
entry.linkedMemoryIds.map((id) =>
|
|
78
|
+
getMemory(id, { depth: 0 }).then((m) => {
|
|
79
|
+
const resolved = Array.isArray(m) ? m[0] : m
|
|
80
|
+
return [id, resolved?.title || id] as const
|
|
81
|
+
}).catch(() => [id, id] as const),
|
|
82
|
+
),
|
|
83
|
+
).then((pairs) => {
|
|
84
|
+
if (cancelled) return
|
|
85
|
+
setLinkedTitles(Object.fromEntries(pairs))
|
|
86
|
+
})
|
|
87
|
+
return () => { cancelled = true }
|
|
88
|
+
}, [entry?.linkedMemoryIds])
|
|
89
|
+
|
|
58
90
|
const handleSave = useCallback(async () => {
|
|
59
|
-
if (!entry
|
|
91
|
+
if (!entry) return
|
|
60
92
|
setSaving(true)
|
|
61
93
|
try {
|
|
62
|
-
const updated = await updateMemory(entry.id, {
|
|
94
|
+
const updated = await updateMemory(entry.id, {
|
|
95
|
+
title,
|
|
96
|
+
content,
|
|
97
|
+
category,
|
|
98
|
+
agentId: editAgentId,
|
|
99
|
+
sharedWith: editSharedWith.length ? editSharedWith : undefined,
|
|
100
|
+
})
|
|
63
101
|
setEntry(updated)
|
|
64
|
-
|
|
102
|
+
setEditing(false)
|
|
65
103
|
triggerRefresh()
|
|
66
104
|
} catch { /* ignore */ }
|
|
67
105
|
setSaving(false)
|
|
68
|
-
|
|
106
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
107
|
+
}, [entry, title, content, category, editAgentId, editSharedWith])
|
|
69
108
|
|
|
70
109
|
const handleDelete = useCallback(async () => {
|
|
71
110
|
if (!entry) return
|
|
72
111
|
await deleteMemory(entry.id)
|
|
73
112
|
setSelectedId(null)
|
|
74
113
|
triggerRefresh()
|
|
114
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
115
|
+
}, [entry])
|
|
116
|
+
|
|
117
|
+
const handleTogglePin = useCallback(async () => {
|
|
118
|
+
if (!entry) return
|
|
119
|
+
try {
|
|
120
|
+
const updated = await updateMemory(entry.id, { pinned: !entry.pinned })
|
|
121
|
+
setEntry(updated)
|
|
122
|
+
triggerRefresh()
|
|
123
|
+
} catch { /* ignore */ }
|
|
124
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
75
125
|
}, [entry])
|
|
76
126
|
|
|
77
127
|
const handleNavigateToSession = useCallback(() => {
|
|
78
128
|
if (!entry?.sessionId) return
|
|
79
129
|
setActiveView('agents')
|
|
80
130
|
setCurrentSession(entry.sessionId)
|
|
131
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
81
132
|
}, [entry])
|
|
82
133
|
|
|
83
134
|
if (!entry) {
|
|
@@ -90,9 +141,9 @@ export function MemoryDetail() {
|
|
|
90
141
|
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
|
91
142
|
</svg>
|
|
92
143
|
</div>
|
|
93
|
-
<p className="font-display text-[17px] font-600 text-text-2">Memory</p>
|
|
144
|
+
<p className="font-display text-[17px] font-600 text-text-2">Select a Memory</p>
|
|
94
145
|
<p className="text-[13px] text-text-3/70 max-w-[300px]">
|
|
95
|
-
|
|
146
|
+
Choose a memory from the list to view its details
|
|
96
147
|
</p>
|
|
97
148
|
</div>
|
|
98
149
|
)
|
|
@@ -108,6 +159,8 @@ export function MemoryDetail() {
|
|
|
108
159
|
: null
|
|
109
160
|
|
|
110
161
|
const inputClass = "w-full px-4 py-3 rounded-[12px] border border-white/[0.06] bg-white/[0.02] text-text outline-none transition-all duration-200 placeholder:text-text-3/70 focus:border-accent-bright/20 focus:bg-white/[0.03]"
|
|
162
|
+
const refs = entry.references || []
|
|
163
|
+
const showRefsCollapse = refs.length > 3
|
|
111
164
|
|
|
112
165
|
return (
|
|
113
166
|
<div className="flex-1 flex flex-col h-full min-h-0">
|
|
@@ -116,9 +169,11 @@ export function MemoryDetail() {
|
|
|
116
169
|
<div className="flex-1 min-w-0">
|
|
117
170
|
<div className="flex items-center gap-2.5">
|
|
118
171
|
<span className="shrink-0 text-[10px] font-700 uppercase tracking-wider text-accent-bright/70 bg-accent-soft px-2 py-0.5 rounded-[6px]">
|
|
119
|
-
{category}
|
|
172
|
+
{entry.category || 'note'}
|
|
120
173
|
</span>
|
|
121
|
-
|
|
174
|
+
{!editing && (
|
|
175
|
+
<h2 className="font-display text-[16px] font-700 truncate tracking-[-0.02em]">{entry.title || 'Untitled'}</h2>
|
|
176
|
+
)}
|
|
122
177
|
</div>
|
|
123
178
|
<div className="flex items-center gap-3 mt-1">
|
|
124
179
|
{agentName && (
|
|
@@ -143,16 +198,55 @@ export function MemoryDetail() {
|
|
|
143
198
|
</div>
|
|
144
199
|
|
|
145
200
|
<div className="flex items-center gap-2 shrink-0">
|
|
146
|
-
{
|
|
201
|
+
{/* Pin/unpin toggle */}
|
|
202
|
+
<button
|
|
203
|
+
onClick={handleTogglePin}
|
|
204
|
+
className={`p-2 rounded-[8px] cursor-pointer transition-all bg-transparent border-none
|
|
205
|
+
${entry.pinned ? 'text-amber-400 hover:text-amber-300' : 'text-text-3/40 hover:text-amber-400/70'}`}
|
|
206
|
+
title={entry.pinned ? 'Unpin memory' : 'Pin memory (always preloaded)'}
|
|
207
|
+
>
|
|
208
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill={entry.pinned ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
209
|
+
<path d="M12 17v5" /><path d="M9 2h6l-1.5 6H16l1 4H7l1-4h1.5z" />
|
|
210
|
+
</svg>
|
|
211
|
+
</button>
|
|
212
|
+
{editing ? (
|
|
213
|
+
<>
|
|
214
|
+
<button
|
|
215
|
+
onClick={() => {
|
|
216
|
+
setTitle(entry.title)
|
|
217
|
+
setContent(entry.content)
|
|
218
|
+
setCategory(entry.category || 'note')
|
|
219
|
+
setEditAgentId(entry.agentId || null)
|
|
220
|
+
setEditSharedWith(entry.sharedWith || [])
|
|
221
|
+
setEditing(false)
|
|
222
|
+
}}
|
|
223
|
+
className="px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-2 text-[12px] font-600 cursor-pointer hover:bg-surface-2 transition-all"
|
|
224
|
+
style={{ fontFamily: 'inherit' }}
|
|
225
|
+
>
|
|
226
|
+
Cancel
|
|
227
|
+
</button>
|
|
228
|
+
<button
|
|
229
|
+
onClick={handleSave}
|
|
230
|
+
disabled={saving}
|
|
231
|
+
className="px-4 py-2 rounded-[10px] bg-accent-bright text-white text-[12px] font-600
|
|
232
|
+
cursor-pointer border-none transition-all hover:brightness-110 active:scale-[0.97]
|
|
233
|
+
disabled:opacity-50 shadow-[0_2px_10px_rgba(99,102,241,0.2)]"
|
|
234
|
+
style={{ fontFamily: 'inherit' }}
|
|
235
|
+
>
|
|
236
|
+
{saving ? 'Saving...' : 'Save'}
|
|
237
|
+
</button>
|
|
238
|
+
</>
|
|
239
|
+
) : (
|
|
147
240
|
<button
|
|
148
|
-
onClick={
|
|
149
|
-
|
|
150
|
-
className="px-4 py-2 rounded-[10px] bg-[#6366F1] text-white text-[12px] font-600
|
|
151
|
-
cursor-pointer border-none transition-all hover:brightness-110 active:scale-[0.97]
|
|
152
|
-
disabled:opacity-50 shadow-[0_2px_10px_rgba(99,102,241,0.2)]"
|
|
241
|
+
onClick={() => setEditing(true)}
|
|
242
|
+
className="px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-2 text-[12px] font-600 cursor-pointer hover:bg-white/[0.04] transition-all flex items-center gap-1.5"
|
|
153
243
|
style={{ fontFamily: 'inherit' }}
|
|
154
244
|
>
|
|
155
|
-
|
|
245
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
246
|
+
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
247
|
+
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
248
|
+
</svg>
|
|
249
|
+
Edit
|
|
156
250
|
</button>
|
|
157
251
|
)}
|
|
158
252
|
<button
|
|
@@ -169,138 +263,283 @@ export function MemoryDetail() {
|
|
|
169
263
|
</div>
|
|
170
264
|
</div>
|
|
171
265
|
|
|
172
|
-
{/*
|
|
266
|
+
{/* Content area */}
|
|
173
267
|
<div className="flex-1 overflow-y-auto px-6 py-5">
|
|
174
|
-
<div className="max-w-[
|
|
175
|
-
{
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
268
|
+
<div className="max-w-[720px] space-y-5">
|
|
269
|
+
{editing ? (
|
|
270
|
+
<>
|
|
271
|
+
{/* Title input */}
|
|
272
|
+
<div>
|
|
273
|
+
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Title</label>
|
|
274
|
+
<input
|
|
275
|
+
type="text"
|
|
276
|
+
value={title}
|
|
277
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
278
|
+
className={`${inputClass} text-[15px] font-600`}
|
|
279
|
+
style={{ fontFamily: 'inherit' }}
|
|
280
|
+
placeholder="Memory title"
|
|
281
|
+
/>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
{/* Category picker */}
|
|
285
|
+
<div>
|
|
286
|
+
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Category</label>
|
|
287
|
+
<div className="flex gap-1.5 flex-wrap">
|
|
288
|
+
{CATEGORIES.map((c) => (
|
|
289
|
+
<button
|
|
290
|
+
key={c}
|
|
291
|
+
onClick={() => setCategory(c)}
|
|
292
|
+
className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 capitalize cursor-pointer transition-all border-none
|
|
293
|
+
${category === c
|
|
294
|
+
? 'bg-accent-soft text-accent-bright'
|
|
295
|
+
: 'bg-white/[0.03] text-text-3 hover:text-text-2 hover:bg-white/[0.05]'}`}
|
|
296
|
+
style={{ fontFamily: 'inherit' }}
|
|
297
|
+
>
|
|
298
|
+
{c}
|
|
299
|
+
</button>
|
|
300
|
+
))}
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
187
303
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
304
|
+
{/* Agent assignment */}
|
|
305
|
+
<div>
|
|
306
|
+
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Assigned to</label>
|
|
307
|
+
<div className="flex gap-1.5 flex-wrap">
|
|
308
|
+
<button
|
|
309
|
+
onClick={() => setEditAgentId(null)}
|
|
310
|
+
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all border
|
|
311
|
+
${!editAgentId
|
|
312
|
+
? 'bg-accent-soft border-accent-bright/20 text-accent-bright'
|
|
313
|
+
: 'bg-white/[0.02] border-white/[0.06] text-text-3 hover:text-text-2 hover:bg-white/[0.04]'}`}
|
|
314
|
+
style={{ fontFamily: 'inherit' }}
|
|
315
|
+
>
|
|
316
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className={!editAgentId ? 'text-accent-bright' : 'text-text-3/60'}>
|
|
317
|
+
<circle cx="12" cy="12" r="10" /><line x1="2" y1="12" x2="22" y2="12" />
|
|
318
|
+
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
|
319
|
+
</svg>
|
|
320
|
+
Global
|
|
321
|
+
</button>
|
|
322
|
+
{Object.values(agents).sort((a, b) => a.name.localeCompare(b.name)).map((agent) => (
|
|
323
|
+
<button
|
|
324
|
+
key={agent.id}
|
|
325
|
+
onClick={() => setEditAgentId(agent.id)}
|
|
326
|
+
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all border
|
|
327
|
+
${editAgentId === agent.id
|
|
328
|
+
? 'bg-accent-soft border-accent-bright/20 text-accent-bright'
|
|
329
|
+
: 'bg-white/[0.02] border-white/[0.06] text-text-3 hover:text-text-2 hover:bg-white/[0.04]'}`}
|
|
330
|
+
style={{ fontFamily: 'inherit' }}
|
|
331
|
+
>
|
|
332
|
+
<AgentAvatar seed={agent.avatarSeed || null} name={agent.name} size={16} />
|
|
333
|
+
<span className="truncate max-w-[100px]">{agent.name}</span>
|
|
334
|
+
</button>
|
|
335
|
+
))}
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
{/* Shared with */}
|
|
340
|
+
{editAgentId && (
|
|
341
|
+
<div>
|
|
342
|
+
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Share with</label>
|
|
343
|
+
<div className="flex gap-1.5 flex-wrap">
|
|
344
|
+
{Object.values(agents)
|
|
345
|
+
.filter((a) => a.id !== editAgentId)
|
|
346
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
347
|
+
.map((agent) => {
|
|
348
|
+
const isShared = editSharedWith.includes(agent.id)
|
|
349
|
+
return (
|
|
350
|
+
<button
|
|
351
|
+
key={agent.id}
|
|
352
|
+
onClick={() => {
|
|
353
|
+
setEditSharedWith(isShared
|
|
354
|
+
? editSharedWith.filter((id) => id !== agent.id)
|
|
355
|
+
: [...editSharedWith, agent.id])
|
|
356
|
+
}}
|
|
357
|
+
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all border
|
|
358
|
+
${isShared
|
|
359
|
+
? 'bg-accent-soft border-accent-bright/20 text-accent-bright'
|
|
360
|
+
: 'bg-white/[0.02] border-white/[0.06] text-text-3 hover:text-text-2 hover:bg-white/[0.04]'}`}
|
|
361
|
+
style={{ fontFamily: 'inherit' }}
|
|
362
|
+
>
|
|
363
|
+
<AgentAvatar seed={agent.avatarSeed || null} name={agent.name} size={16} />
|
|
364
|
+
<span className="truncate max-w-[100px]">{agent.name}</span>
|
|
365
|
+
</button>
|
|
366
|
+
)
|
|
367
|
+
})}
|
|
368
|
+
</div>
|
|
369
|
+
{editSharedWith.length === 0 && (
|
|
370
|
+
<p className="text-[10px] text-text-3/40 mt-1.5">No agents selected — only the assigned agent can access this memory</p>
|
|
371
|
+
)}
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
374
|
+
|
|
375
|
+
{/* Content textarea */}
|
|
376
|
+
<div>
|
|
377
|
+
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Content</label>
|
|
378
|
+
<textarea
|
|
379
|
+
value={content}
|
|
380
|
+
onChange={(e) => setContent(e.target.value)}
|
|
381
|
+
placeholder="Memory content..."
|
|
382
|
+
rows={12}
|
|
383
|
+
className={`${inputClass} text-[14px] resize-y min-h-[200px] leading-relaxed`}
|
|
200
384
|
style={{ fontFamily: 'inherit' }}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
385
|
+
/>
|
|
386
|
+
</div>
|
|
387
|
+
</>
|
|
388
|
+
) : (
|
|
389
|
+
<>
|
|
390
|
+
{/* Read-mode: Title as h1 */}
|
|
391
|
+
<h1 className="font-display text-[22px] font-700 tracking-[-0.02em] text-text leading-tight">
|
|
392
|
+
{entry.title || 'Untitled'}
|
|
393
|
+
</h1>
|
|
207
394
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
395
|
+
{/* Read-mode: Content as readable prose */}
|
|
396
|
+
<div className="text-[15px] leading-[1.7] text-text-2 whitespace-pre-wrap break-words">
|
|
397
|
+
{entry.content || '(empty)'}
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
{/* Shared with (read mode) */}
|
|
401
|
+
{entry.sharedWith && entry.sharedWith.length > 0 && (
|
|
402
|
+
<div>
|
|
403
|
+
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Shared with</label>
|
|
404
|
+
<div className="flex gap-1.5 flex-wrap">
|
|
405
|
+
{entry.sharedWith.map((aid) => {
|
|
406
|
+
const a = agents[aid]
|
|
407
|
+
return (
|
|
408
|
+
<span key={aid} className="flex items-center gap-1.5 px-2.5 py-1 rounded-[8px] bg-white/[0.03] text-[11px] text-text-3">
|
|
409
|
+
<AgentAvatar seed={a?.avatarSeed || null} name={a?.name || aid} size={16} />
|
|
410
|
+
{a?.name || aid}
|
|
411
|
+
</span>
|
|
412
|
+
)
|
|
413
|
+
})}
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
)}
|
|
417
|
+
</>
|
|
418
|
+
)}
|
|
220
419
|
|
|
420
|
+
{/* Image (both modes) */}
|
|
221
421
|
{imageUrl && (
|
|
222
422
|
<div>
|
|
223
|
-
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Image</label>
|
|
423
|
+
{editing && <label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Image</label>}
|
|
224
424
|
<a href={imageUrl} target="_blank" rel="noreferrer" className="inline-block rounded-[12px] overflow-hidden border border-white/[0.08]">
|
|
225
|
-
<img src={imageUrl} alt={entry.title} className="max-w-[
|
|
425
|
+
<img src={imageUrl} alt={entry.title} className="max-w-[600px] w-full max-h-[400px] object-cover block" />
|
|
226
426
|
</a>
|
|
227
427
|
</div>
|
|
228
428
|
)}
|
|
229
429
|
|
|
230
|
-
{
|
|
231
|
-
<div>
|
|
232
|
-
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">References</label>
|
|
233
|
-
<div className="space-y-2">
|
|
234
|
-
{entry.references.map((ref, idx) => (
|
|
235
|
-
<div key={`${ref.type}-${ref.path || ref.title || idx}`} className="text-[12px] rounded-[10px] border border-white/[0.06] bg-white/[0.02] px-3 py-2">
|
|
236
|
-
<div className="text-text-2/70">
|
|
237
|
-
<span className="uppercase text-[10px] tracking-[0.06em] mr-1">{ref.type}</span>
|
|
238
|
-
{ref.path || ref.title || '(no path)'}
|
|
239
|
-
</div>
|
|
240
|
-
{(ref.projectName || ref.projectRoot || ref.note || typeof ref.exists === 'boolean') && (
|
|
241
|
-
<div className="text-text-3/55 mt-1">
|
|
242
|
-
{ref.projectName ? `project: ${ref.projectName} ` : ''}
|
|
243
|
-
{ref.projectRoot ? `root: ${ref.projectRoot} ` : ''}
|
|
244
|
-
{typeof ref.exists === 'boolean' ? (ref.exists ? 'exists' : 'missing') : ''}
|
|
245
|
-
{ref.note ? ` — ${ref.note}` : ''}
|
|
246
|
-
</div>
|
|
247
|
-
)}
|
|
248
|
-
</div>
|
|
249
|
-
))}
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
) : null}
|
|
253
|
-
|
|
430
|
+
{/* Linked Memories */}
|
|
254
431
|
{entry.linkedMemoryIds?.length ? (
|
|
255
432
|
<div>
|
|
256
433
|
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Linked Memories</label>
|
|
257
|
-
<div className="flex flex-
|
|
434
|
+
<div className="flex flex-col gap-1.5">
|
|
258
435
|
{entry.linkedMemoryIds.map((id) => (
|
|
259
436
|
<button
|
|
260
437
|
key={id}
|
|
261
438
|
onClick={() => setSelectedId(id)}
|
|
262
|
-
className="
|
|
439
|
+
className="flex items-center gap-2.5 px-3 py-2 rounded-[10px] bg-white/[0.02] border border-white/[0.06] hover:bg-white/[0.04] cursor-pointer transition-colors text-left w-full"
|
|
263
440
|
>
|
|
264
|
-
|
|
441
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright/60 shrink-0">
|
|
442
|
+
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
|
|
443
|
+
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
|
|
444
|
+
</svg>
|
|
445
|
+
<span className="text-[13px] text-text-2 truncate">
|
|
446
|
+
{linkedTitles[id] || id}
|
|
447
|
+
</span>
|
|
265
448
|
</button>
|
|
266
449
|
))}
|
|
267
450
|
</div>
|
|
268
451
|
</div>
|
|
269
452
|
) : null}
|
|
270
453
|
|
|
271
|
-
{/*
|
|
272
|
-
|
|
273
|
-
<div
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
<
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
454
|
+
{/* References (collapsible) */}
|
|
455
|
+
{refs.length > 0 && (
|
|
456
|
+
<div>
|
|
457
|
+
<button
|
|
458
|
+
onClick={() => setRefsExpanded(!refsExpanded)}
|
|
459
|
+
className="flex items-center gap-1.5 text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2 bg-transparent border-none cursor-pointer p-0 hover:text-text-3 transition-colors"
|
|
460
|
+
style={{ fontFamily: 'inherit' }}
|
|
461
|
+
>
|
|
462
|
+
<svg
|
|
463
|
+
width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
|
|
464
|
+
className={`transition-transform ${refsExpanded || !showRefsCollapse ? 'rotate-90' : ''}`}
|
|
465
|
+
>
|
|
466
|
+
<polyline points="9 18 15 12 9 6" />
|
|
467
|
+
</svg>
|
|
468
|
+
References ({refs.length})
|
|
469
|
+
</button>
|
|
470
|
+
{(refsExpanded || !showRefsCollapse) && (
|
|
471
|
+
<div className="space-y-2">
|
|
472
|
+
{refs.map((ref, idx) => (
|
|
473
|
+
<div key={`${ref.type}-${ref.path || ref.title || idx}`} className="text-[12px] rounded-[10px] border border-white/[0.06] bg-white/[0.02] px-3 py-2">
|
|
474
|
+
<div className="text-text-2/70">
|
|
475
|
+
<span className="uppercase text-[10px] tracking-[0.06em] mr-1">{ref.type}</span>
|
|
476
|
+
{ref.path || ref.title || '(no path)'}
|
|
477
|
+
</div>
|
|
478
|
+
{(ref.projectName || ref.projectRoot || ref.note || typeof ref.exists === 'boolean') && (
|
|
479
|
+
<div className="text-text-3/55 mt-1">
|
|
480
|
+
{ref.projectName ? `project: ${ref.projectName} ` : ''}
|
|
481
|
+
{ref.projectRoot ? `root: ${ref.projectRoot} ` : ''}
|
|
482
|
+
{typeof ref.exists === 'boolean' ? (ref.exists ? 'exists' : 'missing') : ''}
|
|
483
|
+
{ref.note ? ` — ${ref.note}` : ''}
|
|
484
|
+
</div>
|
|
485
|
+
)}
|
|
486
|
+
</div>
|
|
487
|
+
))}
|
|
301
488
|
</div>
|
|
302
489
|
)}
|
|
303
490
|
</div>
|
|
491
|
+
)}
|
|
492
|
+
|
|
493
|
+
{/* Metadata (disclosure) */}
|
|
494
|
+
<div className="pt-2">
|
|
495
|
+
<button
|
|
496
|
+
onClick={() => setMetaExpanded(!metaExpanded)}
|
|
497
|
+
className="flex items-center gap-1.5 text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] bg-transparent border-none cursor-pointer p-0 hover:text-text-3 transition-colors"
|
|
498
|
+
style={{ fontFamily: 'inherit' }}
|
|
499
|
+
>
|
|
500
|
+
<svg
|
|
501
|
+
width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"
|
|
502
|
+
className={`transition-transform ${metaExpanded ? 'rotate-90' : ''}`}
|
|
503
|
+
>
|
|
504
|
+
<polyline points="9 18 15 12 9 6" />
|
|
505
|
+
</svg>
|
|
506
|
+
Details
|
|
507
|
+
</button>
|
|
508
|
+
{metaExpanded && (
|
|
509
|
+
<div className="mt-3 pt-3 border-t border-white/[0.04]">
|
|
510
|
+
<div className="grid grid-cols-2 gap-4 text-[11px]">
|
|
511
|
+
<div>
|
|
512
|
+
<span className="text-text-3/70 block mb-1">ID</span>
|
|
513
|
+
<span className="text-text-3/60 font-mono">{entry.id}</span>
|
|
514
|
+
</div>
|
|
515
|
+
<div>
|
|
516
|
+
<span className="text-text-3/70 block mb-1">Created</span>
|
|
517
|
+
<span className="text-text-3/60 font-mono">{new Date(entry.createdAt).toLocaleString()}</span>
|
|
518
|
+
</div>
|
|
519
|
+
<div>
|
|
520
|
+
<span className="text-text-3/70 block mb-1">Updated</span>
|
|
521
|
+
<span className="text-text-3/60 font-mono">{new Date(entry.updatedAt).toLocaleString()}</span>
|
|
522
|
+
</div>
|
|
523
|
+
{entry.agentId && (
|
|
524
|
+
<div>
|
|
525
|
+
<span className="text-text-3/70 block mb-1">Agent</span>
|
|
526
|
+
<span className="text-text-3/60 font-mono">{agentName}</span>
|
|
527
|
+
</div>
|
|
528
|
+
)}
|
|
529
|
+
{entry.sessionId && (
|
|
530
|
+
<div>
|
|
531
|
+
<span className="text-text-3/70 block mb-1">Chat</span>
|
|
532
|
+
<button
|
|
533
|
+
onClick={handleNavigateToSession}
|
|
534
|
+
className="text-accent-bright/60 hover:text-accent-bright font-mono bg-transparent border-none cursor-pointer p-0 text-[11px] transition-colors"
|
|
535
|
+
>
|
|
536
|
+
{sessionName}
|
|
537
|
+
</button>
|
|
538
|
+
</div>
|
|
539
|
+
)}
|
|
540
|
+
</div>
|
|
541
|
+
</div>
|
|
542
|
+
)}
|
|
304
543
|
</div>
|
|
305
544
|
</div>
|
|
306
545
|
</div>
|