@swarmclawai/swarmclaw 0.2.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 +577 -0
- package/bin/server-cmd.js +359 -0
- package/bin/swarmclaw.js +29 -0
- package/bin/swarmclaw.mjs +1504 -0
- package/next.config.ts +33 -0
- package/package.json +112 -0
- package/postcss.config.mjs +7 -0
- package/public/branding/swarmclaw-org-avatar.png +0 -0
- package/public/branding/swarmclaw-org-avatar.svg +58 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/screenshots/agents.png +0 -0
- package/public/screenshots/connectors.png +0 -0
- package/public/screenshots/dashboard.png +0 -0
- package/public/screenshots/new-session-openclaw.png +0 -0
- package/public/screenshots/providers.png +0 -0
- package/public/screenshots/schedules.png +0 -0
- package/public/screenshots/tasks.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/src/app/api/agents/[id]/route.ts +30 -0
- package/src/app/api/agents/[id]/thread/route.ts +66 -0
- package/src/app/api/agents/generate/route.ts +42 -0
- package/src/app/api/agents/route.ts +33 -0
- package/src/app/api/auth/route.ts +25 -0
- package/src/app/api/claude-skills/route.ts +42 -0
- package/src/app/api/clawhub/install/route.ts +39 -0
- package/src/app/api/clawhub/search/route.ts +11 -0
- package/src/app/api/connectors/[id]/route.ts +79 -0
- package/src/app/api/connectors/route.ts +60 -0
- package/src/app/api/credentials/[id]/route.ts +14 -0
- package/src/app/api/credentials/route.ts +31 -0
- package/src/app/api/daemon/health-check/route.ts +11 -0
- package/src/app/api/daemon/route.ts +22 -0
- package/src/app/api/dirs/pick/route.ts +60 -0
- package/src/app/api/dirs/route.ts +29 -0
- package/src/app/api/documents/[id]/route.ts +47 -0
- package/src/app/api/documents/route.ts +93 -0
- package/src/app/api/files/serve/route.ts +69 -0
- package/src/app/api/generate/info/route.ts +12 -0
- package/src/app/api/generate/route.ts +106 -0
- package/src/app/api/ip/route.ts +6 -0
- package/src/app/api/knowledge/[id]/route.ts +61 -0
- package/src/app/api/knowledge/route.ts +48 -0
- package/src/app/api/knowledge/upload/route.ts +86 -0
- package/src/app/api/logs/route.ts +65 -0
- package/src/app/api/mcp-servers/[id]/route.ts +32 -0
- package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
- package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
- package/src/app/api/mcp-servers/route.ts +27 -0
- package/src/app/api/memory/[id]/route.ts +126 -0
- package/src/app/api/memory/maintenance/route.ts +63 -0
- package/src/app/api/memory/route.ts +111 -0
- package/src/app/api/memory-images/[filename]/route.ts +36 -0
- package/src/app/api/orchestrator/run/route.ts +43 -0
- package/src/app/api/plugins/install/route.ts +58 -0
- package/src/app/api/plugins/marketplace/route.ts +33 -0
- package/src/app/api/plugins/route.ts +21 -0
- package/src/app/api/preview-server/route.ts +339 -0
- package/src/app/api/providers/[id]/models/route.ts +29 -0
- package/src/app/api/providers/[id]/route.ts +34 -0
- package/src/app/api/providers/configs/route.ts +7 -0
- package/src/app/api/providers/ollama/route.ts +30 -0
- package/src/app/api/providers/openclaw/health/route.ts +23 -0
- package/src/app/api/providers/route.ts +28 -0
- package/src/app/api/runs/[id]/route.ts +9 -0
- package/src/app/api/runs/route.ts +13 -0
- package/src/app/api/schedules/[id]/route.ts +28 -0
- package/src/app/api/schedules/[id]/run/route.ts +104 -0
- package/src/app/api/schedules/route.ts +78 -0
- package/src/app/api/secrets/[id]/route.ts +29 -0
- package/src/app/api/secrets/route.ts +42 -0
- package/src/app/api/sessions/[id]/browser/route.ts +13 -0
- package/src/app/api/sessions/[id]/chat/route.ts +96 -0
- package/src/app/api/sessions/[id]/clear/route.ts +19 -0
- package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
- package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
- package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
- package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
- package/src/app/api/sessions/[id]/messages/route.ts +9 -0
- package/src/app/api/sessions/[id]/retry/route.ts +28 -0
- package/src/app/api/sessions/[id]/route.ts +103 -0
- package/src/app/api/sessions/[id]/stop/route.ts +13 -0
- package/src/app/api/sessions/heartbeat/route.ts +26 -0
- package/src/app/api/sessions/route.ts +85 -0
- package/src/app/api/settings/route.ts +58 -0
- package/src/app/api/setup/check-provider/route.ts +326 -0
- package/src/app/api/setup/doctor/route.ts +250 -0
- package/src/app/api/skills/[id]/route.ts +40 -0
- package/src/app/api/skills/import/route.ts +69 -0
- package/src/app/api/skills/route.ts +28 -0
- package/src/app/api/tasks/[id]/route.ts +102 -0
- package/src/app/api/tasks/route.ts +115 -0
- package/src/app/api/tts/route.ts +40 -0
- package/src/app/api/upload/route.ts +18 -0
- package/src/app/api/uploads/[filename]/route.ts +59 -0
- package/src/app/api/usage/route.ts +35 -0
- package/src/app/api/version/route.ts +81 -0
- package/src/app/api/version/update/route.ts +95 -0
- package/src/app/api/webhooks/[id]/history/route.ts +13 -0
- package/src/app/api/webhooks/[id]/route.ts +204 -0
- package/src/app/api/webhooks/route.ts +37 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +370 -0
- package/src/app/layout.tsx +52 -0
- package/src/app/page.tsx +172 -0
- package/src/cli/index.js +1232 -0
- package/src/cli/index.test.js +281 -0
- package/src/cli/index.ts +1158 -0
- package/src/cli/spec.js +284 -0
- package/src/components/agents/agent-card.tsx +219 -0
- package/src/components/agents/agent-chat-list.tsx +165 -0
- package/src/components/agents/agent-list.tsx +110 -0
- package/src/components/agents/agent-sheet.tsx +1220 -0
- package/src/components/auth/access-key-gate.tsx +248 -0
- package/src/components/auth/setup-wizard.tsx +940 -0
- package/src/components/auth/user-picker.tsx +88 -0
- package/src/components/chat/chat-area.tsx +406 -0
- package/src/components/chat/chat-header.tsx +491 -0
- package/src/components/chat/chat-tool-toggles.tsx +161 -0
- package/src/components/chat/code-block.tsx +146 -0
- package/src/components/chat/dev-server-bar.tsx +39 -0
- package/src/components/chat/message-bubble.tsx +486 -0
- package/src/components/chat/message-list.tsx +299 -0
- package/src/components/chat/session-debug-panel.tsx +196 -0
- package/src/components/chat/streaming-bubble.tsx +85 -0
- package/src/components/chat/thinking-indicator.tsx +26 -0
- package/src/components/chat/tool-call-bubble.tsx +438 -0
- package/src/components/chat/tool-request-banner.tsx +103 -0
- package/src/components/connectors/connector-list.tsx +196 -0
- package/src/components/connectors/connector-sheet.tsx +804 -0
- package/src/components/input/chat-input.tsx +235 -0
- package/src/components/knowledge/knowledge-list.tsx +206 -0
- package/src/components/knowledge/knowledge-sheet.tsx +316 -0
- package/src/components/layout/app-layout.tsx +1016 -0
- package/src/components/layout/daemon-indicator.tsx +56 -0
- package/src/components/layout/mobile-header.tsx +31 -0
- package/src/components/layout/network-banner.tsx +17 -0
- package/src/components/layout/update-banner.tsx +130 -0
- package/src/components/logs/log-list.tsx +358 -0
- package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
- package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
- package/src/components/memory/memory-card.tsx +63 -0
- package/src/components/memory/memory-detail.tsx +339 -0
- package/src/components/memory/memory-list.tsx +198 -0
- package/src/components/memory/memory-sheet.tsx +70 -0
- package/src/components/plugins/plugin-list.tsx +60 -0
- package/src/components/plugins/plugin-sheet.tsx +311 -0
- package/src/components/providers/provider-list.tsx +96 -0
- package/src/components/providers/provider-sheet.tsx +542 -0
- package/src/components/runs/run-list.tsx +231 -0
- package/src/components/schedules/schedule-card.tsx +63 -0
- package/src/components/schedules/schedule-list.tsx +76 -0
- package/src/components/schedules/schedule-sheet.tsx +336 -0
- package/src/components/secrets/secret-sheet.tsx +180 -0
- package/src/components/secrets/secrets-list.tsx +91 -0
- package/src/components/sessions/new-session-sheet.tsx +478 -0
- package/src/components/sessions/session-card.tsx +144 -0
- package/src/components/sessions/session-list.tsx +202 -0
- package/src/components/shared/ai-gen-block.tsx +77 -0
- package/src/components/shared/avatar.tsx +48 -0
- package/src/components/shared/bottom-sheet.tsx +30 -0
- package/src/components/shared/confirm-dialog.tsx +47 -0
- package/src/components/shared/connector-platform-icon.tsx +113 -0
- package/src/components/shared/dir-browser.tsx +285 -0
- package/src/components/shared/dropdown.tsx +55 -0
- package/src/components/shared/icon-button.tsx +25 -0
- package/src/components/shared/settings/plugin-manager.tsx +207 -0
- package/src/components/shared/settings/section-capability-policy.tsx +93 -0
- package/src/components/shared/settings/section-embedding.tsx +99 -0
- package/src/components/shared/settings/section-heartbeat.tsx +168 -0
- package/src/components/shared/settings/section-memory.tsx +77 -0
- package/src/components/shared/settings/section-orchestrator.tsx +108 -0
- package/src/components/shared/settings/section-providers.tsx +181 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
- package/src/components/shared/settings/section-secrets.tsx +132 -0
- package/src/components/shared/settings/section-user-preferences.tsx +24 -0
- package/src/components/shared/settings/section-voice.tsx +53 -0
- package/src/components/shared/settings/settings-sheet.tsx +88 -0
- package/src/components/shared/settings/types.ts +7 -0
- package/src/components/shared/settings/utils.ts +13 -0
- package/src/components/shared/settings-sheet.tsx +1 -0
- package/src/components/shared/skeleton.tsx +19 -0
- package/src/components/shared/usage-badge.tsx +28 -0
- package/src/components/skills/clawhub-browser.tsx +225 -0
- package/src/components/skills/skill-list.tsx +70 -0
- package/src/components/skills/skill-sheet.tsx +254 -0
- package/src/components/tasks/task-board.tsx +96 -0
- package/src/components/tasks/task-card.tsx +179 -0
- package/src/components/tasks/task-column.tsx +73 -0
- package/src/components/tasks/task-list.tsx +118 -0
- package/src/components/tasks/task-sheet.tsx +415 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/dialog.tsx +158 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +143 -0
- package/src/components/ui/sonner.tsx +22 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +56 -0
- package/src/components/usage/usage-list.tsx +105 -0
- package/src/components/webhooks/webhook-list.tsx +166 -0
- package/src/components/webhooks/webhook-sheet.tsx +402 -0
- package/src/hooks/use-auto-resize.ts +20 -0
- package/src/hooks/use-media-query.ts +21 -0
- package/src/hooks/use-speech-recognition.ts +83 -0
- package/src/instrumentation.ts +8 -0
- package/src/lib/agents.ts +13 -0
- package/src/lib/api-client.ts +100 -0
- package/src/lib/chat.ts +60 -0
- package/src/lib/memory.ts +42 -0
- package/src/lib/openclaw-endpoint.test.ts +48 -0
- package/src/lib/openclaw-endpoint.ts +67 -0
- package/src/lib/provider-config.ts +13 -0
- package/src/lib/providers/anthropic.ts +135 -0
- package/src/lib/providers/claude-cli.ts +202 -0
- package/src/lib/providers/codex-cli.ts +260 -0
- package/src/lib/providers/index.ts +351 -0
- package/src/lib/providers/ollama.ts +131 -0
- package/src/lib/providers/openai.ts +164 -0
- package/src/lib/providers/openclaw.ts +330 -0
- package/src/lib/providers/opencode-cli.ts +164 -0
- package/src/lib/runtime-loop.ts +15 -0
- package/src/lib/schedule-dedupe.test.ts +84 -0
- package/src/lib/schedule-dedupe.ts +174 -0
- package/src/lib/schedule-name.ts +62 -0
- package/src/lib/schedules.ts +16 -0
- package/src/lib/server/agent-registry.ts +70 -0
- package/src/lib/server/api-routes.test.ts +362 -0
- package/src/lib/server/autonomy-contract.ts +200 -0
- package/src/lib/server/build-llm.ts +155 -0
- package/src/lib/server/capability-router.test.ts +21 -0
- package/src/lib/server/capability-router.ts +172 -0
- package/src/lib/server/chat-execution.ts +894 -0
- package/src/lib/server/clawhub-client.test.ts +161 -0
- package/src/lib/server/clawhub-client.ts +26 -0
- package/src/lib/server/connectors/connector-routing.test.ts +243 -0
- package/src/lib/server/connectors/discord.ts +116 -0
- package/src/lib/server/connectors/googlechat.ts +66 -0
- package/src/lib/server/connectors/manager.ts +559 -0
- package/src/lib/server/connectors/matrix.ts +78 -0
- package/src/lib/server/connectors/media.ts +149 -0
- package/src/lib/server/connectors/openclaw.test.ts +375 -0
- package/src/lib/server/connectors/openclaw.ts +1132 -0
- package/src/lib/server/connectors/signal.ts +183 -0
- package/src/lib/server/connectors/slack.ts +258 -0
- package/src/lib/server/connectors/teams.ts +94 -0
- package/src/lib/server/connectors/telegram.ts +221 -0
- package/src/lib/server/connectors/types.ts +62 -0
- package/src/lib/server/connectors/whatsapp.ts +349 -0
- package/src/lib/server/context-manager.ts +232 -0
- package/src/lib/server/cost.ts +31 -0
- package/src/lib/server/daemon-state.ts +354 -0
- package/src/lib/server/data-dir.ts +3 -0
- package/src/lib/server/embeddings.ts +111 -0
- package/src/lib/server/execution-log.ts +257 -0
- package/src/lib/server/gateway/protocol.test.ts +54 -0
- package/src/lib/server/gateway/protocol.ts +114 -0
- package/src/lib/server/heartbeat-service.ts +366 -0
- package/src/lib/server/knowledge-db.test.ts +441 -0
- package/src/lib/server/logger.ts +47 -0
- package/src/lib/server/main-agent-loop.ts +1017 -0
- package/src/lib/server/mcp-client.test.ts +342 -0
- package/src/lib/server/mcp-client.ts +130 -0
- package/src/lib/server/memory-db.ts +1078 -0
- package/src/lib/server/memory-graph.test.ts +153 -0
- package/src/lib/server/memory-graph.ts +138 -0
- package/src/lib/server/openclaw-health.ts +245 -0
- package/src/lib/server/orchestrator-lg.ts +431 -0
- package/src/lib/server/orchestrator.ts +364 -0
- package/src/lib/server/playwright-proxy.mjs +70 -0
- package/src/lib/server/plugins.ts +229 -0
- package/src/lib/server/process-manager.ts +327 -0
- package/src/lib/server/provider-health.ts +113 -0
- package/src/lib/server/queue.ts +859 -0
- package/src/lib/server/runtime-settings.ts +119 -0
- package/src/lib/server/scheduler.ts +196 -0
- package/src/lib/server/session-mailbox.ts +129 -0
- package/src/lib/server/session-run-manager.ts +512 -0
- package/src/lib/server/session-tools/connector.ts +124 -0
- package/src/lib/server/session-tools/context-mgmt.ts +103 -0
- package/src/lib/server/session-tools/context.ts +114 -0
- package/src/lib/server/session-tools/crud.ts +673 -0
- package/src/lib/server/session-tools/delegate.ts +708 -0
- package/src/lib/server/session-tools/file.ts +264 -0
- package/src/lib/server/session-tools/index.ts +164 -0
- package/src/lib/server/session-tools/memory.ts +230 -0
- package/src/lib/server/session-tools/session-info.ts +422 -0
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
- package/src/lib/server/session-tools/shell.ts +171 -0
- package/src/lib/server/session-tools/web.ts +408 -0
- package/src/lib/server/session-tools.ts +9 -0
- package/src/lib/server/skills-normalize.ts +130 -0
- package/src/lib/server/storage-mcp.test.ts +161 -0
- package/src/lib/server/storage.ts +670 -0
- package/src/lib/server/stream-agent-chat.ts +571 -0
- package/src/lib/server/task-reports.ts +122 -0
- package/src/lib/server/task-result.ts +161 -0
- package/src/lib/server/task-validation.test.ts +27 -0
- package/src/lib/server/task-validation.ts +90 -0
- package/src/lib/server/tool-capability-policy.test.ts +58 -0
- package/src/lib/server/tool-capability-policy.ts +262 -0
- package/src/lib/sessions.ts +68 -0
- package/src/lib/tasks.ts +20 -0
- package/src/lib/tts.ts +42 -0
- package/src/lib/upload.ts +10 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +43 -0
- package/src/stores/use-app-store.ts +468 -0
- package/src/stores/use-chat-store.ts +323 -0
- package/src/types/index.ts +621 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { getMemory, updateMemory, deleteMemory } from '@/lib/memory'
|
|
6
|
+
import type { MemoryEntry } from '@/types'
|
|
7
|
+
|
|
8
|
+
const CATEGORIES = ['note', 'fact', 'preference', 'finding', 'learning', 'general']
|
|
9
|
+
|
|
10
|
+
export function MemoryDetail() {
|
|
11
|
+
const selectedId = useAppStore((s) => s.selectedMemoryId)
|
|
12
|
+
const setSelectedId = useAppStore((s) => s.setSelectedMemoryId)
|
|
13
|
+
const triggerRefresh = useAppStore((s) => s.triggerMemoryRefresh)
|
|
14
|
+
const agents = useAppStore((s) => s.agents)
|
|
15
|
+
const sessions = useAppStore((s) => s.sessions)
|
|
16
|
+
const setCurrentSession = useAppStore((s) => s.setCurrentSession)
|
|
17
|
+
const setActiveView = useAppStore((s) => s.setActiveView)
|
|
18
|
+
|
|
19
|
+
const [entry, setEntry] = useState<MemoryEntry | null>(null)
|
|
20
|
+
const [title, setTitle] = useState('')
|
|
21
|
+
const [content, setContent] = useState('')
|
|
22
|
+
const [category, setCategory] = useState('note')
|
|
23
|
+
const [dirty, setDirty] = useState(false)
|
|
24
|
+
const [saving, setSaving] = useState(false)
|
|
25
|
+
const [confirmDelete, setConfirmDelete] = useState(false)
|
|
26
|
+
|
|
27
|
+
// Load memory entry when selection changes
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!selectedId) {
|
|
30
|
+
setEntry(null)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let cancelled = false
|
|
35
|
+
getMemory(selectedId, { depth: 0 })
|
|
36
|
+
.then((found) => {
|
|
37
|
+
if (cancelled || !found) return
|
|
38
|
+
|
|
39
|
+
const resolved = Array.isArray(found)
|
|
40
|
+
? found.find((item) => item.id === selectedId) || found[0] || null
|
|
41
|
+
: found
|
|
42
|
+
|
|
43
|
+
if (!resolved) return
|
|
44
|
+
|
|
45
|
+
setEntry(resolved)
|
|
46
|
+
setTitle(resolved.title)
|
|
47
|
+
setContent(resolved.content)
|
|
48
|
+
setCategory(resolved.category || 'note')
|
|
49
|
+
setDirty(false)
|
|
50
|
+
})
|
|
51
|
+
.catch((err) => console.error('Memory operation failed:', err))
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
cancelled = true
|
|
55
|
+
}
|
|
56
|
+
}, [selectedId])
|
|
57
|
+
|
|
58
|
+
const handleSave = useCallback(async () => {
|
|
59
|
+
if (!entry || !dirty) return
|
|
60
|
+
setSaving(true)
|
|
61
|
+
try {
|
|
62
|
+
const updated = await updateMemory(entry.id, { title, content, category })
|
|
63
|
+
setEntry(updated)
|
|
64
|
+
setDirty(false)
|
|
65
|
+
triggerRefresh()
|
|
66
|
+
} catch { /* ignore */ }
|
|
67
|
+
setSaving(false)
|
|
68
|
+
}, [entry, title, content, category, dirty])
|
|
69
|
+
|
|
70
|
+
const handleDelete = useCallback(async () => {
|
|
71
|
+
if (!entry) return
|
|
72
|
+
await deleteMemory(entry.id)
|
|
73
|
+
setSelectedId(null)
|
|
74
|
+
triggerRefresh()
|
|
75
|
+
}, [entry])
|
|
76
|
+
|
|
77
|
+
const handleNavigateToSession = useCallback(() => {
|
|
78
|
+
if (!entry?.sessionId) return
|
|
79
|
+
setActiveView('sessions')
|
|
80
|
+
setCurrentSession(entry.sessionId)
|
|
81
|
+
}, [entry])
|
|
82
|
+
|
|
83
|
+
if (!entry) {
|
|
84
|
+
return (
|
|
85
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-4 text-text-3 p-8 text-center">
|
|
86
|
+
<div className="w-14 h-14 rounded-[16px] bg-white/[0.03] flex items-center justify-center mb-2">
|
|
87
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3/60">
|
|
88
|
+
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
|
89
|
+
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
|
|
90
|
+
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
|
91
|
+
</svg>
|
|
92
|
+
</div>
|
|
93
|
+
<p className="font-display text-[17px] font-600 text-text-2">Memory</p>
|
|
94
|
+
<p className="text-[13px] text-text-3/70 max-w-[300px]">
|
|
95
|
+
Select a memory from the sidebar to view and edit it
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const agentName = entry.agentId ? (agents[entry.agentId]?.name || entry.agentId) : null
|
|
102
|
+
const sessionName = entry.sessionId ? (sessions[entry.sessionId]?.name || entry.sessionId) : null
|
|
103
|
+
const imagePath = entry.image?.path || entry.imagePath || null
|
|
104
|
+
const imageUrl = imagePath
|
|
105
|
+
? imagePath.startsWith('data/memory-images/')
|
|
106
|
+
? `/api/memory-images/${imagePath.split('/').pop()}`
|
|
107
|
+
: imagePath
|
|
108
|
+
: null
|
|
109
|
+
|
|
110
|
+
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]"
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div className="flex-1 flex flex-col h-full min-h-0">
|
|
114
|
+
{/* Header */}
|
|
115
|
+
<div className="shrink-0 px-6 py-4 border-b border-white/[0.04] flex items-center gap-3">
|
|
116
|
+
<div className="flex-1 min-w-0">
|
|
117
|
+
<div className="flex items-center gap-2.5">
|
|
118
|
+
<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}
|
|
120
|
+
</span>
|
|
121
|
+
<h2 className="font-display text-[16px] font-700 truncate tracking-[-0.02em]">{title || 'Untitled'}</h2>
|
|
122
|
+
</div>
|
|
123
|
+
<div className="flex items-center gap-3 mt-1">
|
|
124
|
+
{agentName && (
|
|
125
|
+
<span className="text-[11px] text-text-3/50 flex items-center gap-1">
|
|
126
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
|
|
127
|
+
{agentName}
|
|
128
|
+
</span>
|
|
129
|
+
)}
|
|
130
|
+
{sessionName && (
|
|
131
|
+
<button
|
|
132
|
+
onClick={handleNavigateToSession}
|
|
133
|
+
className="text-[11px] text-accent-bright/50 hover:text-accent-bright flex items-center gap-1 bg-transparent border-none cursor-pointer p-0 transition-colors"
|
|
134
|
+
>
|
|
135
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /></svg>
|
|
136
|
+
{sessionName}
|
|
137
|
+
</button>
|
|
138
|
+
)}
|
|
139
|
+
<span className="text-[10px] text-text-3/50 font-mono tabular-nums">
|
|
140
|
+
{new Date(entry.createdAt).toLocaleString()}
|
|
141
|
+
</span>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<div className="flex items-center gap-2 shrink-0">
|
|
146
|
+
{dirty && (
|
|
147
|
+
<button
|
|
148
|
+
onClick={handleSave}
|
|
149
|
+
disabled={saving}
|
|
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)]"
|
|
153
|
+
style={{ fontFamily: 'inherit' }}
|
|
154
|
+
>
|
|
155
|
+
{saving ? 'Saving...' : 'Save'}
|
|
156
|
+
</button>
|
|
157
|
+
)}
|
|
158
|
+
<button
|
|
159
|
+
onClick={() => setConfirmDelete(true)}
|
|
160
|
+
className="p-2 rounded-[8px] text-text-3/70 hover:text-red-400 hover:bg-red-400/[0.06]
|
|
161
|
+
cursor-pointer transition-all bg-transparent border-none"
|
|
162
|
+
title="Delete memory"
|
|
163
|
+
>
|
|
164
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
165
|
+
<polyline points="3 6 5 6 21 6" /><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
|
|
166
|
+
<path d="M10 11v6" /><path d="M14 11v6" /><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
|
|
167
|
+
</svg>
|
|
168
|
+
</button>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
{/* Edit form */}
|
|
173
|
+
<div className="flex-1 overflow-y-auto px-6 py-5">
|
|
174
|
+
<div className="max-w-[640px] space-y-5">
|
|
175
|
+
{/* Title */}
|
|
176
|
+
<div>
|
|
177
|
+
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Title</label>
|
|
178
|
+
<input
|
|
179
|
+
type="text"
|
|
180
|
+
value={title}
|
|
181
|
+
onChange={(e) => { setTitle(e.target.value); setDirty(true) }}
|
|
182
|
+
className={`${inputClass} text-[15px] font-600`}
|
|
183
|
+
style={{ fontFamily: 'inherit' }}
|
|
184
|
+
placeholder="Memory title"
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* Category */}
|
|
189
|
+
<div>
|
|
190
|
+
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Category</label>
|
|
191
|
+
<div className="flex gap-1.5 flex-wrap">
|
|
192
|
+
{CATEGORIES.map((c) => (
|
|
193
|
+
<button
|
|
194
|
+
key={c}
|
|
195
|
+
onClick={() => { setCategory(c); setDirty(true) }}
|
|
196
|
+
className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 capitalize cursor-pointer transition-all border-none
|
|
197
|
+
${category === c
|
|
198
|
+
? 'bg-accent-soft text-accent-bright'
|
|
199
|
+
: 'bg-white/[0.03] text-text-3 hover:text-text-2 hover:bg-white/[0.05]'}`}
|
|
200
|
+
style={{ fontFamily: 'inherit' }}
|
|
201
|
+
>
|
|
202
|
+
{c}
|
|
203
|
+
</button>
|
|
204
|
+
))}
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
{/* Content */}
|
|
209
|
+
<div>
|
|
210
|
+
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Content</label>
|
|
211
|
+
<textarea
|
|
212
|
+
value={content}
|
|
213
|
+
onChange={(e) => { setContent(e.target.value); setDirty(true) }}
|
|
214
|
+
placeholder="Memory content..."
|
|
215
|
+
rows={12}
|
|
216
|
+
className={`${inputClass} text-[14px] resize-y min-h-[200px] leading-relaxed`}
|
|
217
|
+
style={{ fontFamily: 'inherit' }}
|
|
218
|
+
/>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
{imageUrl && (
|
|
222
|
+
<div>
|
|
223
|
+
<label className="block text-[11px] font-600 text-text-3/60 uppercase tracking-[0.06em] mb-2">Image</label>
|
|
224
|
+
<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-[320px] max-h-[220px] object-cover block" />
|
|
226
|
+
</a>
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
|
|
230
|
+
{entry.references?.length ? (
|
|
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
|
+
|
|
254
|
+
{entry.linkedMemoryIds?.length ? (
|
|
255
|
+
<div>
|
|
256
|
+
<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-wrap gap-1.5">
|
|
258
|
+
{entry.linkedMemoryIds.map((id) => (
|
|
259
|
+
<button
|
|
260
|
+
key={id}
|
|
261
|
+
onClick={() => setSelectedId(id)}
|
|
262
|
+
className="px-2.5 py-1 rounded-[8px] text-[11px] font-mono bg-white/[0.04] border border-white/[0.08] text-accent-bright/70 hover:text-accent-bright cursor-pointer transition-colors"
|
|
263
|
+
>
|
|
264
|
+
{id}
|
|
265
|
+
</button>
|
|
266
|
+
))}
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
) : null}
|
|
270
|
+
|
|
271
|
+
{/* Metadata */}
|
|
272
|
+
<div className="pt-4 border-t border-white/[0.04]">
|
|
273
|
+
<div className="grid grid-cols-2 gap-4 text-[11px]">
|
|
274
|
+
<div>
|
|
275
|
+
<span className="text-text-3/70 block mb-1">ID</span>
|
|
276
|
+
<span className="text-text-3/60 font-mono">{entry.id}</span>
|
|
277
|
+
</div>
|
|
278
|
+
<div>
|
|
279
|
+
<span className="text-text-3/70 block mb-1">Created</span>
|
|
280
|
+
<span className="text-text-3/60 font-mono">{new Date(entry.createdAt).toLocaleString()}</span>
|
|
281
|
+
</div>
|
|
282
|
+
<div>
|
|
283
|
+
<span className="text-text-3/70 block mb-1">Updated</span>
|
|
284
|
+
<span className="text-text-3/60 font-mono">{new Date(entry.updatedAt).toLocaleString()}</span>
|
|
285
|
+
</div>
|
|
286
|
+
{entry.agentId && (
|
|
287
|
+
<div>
|
|
288
|
+
<span className="text-text-3/70 block mb-1">Agent</span>
|
|
289
|
+
<span className="text-text-3/60 font-mono">{agentName}</span>
|
|
290
|
+
</div>
|
|
291
|
+
)}
|
|
292
|
+
{entry.sessionId && (
|
|
293
|
+
<div>
|
|
294
|
+
<span className="text-text-3/70 block mb-1">Session</span>
|
|
295
|
+
<button
|
|
296
|
+
onClick={handleNavigateToSession}
|
|
297
|
+
className="text-accent-bright/60 hover:text-accent-bright font-mono bg-transparent border-none cursor-pointer p-0 text-[11px] transition-colors"
|
|
298
|
+
>
|
|
299
|
+
{sessionName}
|
|
300
|
+
</button>
|
|
301
|
+
</div>
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
{/* Delete confirmation */}
|
|
309
|
+
{confirmDelete && (
|
|
310
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
311
|
+
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={() => setConfirmDelete(false)} />
|
|
312
|
+
<div className="relative bg-raised rounded-[16px] p-6 max-w-[360px] w-full shadow-xl border border-white/[0.06]"
|
|
313
|
+
style={{ animation: 'fade-in 0.15s cubic-bezier(0.16, 1, 0.3, 1)' }}>
|
|
314
|
+
<h3 className="font-display text-[16px] font-700 mb-2">Delete Memory</h3>
|
|
315
|
+
<p className="text-[13px] text-text-3 mb-5">
|
|
316
|
+
Delete “{entry.title}”? This cannot be undone.
|
|
317
|
+
</p>
|
|
318
|
+
<div className="flex gap-3">
|
|
319
|
+
<button
|
|
320
|
+
onClick={() => setConfirmDelete(false)}
|
|
321
|
+
className="flex-1 py-2.5 rounded-[10px] border border-white/[0.08] bg-transparent text-text-2 text-[13px] font-600 cursor-pointer hover:bg-surface-2 transition-all"
|
|
322
|
+
style={{ fontFamily: 'inherit' }}
|
|
323
|
+
>
|
|
324
|
+
Cancel
|
|
325
|
+
</button>
|
|
326
|
+
<button
|
|
327
|
+
onClick={handleDelete}
|
|
328
|
+
className="flex-1 py-2.5 rounded-[10px] border-none bg-red-500/90 text-white text-[13px] font-600 cursor-pointer active:scale-[0.97] transition-all hover:bg-red-500"
|
|
329
|
+
style={{ fontFamily: 'inherit' }}
|
|
330
|
+
>
|
|
331
|
+
Delete
|
|
332
|
+
</button>
|
|
333
|
+
</div>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
)}
|
|
337
|
+
</div>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
4
|
+
import { searchMemory } from '@/lib/memory'
|
|
5
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
6
|
+
import { MemoryCard } from './memory-card'
|
|
7
|
+
import type { MemoryEntry } from '@/types'
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
inSidebar?: boolean
|
|
11
|
+
onSelect?: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function MemoryList({ inSidebar: _inSidebar, onSelect }: Props) {
|
|
15
|
+
void _inSidebar
|
|
16
|
+
const selectedMemoryId = useAppStore((s) => s.selectedMemoryId)
|
|
17
|
+
const setSelectedMemoryId = useAppStore((s) => s.setSelectedMemoryId)
|
|
18
|
+
const refreshKey = useAppStore((s) => s.memoryRefreshKey)
|
|
19
|
+
const agents = useAppStore((s) => s.agents)
|
|
20
|
+
const memoryAgentFilter = useAppStore((s) => s.memoryAgentFilter)
|
|
21
|
+
const setMemoryAgentFilter = useAppStore((s) => s.setMemoryAgentFilter)
|
|
22
|
+
const [search, setSearch] = useState('')
|
|
23
|
+
const [entries, setEntries] = useState<MemoryEntry[]>([])
|
|
24
|
+
const [loaded, setLoaded] = useState(false)
|
|
25
|
+
const [error, setError] = useState<string | null>(null)
|
|
26
|
+
const [categoryFilter, setCategoryFilter] = useState<string>('')
|
|
27
|
+
const searchRef = useRef(search)
|
|
28
|
+
|
|
29
|
+
const load = useCallback(async (query: string) => {
|
|
30
|
+
try {
|
|
31
|
+
const results = await searchMemory({ q: query || undefined })
|
|
32
|
+
setEntries(Array.isArray(results) ? results : [])
|
|
33
|
+
setError(null)
|
|
34
|
+
} catch {
|
|
35
|
+
setError('Unable to load memories right now.')
|
|
36
|
+
}
|
|
37
|
+
setLoaded(true)
|
|
38
|
+
}, [])
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
searchRef.current = search
|
|
42
|
+
}, [search])
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const timer = setTimeout(() => { void load(searchRef.current) }, 0)
|
|
46
|
+
return () => clearTimeout(timer)
|
|
47
|
+
}, [refreshKey, load])
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const timer = setTimeout(() => { void load(search) }, 300)
|
|
51
|
+
return () => clearTimeout(timer)
|
|
52
|
+
}, [search, load])
|
|
53
|
+
|
|
54
|
+
// Derive unique agents and categories
|
|
55
|
+
const uniqueAgents = useMemo(() => {
|
|
56
|
+
const map = new Map<string, number>()
|
|
57
|
+
for (const e of entries) {
|
|
58
|
+
const key = e.agentId || '_global'
|
|
59
|
+
map.set(key, (map.get(key) || 0) + 1)
|
|
60
|
+
}
|
|
61
|
+
return map
|
|
62
|
+
}, [entries])
|
|
63
|
+
|
|
64
|
+
const uniqueCategories = useMemo(() => {
|
|
65
|
+
const cats = new Set<string>()
|
|
66
|
+
for (const e of entries) cats.add(e.category || 'note')
|
|
67
|
+
return Array.from(cats).sort()
|
|
68
|
+
}, [entries])
|
|
69
|
+
|
|
70
|
+
const filtered = useMemo(() => {
|
|
71
|
+
return entries.filter((e) => {
|
|
72
|
+
if (memoryAgentFilter && (e.agentId || null) !== memoryAgentFilter) return false
|
|
73
|
+
if (categoryFilter && (e.category || 'note') !== categoryFilter) return false
|
|
74
|
+
return true
|
|
75
|
+
})
|
|
76
|
+
}, [entries, memoryAgentFilter, categoryFilter])
|
|
77
|
+
|
|
78
|
+
const hasMultipleAgents = uniqueAgents.size > 1 || (uniqueAgents.size === 1 && !uniqueAgents.has('_global'))
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className="flex-1 flex flex-col overflow-y-auto">
|
|
82
|
+
{/* Search */}
|
|
83
|
+
<div className="px-3 py-2 shrink-0">
|
|
84
|
+
<input
|
|
85
|
+
type="text"
|
|
86
|
+
value={search}
|
|
87
|
+
onChange={(e) => setSearch(e.target.value)}
|
|
88
|
+
placeholder="Search memories..."
|
|
89
|
+
className="w-full px-3 py-2 rounded-[10px] border border-white/[0.04] bg-surface text-text
|
|
90
|
+
text-[12px] outline-none transition-all duration-200 placeholder:text-text-3/70 focus-glow"
|
|
91
|
+
style={{ fontFamily: 'inherit' }}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
{/* Agent filter tabs */}
|
|
96
|
+
{entries.length > 0 && hasMultipleAgents && (
|
|
97
|
+
<div className="px-3 pb-1.5 shrink-0">
|
|
98
|
+
<div className="flex gap-1 flex-wrap">
|
|
99
|
+
<button
|
|
100
|
+
onClick={() => setMemoryAgentFilter(null)}
|
|
101
|
+
className={`px-2.5 py-1 rounded-[7px] text-[10px] font-600 cursor-pointer transition-all
|
|
102
|
+
${!memoryAgentFilter ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
|
|
103
|
+
style={{ fontFamily: 'inherit' }}
|
|
104
|
+
>
|
|
105
|
+
All ({entries.length})
|
|
106
|
+
</button>
|
|
107
|
+
{Array.from(uniqueAgents.entries()).map(([agentId, count]) => {
|
|
108
|
+
const id = agentId === '_global' ? null : agentId
|
|
109
|
+
const name = id ? (agents[id]?.name || id.slice(0, 8)) : 'Global'
|
|
110
|
+
return (
|
|
111
|
+
<button
|
|
112
|
+
key={agentId}
|
|
113
|
+
onClick={() => setMemoryAgentFilter(id)}
|
|
114
|
+
className={`px-2.5 py-1 rounded-[7px] text-[10px] font-600 cursor-pointer transition-all truncate max-w-[120px]
|
|
115
|
+
${memoryAgentFilter === id ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
|
|
116
|
+
style={{ fontFamily: 'inherit' }}
|
|
117
|
+
>
|
|
118
|
+
{name} ({count})
|
|
119
|
+
</button>
|
|
120
|
+
)
|
|
121
|
+
})}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
|
|
126
|
+
{/* Category filter */}
|
|
127
|
+
{entries.length > 0 && uniqueCategories.length > 1 && (
|
|
128
|
+
<div className="px-3 pb-1.5 shrink-0">
|
|
129
|
+
<div className="flex gap-1 flex-wrap">
|
|
130
|
+
<button
|
|
131
|
+
onClick={() => setCategoryFilter('')}
|
|
132
|
+
className={`px-2 py-0.5 rounded-[6px] text-[9px] font-600 cursor-pointer transition-all uppercase tracking-wider
|
|
133
|
+
${!categoryFilter ? 'bg-white/[0.06] text-text-2' : 'bg-transparent text-text-3/70 hover:text-text-3'}`}
|
|
134
|
+
style={{ fontFamily: 'inherit' }}
|
|
135
|
+
>
|
|
136
|
+
all
|
|
137
|
+
</button>
|
|
138
|
+
{uniqueCategories.map((c) => (
|
|
139
|
+
<button
|
|
140
|
+
key={c}
|
|
141
|
+
onClick={() => setCategoryFilter(categoryFilter === c ? '' : c)}
|
|
142
|
+
className={`px-2 py-0.5 rounded-[6px] text-[9px] font-600 cursor-pointer transition-all uppercase tracking-wider
|
|
143
|
+
${categoryFilter === c ? 'bg-white/[0.06] text-text-2' : 'bg-transparent text-text-3/70 hover:text-text-3'}`}
|
|
144
|
+
style={{ fontFamily: 'inherit' }}
|
|
145
|
+
>
|
|
146
|
+
{c}
|
|
147
|
+
</button>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
{/* Memory cards */}
|
|
154
|
+
{filtered.length > 0 ? (
|
|
155
|
+
<div className="flex flex-col gap-0.5 px-2 pb-4">
|
|
156
|
+
{filtered.map((e) => (
|
|
157
|
+
<MemoryCard
|
|
158
|
+
key={e.id}
|
|
159
|
+
entry={e}
|
|
160
|
+
active={e.id === selectedMemoryId}
|
|
161
|
+
agentName={e.agentId ? (agents[e.agentId]?.name || null) : null}
|
|
162
|
+
onClick={() => {
|
|
163
|
+
setSelectedMemoryId(e.id)
|
|
164
|
+
onSelect?.()
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
))}
|
|
168
|
+
</div>
|
|
169
|
+
) : error ? (
|
|
170
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center">
|
|
171
|
+
<p className="font-display text-[14px] font-600 text-text-2">Couldn't load memories</p>
|
|
172
|
+
<p className="text-[12px] text-text-3/60">{error}</p>
|
|
173
|
+
<button
|
|
174
|
+
onClick={() => { void load(search) }}
|
|
175
|
+
className="px-3 py-1.5 rounded-[8px] bg-accent-soft text-accent-bright text-[12px] font-600 cursor-pointer border-none"
|
|
176
|
+
style={{ fontFamily: 'inherit' }}
|
|
177
|
+
>
|
|
178
|
+
Retry
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
) : loaded ? (
|
|
182
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-4 text-text-3 p-8 text-center">
|
|
183
|
+
<div className="w-12 h-12 rounded-[14px] bg-accent-soft flex items-center justify-center mb-1">
|
|
184
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright">
|
|
185
|
+
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
|
186
|
+
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
|
|
187
|
+
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
|
188
|
+
</svg>
|
|
189
|
+
</div>
|
|
190
|
+
<p className="font-display text-[15px] font-600 text-text-2">
|
|
191
|
+
{memoryAgentFilter ? 'No memories for this agent' : 'No memories yet'}
|
|
192
|
+
</p>
|
|
193
|
+
<p className="text-[13px] text-text-3/50">AI agents store knowledge here</p>
|
|
194
|
+
</div>
|
|
195
|
+
) : null}
|
|
196
|
+
</div>
|
|
197
|
+
)
|
|
198
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
import { createMemory } from '@/lib/memory'
|
|
6
|
+
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
7
|
+
|
|
8
|
+
export function MemorySheet() {
|
|
9
|
+
const open = useAppStore((s) => s.memorySheetOpen)
|
|
10
|
+
const setOpen = useAppStore((s) => s.setMemorySheetOpen)
|
|
11
|
+
const triggerRefresh = useAppStore((s) => s.triggerMemoryRefresh)
|
|
12
|
+
|
|
13
|
+
const [title, setTitle] = useState('')
|
|
14
|
+
const [content, setContent] = useState('')
|
|
15
|
+
|
|
16
|
+
const onClose = () => {
|
|
17
|
+
setOpen(false)
|
|
18
|
+
setTitle('')
|
|
19
|
+
setContent('')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const handleSave = async () => {
|
|
23
|
+
await createMemory({
|
|
24
|
+
title: title.trim() || 'Untitled',
|
|
25
|
+
category: 'general',
|
|
26
|
+
content,
|
|
27
|
+
agentId: null,
|
|
28
|
+
sessionId: null,
|
|
29
|
+
})
|
|
30
|
+
triggerRefresh()
|
|
31
|
+
onClose()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const inputClass = "w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow"
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<BottomSheet open={open} onClose={onClose}>
|
|
38
|
+
<div className="mb-10">
|
|
39
|
+
<h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">New Memory</h2>
|
|
40
|
+
<p className="text-[14px] text-text-3">Store a piece of knowledge</p>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div className="mb-8">
|
|
44
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Title</label>
|
|
45
|
+
<input type="text" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Memory title" className={inputClass} style={{ fontFamily: 'inherit' }} />
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div className="mb-8">
|
|
49
|
+
<label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Content</label>
|
|
50
|
+
<textarea
|
|
51
|
+
value={content}
|
|
52
|
+
onChange={(e) => setContent(e.target.value)}
|
|
53
|
+
placeholder="Memory content..."
|
|
54
|
+
rows={6}
|
|
55
|
+
className={`${inputClass} resize-y min-h-[150px]`}
|
|
56
|
+
style={{ fontFamily: 'inherit' }}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div className="flex gap-3 pt-2 border-t border-white/[0.04]">
|
|
61
|
+
<button onClick={onClose} className="flex-1 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer hover:bg-surface-2 transition-all" style={{ fontFamily: 'inherit' }}>
|
|
62
|
+
Cancel
|
|
63
|
+
</button>
|
|
64
|
+
<button onClick={handleSave} disabled={!title.trim()} className="flex-1 py-3.5 rounded-[14px] border-none bg-[#6366F1] 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" style={{ fontFamily: 'inherit' }}>
|
|
65
|
+
Save
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
</BottomSheet>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
5
|
+
|
|
6
|
+
export function PluginList({ inSidebar }: { inSidebar?: boolean }) {
|
|
7
|
+
const plugins = useAppStore((s) => s.plugins)
|
|
8
|
+
const loadPlugins = useAppStore((s) => s.loadPlugins)
|
|
9
|
+
const setPluginSheetOpen = useAppStore((s) => s.setPluginSheetOpen)
|
|
10
|
+
const setEditingPluginFilename = useAppStore((s) => s.setEditingPluginFilename)
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
loadPlugins()
|
|
14
|
+
}, [])
|
|
15
|
+
|
|
16
|
+
const pluginList = Object.values(plugins)
|
|
17
|
+
|
|
18
|
+
const handleEdit = (filename: string) => {
|
|
19
|
+
setEditingPluginFilename(filename)
|
|
20
|
+
setPluginSheetOpen(true)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-4'}`}>
|
|
25
|
+
{pluginList.length === 0 ? (
|
|
26
|
+
<div className="text-center py-12">
|
|
27
|
+
<p className="text-[13px] text-text-3/60">No plugins installed</p>
|
|
28
|
+
<button
|
|
29
|
+
onClick={() => { setEditingPluginFilename(null); setPluginSheetOpen(true) }}
|
|
30
|
+
className="mt-3 px-4 py-2 rounded-[10px] bg-transparent text-accent-bright text-[13px] font-600 cursor-pointer border border-accent-bright/20 hover:bg-accent-soft transition-all"
|
|
31
|
+
style={{ fontFamily: 'inherit' }}
|
|
32
|
+
>
|
|
33
|
+
+ Add Plugin
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
) : (
|
|
37
|
+
<div className="space-y-2">
|
|
38
|
+
{pluginList.map((plugin) => (
|
|
39
|
+
<button
|
|
40
|
+
key={plugin.filename}
|
|
41
|
+
onClick={() => handleEdit(plugin.filename)}
|
|
42
|
+
className="w-full text-left p-4 rounded-[14px] border border-white/[0.06] bg-surface hover:bg-surface-2 transition-all cursor-pointer"
|
|
43
|
+
>
|
|
44
|
+
<div className="flex items-center justify-between mb-1">
|
|
45
|
+
<span className="font-display text-[14px] font-600 text-text truncate">{plugin.name}</span>
|
|
46
|
+
<span className={`text-[10px] font-600 px-1.5 py-0.5 rounded-full shrink-0 ml-2 ${plugin.enabled ? 'text-emerald-400 bg-emerald-400/10' : 'text-text-3/50 bg-white/[0.04]'}`}>
|
|
47
|
+
{plugin.enabled ? 'Enabled' : 'Disabled'}
|
|
48
|
+
</span>
|
|
49
|
+
</div>
|
|
50
|
+
<div className="text-[11px] font-mono text-text-3/50 mb-1">{plugin.filename}</div>
|
|
51
|
+
{plugin.description && (
|
|
52
|
+
<p className="text-[12px] text-text-3/60 line-clamp-2">{plugin.description}</p>
|
|
53
|
+
)}
|
|
54
|
+
</button>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|