@swarmclawai/swarmclaw 0.3.1 → 0.4.5
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 +33 -13
- package/bin/server-cmd.js +14 -7
- package/bin/swarmclaw.js +3 -1
- package/bin/update-cmd.js +120 -0
- package/next.config.ts +10 -0
- package/package.json +4 -1
- package/src/app/api/agents/[id]/route.ts +20 -18
- package/src/app/api/agents/[id]/thread/route.ts +4 -3
- package/src/app/api/agents/route.ts +8 -3
- package/src/app/api/auth/route.ts +3 -1
- package/src/app/api/claude-skills/route.ts +3 -1
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +14 -3
- package/src/app/api/connectors/[id]/webhook/route.ts +99 -0
- package/src/app/api/connectors/route.ts +12 -4
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +5 -3
- package/src/app/api/daemon/route.ts +6 -1
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- package/src/app/api/ip/route.ts +3 -1
- package/src/app/api/knowledge/[id]/route.ts +5 -4
- package/src/app/api/knowledge/upload/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/route.ts +11 -14
- package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
- package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
- package/src/app/api/mcp-servers/route.ts +5 -3
- package/src/app/api/memory/[id]/route.ts +9 -8
- package/src/app/api/memory/route.ts +2 -2
- package/src/app/api/memory-images/[filename]/route.ts +2 -1
- package/src/app/api/openclaw/directory/route.ts +26 -0
- package/src/app/api/openclaw/discover/route.ts +61 -0
- package/src/app/api/openclaw/sync/route.ts +30 -0
- package/src/app/api/orchestrator/graph/route.ts +25 -0
- package/src/app/api/orchestrator/run/route.ts +2 -2
- package/src/app/api/plugins/marketplace/route.ts +3 -1
- package/src/app/api/plugins/route.ts +3 -1
- package/src/app/api/projects/[id]/route.ts +55 -0
- package/src/app/api/projects/route.ts +27 -0
- package/src/app/api/providers/[id]/models/route.ts +2 -1
- package/src/app/api/providers/[id]/route.ts +13 -12
- package/src/app/api/providers/configs/route.ts +3 -1
- package/src/app/api/providers/route.ts +7 -3
- package/src/app/api/schedules/[id]/route.ts +16 -15
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +8 -3
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +5 -3
- package/src/app/api/sessions/[id]/chat/route.ts +5 -2
- package/src/app/api/sessions/[id]/clear/route.ts +2 -1
- package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
- package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
- package/src/app/api/sessions/[id]/messages/route.ts +2 -1
- package/src/app/api/sessions/[id]/retry/route.ts +2 -1
- package/src/app/api/sessions/[id]/route.ts +2 -1
- package/src/app/api/sessions/route.ts +11 -4
- package/src/app/api/settings/route.ts +3 -1
- package/src/app/api/setup/doctor/route.ts +1 -0
- package/src/app/api/setup/openclaw-device/route.ts +3 -1
- package/src/app/api/skills/[id]/route.ts +23 -21
- package/src/app/api/skills/import/route.ts +2 -2
- package/src/app/api/skills/route.ts +5 -3
- package/src/app/api/tasks/[id]/approve/route.ts +74 -0
- package/src/app/api/tasks/[id]/route.ts +9 -5
- package/src/app/api/tasks/route.ts +5 -2
- package/src/app/api/tts/stream/route.ts +48 -0
- package/src/app/api/upload/route.ts +2 -2
- package/src/app/api/uploads/[filename]/route.ts +4 -1
- package/src/app/api/usage/route.ts +3 -1
- package/src/app/api/version/route.ts +3 -1
- package/src/app/api/webhooks/[id]/route.ts +31 -32
- package/src/app/api/webhooks/route.ts +5 -3
- package/src/app/icon.svg +58 -0
- package/src/app/page.tsx +11 -26
- package/src/cli/index.js +28 -9
- package/src/cli/index.ts +45 -2
- package/src/cli/spec.js +2 -8
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-list.tsx +3 -1
- package/src/components/agents/agent-sheet.tsx +166 -81
- package/src/components/chat/chat-area.tsx +71 -34
- package/src/components/chat/chat-header.tsx +141 -29
- package/src/components/chat/chat-tool-toggles.tsx +12 -53
- package/src/components/chat/message-bubble.tsx +110 -42
- package/src/components/chat/tool-call-bubble.tsx +50 -6
- package/src/components/chat/tool-request-banner.tsx +1 -9
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +9 -10
- package/src/components/connectors/connector-sheet.tsx +55 -36
- package/src/components/input/chat-input.tsx +72 -56
- package/src/components/knowledge/knowledge-list.tsx +27 -31
- package/src/components/layout/app-layout.tsx +133 -90
- package/src/components/layout/daemon-indicator.tsx +3 -5
- package/src/components/logs/log-list.tsx +5 -9
- package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
- package/src/components/memory/memory-detail.tsx +1 -1
- package/src/components/plugins/plugin-list.tsx +227 -27
- package/src/components/projects/project-list.tsx +122 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/providers/provider-list.tsx +46 -13
- package/src/components/providers/provider-sheet.tsx +0 -45
- package/src/components/runs/run-list.tsx +6 -15
- package/src/components/schedules/schedule-card.tsx +54 -4
- package/src/components/schedules/schedule-list.tsx +9 -4
- package/src/components/schedules/schedule-sheet.tsx +0 -47
- package/src/components/secrets/secrets-list.tsx +20 -2
- package/src/components/sessions/new-session-sheet.tsx +14 -15
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/sessions/session-list.tsx +7 -7
- package/src/components/shared/connector-platform-icon.tsx +26 -20
- package/src/components/shared/model-combobox.tsx +148 -0
- package/src/components/shared/settings/section-heartbeat.tsx +8 -40
- package/src/components/shared/settings/section-orchestrator.tsx +9 -11
- package/src/components/shared/settings/section-web-search.tsx +56 -0
- package/src/components/shared/settings/settings-page.tsx +73 -0
- package/src/components/skills/skill-list.tsx +262 -35
- package/src/components/skills/skill-sheet.tsx +0 -45
- package/src/components/tasks/task-board.tsx +3 -6
- package/src/components/tasks/task-card.tsx +43 -1
- package/src/components/tasks/task-list.tsx +8 -7
- package/src/components/tasks/task-sheet.tsx +0 -44
- package/src/components/usage/usage-list.tsx +12 -4
- package/src/hooks/use-continuous-speech.ts +144 -0
- package/src/hooks/use-view-router.ts +52 -0
- package/src/hooks/use-voice-conversation.ts +80 -0
- package/src/hooks/use-ws.ts +66 -0
- package/src/instrumentation.ts +2 -0
- package/src/lib/chat.ts +14 -2
- package/src/lib/id.ts +6 -0
- package/src/lib/projects.ts +13 -0
- package/src/lib/provider-sets.ts +5 -0
- package/src/lib/providers/anthropic.ts +15 -2
- package/src/lib/providers/index.ts +8 -0
- package/src/lib/providers/ollama.ts +10 -2
- package/src/lib/providers/openai.ts +42 -13
- package/src/lib/providers/openclaw.ts +11 -0
- package/src/lib/server/api-routes.test.ts +5 -6
- package/src/lib/server/build-llm.ts +17 -4
- package/src/lib/server/chat-execution.ts +57 -8
- package/src/lib/server/collection-helpers.ts +54 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +208 -0
- package/src/lib/server/connectors/bluebubbles.ts +357 -0
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/googlechat.ts +46 -7
- package/src/lib/server/connectors/manager.ts +401 -6
- package/src/lib/server/connectors/media.ts +2 -2
- package/src/lib/server/connectors/openclaw.ts +64 -0
- package/src/lib/server/connectors/pairing.test.ts +99 -0
- package/src/lib/server/connectors/pairing.ts +256 -0
- package/src/lib/server/connectors/signal.ts +1 -0
- package/src/lib/server/connectors/teams.ts +5 -5
- package/src/lib/server/connectors/types.ts +10 -0
- package/src/lib/server/context-manager.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -0
- package/src/lib/server/data-dir.ts +1 -0
- package/src/lib/server/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +67 -3
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/langgraph-checkpoint.ts +274 -0
- package/src/lib/server/main-agent-loop.ts +67 -8
- package/src/lib/server/memory-db.ts +6 -6
- package/src/lib/server/openclaw-approvals.ts +105 -0
- package/src/lib/server/openclaw-sync.ts +496 -0
- package/src/lib/server/orchestrator-lg.ts +422 -20
- package/src/lib/server/orchestrator.ts +29 -9
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +39 -13
- package/src/lib/server/scheduler.ts +2 -2
- package/src/lib/server/session-mailbox.ts +2 -2
- package/src/lib/server/session-run-manager.ts +8 -3
- package/src/lib/server/session-tools/connector.ts +51 -4
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/delegate.ts +5 -5
- package/src/lib/server/session-tools/file.ts +176 -3
- package/src/lib/server/session-tools/index.ts +4 -0
- package/src/lib/server/session-tools/memory.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
- package/src/lib/server/session-tools/sandbox.ts +197 -0
- package/src/lib/server/session-tools/search-providers.ts +270 -0
- package/src/lib/server/session-tools/session-info.ts +2 -2
- package/src/lib/server/session-tools/web.ts +47 -66
- package/src/lib/server/storage-mcp.test.ts +25 -2
- package/src/lib/server/storage.ts +36 -7
- package/src/lib/server/stream-agent-chat.ts +106 -22
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/server/task-validation.test.ts +23 -0
- package/src/lib/server/task-validation.ts +5 -3
- package/src/lib/server/ws-hub.ts +85 -0
- package/src/lib/tool-definitions.ts +44 -0
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/upload.ts +7 -1
- package/src/lib/view-routes.ts +28 -0
- package/src/lib/ws-client.ts +124 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +28 -1
- package/src/stores/use-chat-store.ts +42 -14
- package/src/types/index.ts +34 -2
- package/src/app/api/agents/generate/route.ts +0 -42
- package/src/app/api/generate/info/route.ts +0 -12
- package/src/app/api/generate/route.ts +0 -106
- package/src/app/favicon.ico +0 -0
- package/src/components/shared/ai-gen-block.tsx +0 -77
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useCallback, useRef, useState } from 'react'
|
|
4
|
-
import { useChatStore } from '@/stores/use-chat-store'
|
|
4
|
+
import { useChatStore, type PendingFile } 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'
|
|
@@ -13,23 +13,56 @@ interface Props {
|
|
|
13
13
|
onStop: () => void
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function FilePreview({ file, onRemove }: { file: PendingFile; onRemove: () => void }) {
|
|
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
|
+
}
|
|
46
|
+
|
|
16
47
|
export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
17
48
|
const [value, setValue] = useState('')
|
|
18
49
|
const { ref: textareaRef, resize } = useAutoResize()
|
|
19
50
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
20
|
-
const
|
|
21
|
-
const
|
|
51
|
+
const imageInputRef = useRef<HTMLInputElement>(null)
|
|
52
|
+
const pendingFiles = useChatStore((s) => s.pendingFiles)
|
|
53
|
+
const addPendingFile = useChatStore((s) => s.addPendingFile)
|
|
54
|
+
const removePendingFile = useChatStore((s) => s.removePendingFile)
|
|
22
55
|
const speechRecognitionLang = useAppStore((s) => s.appSettings.speechRecognitionLang)
|
|
23
56
|
|
|
24
57
|
const handleSend = useCallback(() => {
|
|
25
58
|
const text = value.trim()
|
|
26
|
-
if (!text || streaming) return
|
|
27
|
-
onSend(text)
|
|
59
|
+
if ((!text && !pendingFiles.length) || streaming) return
|
|
60
|
+
onSend(text || 'See attached file(s).')
|
|
28
61
|
setValue('')
|
|
29
62
|
if (textareaRef.current) {
|
|
30
63
|
textareaRef.current.style.height = 'auto'
|
|
31
64
|
}
|
|
32
|
-
}, [value, streaming, onSend])
|
|
65
|
+
}, [value, streaming, onSend, pendingFiles.length])
|
|
33
66
|
|
|
34
67
|
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
35
68
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
@@ -47,6 +80,15 @@ export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
|
47
80
|
{ lang: speechRecognitionLang || undefined },
|
|
48
81
|
)
|
|
49
82
|
|
|
83
|
+
const uploadAndAdd = useCallback(async (file: File) => {
|
|
84
|
+
try {
|
|
85
|
+
const result = await uploadImage(file)
|
|
86
|
+
addPendingFile({ file, path: result.path, url: result.url })
|
|
87
|
+
} catch {
|
|
88
|
+
// ignore upload errors
|
|
89
|
+
}
|
|
90
|
+
}, [addPendingFile])
|
|
91
|
+
|
|
50
92
|
const handlePaste = useCallback(async (e: React.ClipboardEvent) => {
|
|
51
93
|
const items = e.clipboardData?.items
|
|
52
94
|
if (!items) return
|
|
@@ -54,35 +96,22 @@ export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
|
54
96
|
if (item.type.startsWith('image/')) {
|
|
55
97
|
e.preventDefault()
|
|
56
98
|
const file = item.getAsFile()
|
|
57
|
-
if (
|
|
58
|
-
try {
|
|
59
|
-
const result = await uploadImage(file)
|
|
60
|
-
setPendingImage({ file, path: result.path, url: result.url })
|
|
61
|
-
} catch {
|
|
62
|
-
// ignore
|
|
63
|
-
}
|
|
99
|
+
if (file) await uploadAndAdd(file)
|
|
64
100
|
return
|
|
65
101
|
}
|
|
66
102
|
}
|
|
67
|
-
}, [])
|
|
103
|
+
}, [uploadAndAdd])
|
|
68
104
|
|
|
69
105
|
const handleFileChange = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
70
|
-
const
|
|
71
|
-
if (!
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
setPendingImage({
|
|
75
|
-
file,
|
|
76
|
-
path: result.path,
|
|
77
|
-
url: result.url,
|
|
78
|
-
})
|
|
79
|
-
} catch {
|
|
80
|
-
// ignore
|
|
106
|
+
const files = e.target.files
|
|
107
|
+
if (!files?.length) return
|
|
108
|
+
for (const file of Array.from(files)) {
|
|
109
|
+
await uploadAndAdd(file)
|
|
81
110
|
}
|
|
82
111
|
e.target.value = ''
|
|
83
|
-
}, [])
|
|
112
|
+
}, [uploadAndAdd])
|
|
84
113
|
|
|
85
|
-
const hasContent = value.trim().length > 0 ||
|
|
114
|
+
const hasContent = value.trim().length > 0 || pendingFiles.length > 0
|
|
86
115
|
|
|
87
116
|
return (
|
|
88
117
|
<div className="shrink-0 px-6 md:px-12 lg:px-16 pb-4 pt-2"
|
|
@@ -105,33 +134,11 @@ export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
|
105
134
|
<div className="glass rounded-[20px] overflow-hidden
|
|
106
135
|
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">
|
|
107
136
|
|
|
108
|
-
{
|
|
109
|
-
<div className="flex items-center gap-2 px-5 pt-4">
|
|
110
|
-
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
src={URL.createObjectURL(pendingImage.file)}
|
|
114
|
-
alt="Preview"
|
|
115
|
-
className="h-16 rounded-[10px] object-cover border border-white/[0.06]"
|
|
116
|
-
/>
|
|
117
|
-
) : (
|
|
118
|
-
<div className="flex items-center gap-2.5 px-3 py-2.5 rounded-[10px] border border-white/[0.06] bg-white/[0.03]">
|
|
119
|
-
<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">
|
|
120
|
-
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
|
121
|
-
<polyline points="14 2 14 8 20 8" />
|
|
122
|
-
</svg>
|
|
123
|
-
<span className="text-[13px] text-text-2 font-500 truncate max-w-[180px]">{pendingImage.file.name}</span>
|
|
124
|
-
</div>
|
|
125
|
-
)}
|
|
126
|
-
<button
|
|
127
|
-
onClick={() => setPendingImage(null)}
|
|
128
|
-
className="absolute -top-1.5 -right-1.5 w-5 h-5 rounded-full border border-white/10 bg-raised
|
|
129
|
-
text-text-2 text-[10px] cursor-pointer flex items-center justify-center
|
|
130
|
-
hover:bg-danger-soft hover:text-danger hover:border-danger/20 transition-colors"
|
|
131
|
-
>
|
|
132
|
-
×
|
|
133
|
-
</button>
|
|
134
|
-
</div>
|
|
137
|
+
{pendingFiles.length > 0 && (
|
|
138
|
+
<div className="flex items-center gap-2 px-5 pt-4 flex-wrap">
|
|
139
|
+
{pendingFiles.map((f, i) => (
|
|
140
|
+
<FilePreview key={`${f.path}-${i}`} file={f} onRemove={() => removePendingFile(i)} />
|
|
141
|
+
))}
|
|
135
142
|
</div>
|
|
136
143
|
)}
|
|
137
144
|
|
|
@@ -162,7 +169,7 @@ export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
|
162
169
|
</button>
|
|
163
170
|
|
|
164
171
|
<button
|
|
165
|
-
onClick={() =>
|
|
172
|
+
onClick={() => imageInputRef.current?.click()}
|
|
166
173
|
className="flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-transparent
|
|
167
174
|
text-text-3 text-[13px] cursor-pointer hover:text-text-2 hover:bg-white/[0.05] transition-all duration-200"
|
|
168
175
|
style={{ fontFamily: 'inherit' }}
|
|
@@ -217,7 +224,16 @@ export function ChatInput({ streaming, onSend, onStop }: Props) {
|
|
|
217
224
|
<input
|
|
218
225
|
ref={fileInputRef}
|
|
219
226
|
type="file"
|
|
220
|
-
|
|
227
|
+
multiple
|
|
228
|
+
accept="image/*,.pdf,.txt,.md,.csv,.json,.xml,.html,.js,.ts,.tsx,.jsx,.py,.go,.rs,.java,.c,.cpp,.h,.yml,.yaml,.toml,.env,.log,.sh,.sql,.css,.scss"
|
|
229
|
+
onChange={handleFileChange}
|
|
230
|
+
className="hidden"
|
|
231
|
+
/>
|
|
232
|
+
<input
|
|
233
|
+
ref={imageInputRef}
|
|
234
|
+
type="file"
|
|
235
|
+
multiple
|
|
236
|
+
accept="image/*"
|
|
221
237
|
onChange={handleFileChange}
|
|
222
238
|
className="hidden"
|
|
223
239
|
/>
|
|
@@ -12,7 +12,6 @@ export function KnowledgeList() {
|
|
|
12
12
|
const [loaded, setLoaded] = useState(false)
|
|
13
13
|
const [error, setError] = useState<string | null>(null)
|
|
14
14
|
const [activeTag, setActiveTag] = useState<string | null>(null)
|
|
15
|
-
const [selectedId, setSelectedId] = useState<string | null>(null)
|
|
16
15
|
const searchRef = useRef(search)
|
|
17
16
|
const setKnowledgeSheetOpen = useAppStore((s) => s.setKnowledgeSheetOpen)
|
|
18
17
|
const setEditingKnowledgeId = useAppStore((s) => s.setEditingKnowledgeId)
|
|
@@ -64,7 +63,6 @@ export function KnowledgeList() {
|
|
|
64
63
|
try {
|
|
65
64
|
await api('DELETE', `/knowledge/${id}`)
|
|
66
65
|
setEntries((prev) => prev.filter((e) => e.id !== id))
|
|
67
|
-
if (selectedId === id) setSelectedId(null)
|
|
68
66
|
} catch {
|
|
69
67
|
// silent
|
|
70
68
|
}
|
|
@@ -78,7 +76,7 @@ export function KnowledgeList() {
|
|
|
78
76
|
<div className="flex-1 flex flex-col overflow-y-auto">
|
|
79
77
|
{/* Search — only show when there are entries */}
|
|
80
78
|
{entries.length > 0 && (
|
|
81
|
-
<div className="px-
|
|
79
|
+
<div className="px-5 py-2 shrink-0">
|
|
82
80
|
<input
|
|
83
81
|
type="text"
|
|
84
82
|
value={search}
|
|
@@ -93,7 +91,7 @@ export function KnowledgeList() {
|
|
|
93
91
|
|
|
94
92
|
{/* Tag filters */}
|
|
95
93
|
{uniqueTags.length > 0 && (
|
|
96
|
-
<div className="px-
|
|
94
|
+
<div className="px-5 pb-1.5 shrink-0">
|
|
97
95
|
<div className="flex gap-1 flex-wrap">
|
|
98
96
|
<button
|
|
99
97
|
onClick={() => setActiveTag(null)}
|
|
@@ -120,52 +118,50 @@ export function KnowledgeList() {
|
|
|
120
118
|
|
|
121
119
|
{/* Entries */}
|
|
122
120
|
{entries.length > 0 ? (
|
|
123
|
-
<div className="
|
|
121
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 px-5 pb-6">
|
|
124
122
|
{entries.map((entry) => {
|
|
125
123
|
const meta = entry.metadata as { tags?: string[] } | undefined
|
|
126
124
|
const tags = meta?.tags || []
|
|
127
125
|
return (
|
|
128
126
|
<div
|
|
129
127
|
key={entry.id}
|
|
130
|
-
|
|
131
|
-
className={`p-3 rounded-[12px] border cursor-pointer transition-all
|
|
132
|
-
${selectedId === entry.id
|
|
133
|
-
? 'border-accent-bright/30 bg-accent-soft/30'
|
|
134
|
-
: 'border-white/[0.04] bg-transparent hover:bg-surface-2'
|
|
135
|
-
}`}
|
|
128
|
+
className="p-3 rounded-[12px] border border-white/[0.04] bg-transparent hover:bg-surface-2 transition-all relative group"
|
|
136
129
|
>
|
|
137
130
|
<div className="flex items-start justify-between gap-2 mb-1">
|
|
138
131
|
<span className="font-display text-[13px] font-600 text-text truncate">{entry.title}</span>
|
|
139
|
-
<
|
|
132
|
+
<div className="flex items-center gap-1.5 shrink-0">
|
|
133
|
+
<button
|
|
134
|
+
onClick={() => openSheet(entry.id)}
|
|
135
|
+
className="text-text-3/40 hover:text-accent-bright transition-colors p-0.5 cursor-pointer"
|
|
136
|
+
title="Edit"
|
|
137
|
+
>
|
|
138
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
139
|
+
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" />
|
|
140
|
+
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" />
|
|
141
|
+
</svg>
|
|
142
|
+
</button>
|
|
143
|
+
<button
|
|
144
|
+
onClick={() => void handleDelete(entry.id)}
|
|
145
|
+
className="text-text-3/40 hover:text-red-400 transition-colors p-0.5 cursor-pointer"
|
|
146
|
+
title="Delete"
|
|
147
|
+
>
|
|
148
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
149
|
+
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
150
|
+
</svg>
|
|
151
|
+
</button>
|
|
152
|
+
<span className="text-[10px] text-text-3/50">{formatDate(entry.createdAt)}</span>
|
|
153
|
+
</div>
|
|
140
154
|
</div>
|
|
141
155
|
<p className="text-[11px] text-text-3/60 line-clamp-2 mb-2">
|
|
142
156
|
{entry.content.slice(0, 200)}
|
|
143
157
|
</p>
|
|
144
158
|
{tags.length > 0 && (
|
|
145
|
-
<div className="flex gap-1 flex-wrap
|
|
159
|
+
<div className="flex gap-1 flex-wrap">
|
|
146
160
|
{tags.map((t) => (
|
|
147
161
|
<Badge key={t} variant="secondary" className="text-[9px] px-1.5 py-0">{t}</Badge>
|
|
148
162
|
))}
|
|
149
163
|
</div>
|
|
150
164
|
)}
|
|
151
|
-
{selectedId === entry.id && (
|
|
152
|
-
<div className="flex gap-2 pt-2 border-t border-white/[0.04]">
|
|
153
|
-
<button
|
|
154
|
-
onClick={(e) => { e.stopPropagation(); openSheet(entry.id) }}
|
|
155
|
-
className="px-2.5 py-1 rounded-[7px] text-[10px] font-600 text-accent-bright bg-accent-soft cursor-pointer border-none hover:brightness-110 transition-all"
|
|
156
|
-
style={{ fontFamily: 'inherit' }}
|
|
157
|
-
>
|
|
158
|
-
Edit
|
|
159
|
-
</button>
|
|
160
|
-
<button
|
|
161
|
-
onClick={(e) => { e.stopPropagation(); void handleDelete(entry.id) }}
|
|
162
|
-
className="px-2.5 py-1 rounded-[7px] text-[10px] font-600 text-red-400 bg-red-400/10 cursor-pointer border-none hover:bg-red-400/20 transition-all"
|
|
163
|
-
style={{ fontFamily: 'inherit' }}
|
|
164
|
-
>
|
|
165
|
-
Delete
|
|
166
|
-
</button>
|
|
167
|
-
</div>
|
|
168
|
-
)}
|
|
169
165
|
</div>
|
|
170
166
|
)
|
|
171
167
|
})}
|