@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.
Files changed (319) hide show
  1. package/README.md +577 -0
  2. package/bin/server-cmd.js +359 -0
  3. package/bin/swarmclaw.js +29 -0
  4. package/bin/swarmclaw.mjs +1504 -0
  5. package/next.config.ts +33 -0
  6. package/package.json +112 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/branding/swarmclaw-org-avatar.png +0 -0
  9. package/public/branding/swarmclaw-org-avatar.svg +58 -0
  10. package/public/file.svg +1 -0
  11. package/public/globe.svg +1 -0
  12. package/public/next.svg +1 -0
  13. package/public/screenshots/agents.png +0 -0
  14. package/public/screenshots/connectors.png +0 -0
  15. package/public/screenshots/dashboard.png +0 -0
  16. package/public/screenshots/new-session-openclaw.png +0 -0
  17. package/public/screenshots/providers.png +0 -0
  18. package/public/screenshots/schedules.png +0 -0
  19. package/public/screenshots/tasks.png +0 -0
  20. package/public/vercel.svg +1 -0
  21. package/public/window.svg +1 -0
  22. package/src/app/api/agents/[id]/route.ts +30 -0
  23. package/src/app/api/agents/[id]/thread/route.ts +66 -0
  24. package/src/app/api/agents/generate/route.ts +42 -0
  25. package/src/app/api/agents/route.ts +33 -0
  26. package/src/app/api/auth/route.ts +25 -0
  27. package/src/app/api/claude-skills/route.ts +42 -0
  28. package/src/app/api/clawhub/install/route.ts +39 -0
  29. package/src/app/api/clawhub/search/route.ts +11 -0
  30. package/src/app/api/connectors/[id]/route.ts +79 -0
  31. package/src/app/api/connectors/route.ts +60 -0
  32. package/src/app/api/credentials/[id]/route.ts +14 -0
  33. package/src/app/api/credentials/route.ts +31 -0
  34. package/src/app/api/daemon/health-check/route.ts +11 -0
  35. package/src/app/api/daemon/route.ts +22 -0
  36. package/src/app/api/dirs/pick/route.ts +60 -0
  37. package/src/app/api/dirs/route.ts +29 -0
  38. package/src/app/api/documents/[id]/route.ts +47 -0
  39. package/src/app/api/documents/route.ts +93 -0
  40. package/src/app/api/files/serve/route.ts +69 -0
  41. package/src/app/api/generate/info/route.ts +12 -0
  42. package/src/app/api/generate/route.ts +106 -0
  43. package/src/app/api/ip/route.ts +6 -0
  44. package/src/app/api/knowledge/[id]/route.ts +61 -0
  45. package/src/app/api/knowledge/route.ts +48 -0
  46. package/src/app/api/knowledge/upload/route.ts +86 -0
  47. package/src/app/api/logs/route.ts +65 -0
  48. package/src/app/api/mcp-servers/[id]/route.ts +32 -0
  49. package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
  50. package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
  51. package/src/app/api/mcp-servers/route.ts +27 -0
  52. package/src/app/api/memory/[id]/route.ts +126 -0
  53. package/src/app/api/memory/maintenance/route.ts +63 -0
  54. package/src/app/api/memory/route.ts +111 -0
  55. package/src/app/api/memory-images/[filename]/route.ts +36 -0
  56. package/src/app/api/orchestrator/run/route.ts +43 -0
  57. package/src/app/api/plugins/install/route.ts +58 -0
  58. package/src/app/api/plugins/marketplace/route.ts +33 -0
  59. package/src/app/api/plugins/route.ts +21 -0
  60. package/src/app/api/preview-server/route.ts +339 -0
  61. package/src/app/api/providers/[id]/models/route.ts +29 -0
  62. package/src/app/api/providers/[id]/route.ts +34 -0
  63. package/src/app/api/providers/configs/route.ts +7 -0
  64. package/src/app/api/providers/ollama/route.ts +30 -0
  65. package/src/app/api/providers/openclaw/health/route.ts +23 -0
  66. package/src/app/api/providers/route.ts +28 -0
  67. package/src/app/api/runs/[id]/route.ts +9 -0
  68. package/src/app/api/runs/route.ts +13 -0
  69. package/src/app/api/schedules/[id]/route.ts +28 -0
  70. package/src/app/api/schedules/[id]/run/route.ts +104 -0
  71. package/src/app/api/schedules/route.ts +78 -0
  72. package/src/app/api/secrets/[id]/route.ts +29 -0
  73. package/src/app/api/secrets/route.ts +42 -0
  74. package/src/app/api/sessions/[id]/browser/route.ts +13 -0
  75. package/src/app/api/sessions/[id]/chat/route.ts +96 -0
  76. package/src/app/api/sessions/[id]/clear/route.ts +19 -0
  77. package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
  78. package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
  79. package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
  80. package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
  81. package/src/app/api/sessions/[id]/messages/route.ts +9 -0
  82. package/src/app/api/sessions/[id]/retry/route.ts +28 -0
  83. package/src/app/api/sessions/[id]/route.ts +103 -0
  84. package/src/app/api/sessions/[id]/stop/route.ts +13 -0
  85. package/src/app/api/sessions/heartbeat/route.ts +26 -0
  86. package/src/app/api/sessions/route.ts +85 -0
  87. package/src/app/api/settings/route.ts +58 -0
  88. package/src/app/api/setup/check-provider/route.ts +326 -0
  89. package/src/app/api/setup/doctor/route.ts +250 -0
  90. package/src/app/api/skills/[id]/route.ts +40 -0
  91. package/src/app/api/skills/import/route.ts +69 -0
  92. package/src/app/api/skills/route.ts +28 -0
  93. package/src/app/api/tasks/[id]/route.ts +102 -0
  94. package/src/app/api/tasks/route.ts +115 -0
  95. package/src/app/api/tts/route.ts +40 -0
  96. package/src/app/api/upload/route.ts +18 -0
  97. package/src/app/api/uploads/[filename]/route.ts +59 -0
  98. package/src/app/api/usage/route.ts +35 -0
  99. package/src/app/api/version/route.ts +81 -0
  100. package/src/app/api/version/update/route.ts +95 -0
  101. package/src/app/api/webhooks/[id]/history/route.ts +13 -0
  102. package/src/app/api/webhooks/[id]/route.ts +204 -0
  103. package/src/app/api/webhooks/route.ts +37 -0
  104. package/src/app/favicon.ico +0 -0
  105. package/src/app/globals.css +370 -0
  106. package/src/app/layout.tsx +52 -0
  107. package/src/app/page.tsx +172 -0
  108. package/src/cli/index.js +1232 -0
  109. package/src/cli/index.test.js +281 -0
  110. package/src/cli/index.ts +1158 -0
  111. package/src/cli/spec.js +284 -0
  112. package/src/components/agents/agent-card.tsx +219 -0
  113. package/src/components/agents/agent-chat-list.tsx +165 -0
  114. package/src/components/agents/agent-list.tsx +110 -0
  115. package/src/components/agents/agent-sheet.tsx +1220 -0
  116. package/src/components/auth/access-key-gate.tsx +248 -0
  117. package/src/components/auth/setup-wizard.tsx +940 -0
  118. package/src/components/auth/user-picker.tsx +88 -0
  119. package/src/components/chat/chat-area.tsx +406 -0
  120. package/src/components/chat/chat-header.tsx +491 -0
  121. package/src/components/chat/chat-tool-toggles.tsx +161 -0
  122. package/src/components/chat/code-block.tsx +146 -0
  123. package/src/components/chat/dev-server-bar.tsx +39 -0
  124. package/src/components/chat/message-bubble.tsx +486 -0
  125. package/src/components/chat/message-list.tsx +299 -0
  126. package/src/components/chat/session-debug-panel.tsx +196 -0
  127. package/src/components/chat/streaming-bubble.tsx +85 -0
  128. package/src/components/chat/thinking-indicator.tsx +26 -0
  129. package/src/components/chat/tool-call-bubble.tsx +438 -0
  130. package/src/components/chat/tool-request-banner.tsx +103 -0
  131. package/src/components/connectors/connector-list.tsx +196 -0
  132. package/src/components/connectors/connector-sheet.tsx +804 -0
  133. package/src/components/input/chat-input.tsx +235 -0
  134. package/src/components/knowledge/knowledge-list.tsx +206 -0
  135. package/src/components/knowledge/knowledge-sheet.tsx +316 -0
  136. package/src/components/layout/app-layout.tsx +1016 -0
  137. package/src/components/layout/daemon-indicator.tsx +56 -0
  138. package/src/components/layout/mobile-header.tsx +31 -0
  139. package/src/components/layout/network-banner.tsx +17 -0
  140. package/src/components/layout/update-banner.tsx +130 -0
  141. package/src/components/logs/log-list.tsx +358 -0
  142. package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
  143. package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
  144. package/src/components/memory/memory-card.tsx +63 -0
  145. package/src/components/memory/memory-detail.tsx +339 -0
  146. package/src/components/memory/memory-list.tsx +198 -0
  147. package/src/components/memory/memory-sheet.tsx +70 -0
  148. package/src/components/plugins/plugin-list.tsx +60 -0
  149. package/src/components/plugins/plugin-sheet.tsx +311 -0
  150. package/src/components/providers/provider-list.tsx +96 -0
  151. package/src/components/providers/provider-sheet.tsx +542 -0
  152. package/src/components/runs/run-list.tsx +231 -0
  153. package/src/components/schedules/schedule-card.tsx +63 -0
  154. package/src/components/schedules/schedule-list.tsx +76 -0
  155. package/src/components/schedules/schedule-sheet.tsx +336 -0
  156. package/src/components/secrets/secret-sheet.tsx +180 -0
  157. package/src/components/secrets/secrets-list.tsx +91 -0
  158. package/src/components/sessions/new-session-sheet.tsx +478 -0
  159. package/src/components/sessions/session-card.tsx +144 -0
  160. package/src/components/sessions/session-list.tsx +202 -0
  161. package/src/components/shared/ai-gen-block.tsx +77 -0
  162. package/src/components/shared/avatar.tsx +48 -0
  163. package/src/components/shared/bottom-sheet.tsx +30 -0
  164. package/src/components/shared/confirm-dialog.tsx +47 -0
  165. package/src/components/shared/connector-platform-icon.tsx +113 -0
  166. package/src/components/shared/dir-browser.tsx +285 -0
  167. package/src/components/shared/dropdown.tsx +55 -0
  168. package/src/components/shared/icon-button.tsx +25 -0
  169. package/src/components/shared/settings/plugin-manager.tsx +207 -0
  170. package/src/components/shared/settings/section-capability-policy.tsx +93 -0
  171. package/src/components/shared/settings/section-embedding.tsx +99 -0
  172. package/src/components/shared/settings/section-heartbeat.tsx +168 -0
  173. package/src/components/shared/settings/section-memory.tsx +77 -0
  174. package/src/components/shared/settings/section-orchestrator.tsx +108 -0
  175. package/src/components/shared/settings/section-providers.tsx +181 -0
  176. package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
  177. package/src/components/shared/settings/section-secrets.tsx +132 -0
  178. package/src/components/shared/settings/section-user-preferences.tsx +24 -0
  179. package/src/components/shared/settings/section-voice.tsx +53 -0
  180. package/src/components/shared/settings/settings-sheet.tsx +88 -0
  181. package/src/components/shared/settings/types.ts +7 -0
  182. package/src/components/shared/settings/utils.ts +13 -0
  183. package/src/components/shared/settings-sheet.tsx +1 -0
  184. package/src/components/shared/skeleton.tsx +19 -0
  185. package/src/components/shared/usage-badge.tsx +28 -0
  186. package/src/components/skills/clawhub-browser.tsx +225 -0
  187. package/src/components/skills/skill-list.tsx +70 -0
  188. package/src/components/skills/skill-sheet.tsx +254 -0
  189. package/src/components/tasks/task-board.tsx +96 -0
  190. package/src/components/tasks/task-card.tsx +179 -0
  191. package/src/components/tasks/task-column.tsx +73 -0
  192. package/src/components/tasks/task-list.tsx +118 -0
  193. package/src/components/tasks/task-sheet.tsx +415 -0
  194. package/src/components/ui/avatar.tsx +109 -0
  195. package/src/components/ui/badge.tsx +48 -0
  196. package/src/components/ui/button.tsx +64 -0
  197. package/src/components/ui/card.tsx +92 -0
  198. package/src/components/ui/dialog.tsx +158 -0
  199. package/src/components/ui/dropdown-menu.tsx +257 -0
  200. package/src/components/ui/input.tsx +21 -0
  201. package/src/components/ui/scroll-area.tsx +58 -0
  202. package/src/components/ui/select.tsx +190 -0
  203. package/src/components/ui/separator.tsx +28 -0
  204. package/src/components/ui/sheet.tsx +143 -0
  205. package/src/components/ui/sonner.tsx +22 -0
  206. package/src/components/ui/textarea.tsx +18 -0
  207. package/src/components/ui/tooltip.tsx +56 -0
  208. package/src/components/usage/usage-list.tsx +105 -0
  209. package/src/components/webhooks/webhook-list.tsx +166 -0
  210. package/src/components/webhooks/webhook-sheet.tsx +402 -0
  211. package/src/hooks/use-auto-resize.ts +20 -0
  212. package/src/hooks/use-media-query.ts +21 -0
  213. package/src/hooks/use-speech-recognition.ts +83 -0
  214. package/src/instrumentation.ts +8 -0
  215. package/src/lib/agents.ts +13 -0
  216. package/src/lib/api-client.ts +100 -0
  217. package/src/lib/chat.ts +60 -0
  218. package/src/lib/memory.ts +42 -0
  219. package/src/lib/openclaw-endpoint.test.ts +48 -0
  220. package/src/lib/openclaw-endpoint.ts +67 -0
  221. package/src/lib/provider-config.ts +13 -0
  222. package/src/lib/providers/anthropic.ts +135 -0
  223. package/src/lib/providers/claude-cli.ts +202 -0
  224. package/src/lib/providers/codex-cli.ts +260 -0
  225. package/src/lib/providers/index.ts +351 -0
  226. package/src/lib/providers/ollama.ts +131 -0
  227. package/src/lib/providers/openai.ts +164 -0
  228. package/src/lib/providers/openclaw.ts +330 -0
  229. package/src/lib/providers/opencode-cli.ts +164 -0
  230. package/src/lib/runtime-loop.ts +15 -0
  231. package/src/lib/schedule-dedupe.test.ts +84 -0
  232. package/src/lib/schedule-dedupe.ts +174 -0
  233. package/src/lib/schedule-name.ts +62 -0
  234. package/src/lib/schedules.ts +16 -0
  235. package/src/lib/server/agent-registry.ts +70 -0
  236. package/src/lib/server/api-routes.test.ts +362 -0
  237. package/src/lib/server/autonomy-contract.ts +200 -0
  238. package/src/lib/server/build-llm.ts +155 -0
  239. package/src/lib/server/capability-router.test.ts +21 -0
  240. package/src/lib/server/capability-router.ts +172 -0
  241. package/src/lib/server/chat-execution.ts +894 -0
  242. package/src/lib/server/clawhub-client.test.ts +161 -0
  243. package/src/lib/server/clawhub-client.ts +26 -0
  244. package/src/lib/server/connectors/connector-routing.test.ts +243 -0
  245. package/src/lib/server/connectors/discord.ts +116 -0
  246. package/src/lib/server/connectors/googlechat.ts +66 -0
  247. package/src/lib/server/connectors/manager.ts +559 -0
  248. package/src/lib/server/connectors/matrix.ts +78 -0
  249. package/src/lib/server/connectors/media.ts +149 -0
  250. package/src/lib/server/connectors/openclaw.test.ts +375 -0
  251. package/src/lib/server/connectors/openclaw.ts +1132 -0
  252. package/src/lib/server/connectors/signal.ts +183 -0
  253. package/src/lib/server/connectors/slack.ts +258 -0
  254. package/src/lib/server/connectors/teams.ts +94 -0
  255. package/src/lib/server/connectors/telegram.ts +221 -0
  256. package/src/lib/server/connectors/types.ts +62 -0
  257. package/src/lib/server/connectors/whatsapp.ts +349 -0
  258. package/src/lib/server/context-manager.ts +232 -0
  259. package/src/lib/server/cost.ts +31 -0
  260. package/src/lib/server/daemon-state.ts +354 -0
  261. package/src/lib/server/data-dir.ts +3 -0
  262. package/src/lib/server/embeddings.ts +111 -0
  263. package/src/lib/server/execution-log.ts +257 -0
  264. package/src/lib/server/gateway/protocol.test.ts +54 -0
  265. package/src/lib/server/gateway/protocol.ts +114 -0
  266. package/src/lib/server/heartbeat-service.ts +366 -0
  267. package/src/lib/server/knowledge-db.test.ts +441 -0
  268. package/src/lib/server/logger.ts +47 -0
  269. package/src/lib/server/main-agent-loop.ts +1017 -0
  270. package/src/lib/server/mcp-client.test.ts +342 -0
  271. package/src/lib/server/mcp-client.ts +130 -0
  272. package/src/lib/server/memory-db.ts +1078 -0
  273. package/src/lib/server/memory-graph.test.ts +153 -0
  274. package/src/lib/server/memory-graph.ts +138 -0
  275. package/src/lib/server/openclaw-health.ts +245 -0
  276. package/src/lib/server/orchestrator-lg.ts +431 -0
  277. package/src/lib/server/orchestrator.ts +364 -0
  278. package/src/lib/server/playwright-proxy.mjs +70 -0
  279. package/src/lib/server/plugins.ts +229 -0
  280. package/src/lib/server/process-manager.ts +327 -0
  281. package/src/lib/server/provider-health.ts +113 -0
  282. package/src/lib/server/queue.ts +859 -0
  283. package/src/lib/server/runtime-settings.ts +119 -0
  284. package/src/lib/server/scheduler.ts +196 -0
  285. package/src/lib/server/session-mailbox.ts +129 -0
  286. package/src/lib/server/session-run-manager.ts +512 -0
  287. package/src/lib/server/session-tools/connector.ts +124 -0
  288. package/src/lib/server/session-tools/context-mgmt.ts +103 -0
  289. package/src/lib/server/session-tools/context.ts +114 -0
  290. package/src/lib/server/session-tools/crud.ts +673 -0
  291. package/src/lib/server/session-tools/delegate.ts +708 -0
  292. package/src/lib/server/session-tools/file.ts +264 -0
  293. package/src/lib/server/session-tools/index.ts +164 -0
  294. package/src/lib/server/session-tools/memory.ts +230 -0
  295. package/src/lib/server/session-tools/session-info.ts +422 -0
  296. package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
  297. package/src/lib/server/session-tools/shell.ts +171 -0
  298. package/src/lib/server/session-tools/web.ts +408 -0
  299. package/src/lib/server/session-tools.ts +9 -0
  300. package/src/lib/server/skills-normalize.ts +130 -0
  301. package/src/lib/server/storage-mcp.test.ts +161 -0
  302. package/src/lib/server/storage.ts +670 -0
  303. package/src/lib/server/stream-agent-chat.ts +571 -0
  304. package/src/lib/server/task-reports.ts +122 -0
  305. package/src/lib/server/task-result.ts +161 -0
  306. package/src/lib/server/task-validation.test.ts +27 -0
  307. package/src/lib/server/task-validation.ts +90 -0
  308. package/src/lib/server/tool-capability-policy.test.ts +58 -0
  309. package/src/lib/server/tool-capability-policy.ts +262 -0
  310. package/src/lib/sessions.ts +68 -0
  311. package/src/lib/tasks.ts +20 -0
  312. package/src/lib/tts.ts +42 -0
  313. package/src/lib/upload.ts +10 -0
  314. package/src/lib/utils.ts +6 -0
  315. package/src/proxy.ts +43 -0
  316. package/src/stores/use-app-store.ts +468 -0
  317. package/src/stores/use-chat-store.ts +323 -0
  318. package/src/types/index.ts +621 -0
  319. package/tsconfig.json +34 -0
@@ -0,0 +1,235 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useRef, useState } from 'react'
4
+ import { useChatStore } from '@/stores/use-chat-store'
5
+ import { useAppStore } from '@/stores/use-app-store'
6
+ import { uploadImage } from '@/lib/upload'
7
+ import { useAutoResize } from '@/hooks/use-auto-resize'
8
+ import { useSpeechRecognition } from '@/hooks/use-speech-recognition'
9
+
10
+ interface Props {
11
+ streaming: boolean
12
+ onSend: (text: string) => void
13
+ onStop: () => void
14
+ }
15
+
16
+ export function ChatInput({ streaming, onSend, onStop }: Props) {
17
+ const [value, setValue] = useState('')
18
+ const { ref: textareaRef, resize } = useAutoResize()
19
+ const fileInputRef = useRef<HTMLInputElement>(null)
20
+ const pendingImage = useChatStore((s) => s.pendingImage)
21
+ const setPendingImage = useChatStore((s) => s.setPendingImage)
22
+ const speechRecognitionLang = useAppStore((s) => s.appSettings.speechRecognitionLang)
23
+
24
+ const handleSend = useCallback(() => {
25
+ const text = value.trim()
26
+ if (!text || streaming) return
27
+ onSend(text)
28
+ setValue('')
29
+ if (textareaRef.current) {
30
+ textareaRef.current.style.height = 'auto'
31
+ }
32
+ }, [value, streaming, onSend])
33
+
34
+ const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
35
+ if (e.key === 'Enter' && !e.shiftKey) {
36
+ e.preventDefault()
37
+ handleSend()
38
+ }
39
+ }, [handleSend])
40
+
41
+ const handleVoice = useCallback((text: string) => {
42
+ onSend(text)
43
+ }, [onSend])
44
+
45
+ const { recording, toggle: toggleRecording, supported: micSupported, error: micError } = useSpeechRecognition(
46
+ handleVoice,
47
+ { lang: speechRecognitionLang || undefined },
48
+ )
49
+
50
+ const handlePaste = useCallback(async (e: React.ClipboardEvent) => {
51
+ const items = e.clipboardData?.items
52
+ if (!items) return
53
+ for (const item of items) {
54
+ if (item.type.startsWith('image/')) {
55
+ e.preventDefault()
56
+ const file = item.getAsFile()
57
+ if (!file) return
58
+ try {
59
+ const result = await uploadImage(file)
60
+ setPendingImage({ file, path: result.path, url: result.url })
61
+ } catch {
62
+ // ignore
63
+ }
64
+ return
65
+ }
66
+ }
67
+ }, [])
68
+
69
+ const handleFileChange = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
70
+ const file = e.target.files?.[0]
71
+ if (!file) return
72
+ try {
73
+ const result = await uploadImage(file)
74
+ setPendingImage({
75
+ file,
76
+ path: result.path,
77
+ url: result.url,
78
+ })
79
+ } catch {
80
+ // ignore
81
+ }
82
+ e.target.value = ''
83
+ }, [])
84
+
85
+ const hasContent = value.trim().length > 0 || !!pendingImage
86
+
87
+ return (
88
+ <div className="shrink-0 px-6 md:px-12 lg:px-16 pb-4 pt-2"
89
+ style={{ paddingBottom: 'max(16px, env(safe-area-inset-bottom))' }}>
90
+ <div>
91
+ {streaming && (
92
+ <div className="flex justify-center py-2 mb-2">
93
+ <button
94
+ onClick={onStop}
95
+ className="px-6 py-2.5 rounded-pill border border-danger/20 bg-danger/[0.06]
96
+ text-danger text-[13px] font-600 cursor-pointer transition-all duration-200
97
+ active:scale-95 hover:bg-danger/[0.1] hover:border-danger/30"
98
+ style={{ fontFamily: 'inherit' }}
99
+ >
100
+ Stop generating
101
+ </button>
102
+ </div>
103
+ )}
104
+
105
+ <div className="glass rounded-[20px] overflow-hidden
106
+ 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
+
108
+ {pendingImage && (
109
+ <div className="flex items-center gap-2 px-5 pt-4">
110
+ <div className="relative">
111
+ {pendingImage.file.type.startsWith('image/') ? (
112
+ <img
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
+ &times;
133
+ </button>
134
+ </div>
135
+ </div>
136
+ )}
137
+
138
+ <textarea
139
+ ref={textareaRef}
140
+ value={value}
141
+ onChange={(e) => { setValue(e.target.value); resize() }}
142
+ onKeyDown={handleKeyDown}
143
+ onPaste={handlePaste}
144
+ placeholder="Ask me anything..."
145
+ rows={1}
146
+ className="w-full px-5 pt-4 pb-2 bg-transparent text-text text-[15px] outline-none resize-none
147
+ max-h-[140px] leading-[1.55] placeholder:text-text-3/70 border-none"
148
+ style={{ fontFamily: 'inherit' }}
149
+ />
150
+
151
+ <div className="flex items-center gap-1 px-4 pb-3.5">
152
+ <button
153
+ onClick={() => fileInputRef.current?.click()}
154
+ className="flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-transparent
155
+ text-text-3 text-[13px] cursor-pointer hover:text-text-2 hover:bg-white/[0.05] transition-all duration-200"
156
+ style={{ fontFamily: 'inherit' }}
157
+ >
158
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
159
+ <path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" />
160
+ </svg>
161
+ <span className="hidden sm:inline">Attach</span>
162
+ </button>
163
+
164
+ <button
165
+ onClick={() => fileInputRef.current?.click()}
166
+ className="flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-transparent
167
+ text-text-3 text-[13px] cursor-pointer hover:text-text-2 hover:bg-white/[0.05] transition-all duration-200"
168
+ style={{ fontFamily: 'inherit' }}
169
+ >
170
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
171
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
172
+ <circle cx="8.5" cy="8.5" r="1.5" />
173
+ <polyline points="21 15 16 10 5 21" />
174
+ </svg>
175
+ <span className="hidden sm:inline">Image</span>
176
+ </button>
177
+
178
+ {micSupported && (
179
+ <button
180
+ onClick={toggleRecording}
181
+ className={`flex items-center gap-1.5 px-3 py-2 rounded-[10px] border-none bg-transparent
182
+ text-[13px] cursor-pointer transition-all duration-200
183
+ ${recording ? 'text-danger' : 'text-text-3 hover:text-text-2 hover:bg-white/[0.05]'}`}
184
+ style={recording ? { animation: 'mic-pulse 1.5s ease-out infinite', fontFamily: 'inherit' } : { fontFamily: 'inherit' }}
185
+ >
186
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
187
+ <rect x="9" y="2" width="6" height="11" rx="3" />
188
+ <path d="M5 10a7 7 0 0 0 14 0" />
189
+ <line x1="12" y1="19" x2="12" y2="22" />
190
+ </svg>
191
+ </button>
192
+ )}
193
+
194
+ <div className="flex-1" />
195
+
196
+ <span className="text-[11px] text-text-3/60 tabular-nums mr-2 font-mono">
197
+ {value.length > 0 && value.length}
198
+ </span>
199
+
200
+ <button
201
+ onClick={handleSend}
202
+ disabled={!hasContent || streaming}
203
+ className={`w-9 h-9 rounded-[11px] border-none flex items-center justify-center
204
+ shrink-0 cursor-pointer transition-all duration-250
205
+ ${hasContent && !streaming
206
+ ? 'bg-[#6366F1] text-white active:scale-90 shadow-[0_4px_16px_rgba(99,102,241,0.3)]'
207
+ : 'bg-white/[0.04] text-text-3 pointer-events-none'}`}
208
+ >
209
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
210
+ <line x1="12" y1="19" x2="12" y2="5" />
211
+ <polyline points="5 12 12 5 19 12" />
212
+ </svg>
213
+ </button>
214
+ </div>
215
+ </div>
216
+
217
+ <input
218
+ ref={fileInputRef}
219
+ type="file"
220
+ 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"
221
+ onChange={handleFileChange}
222
+ className="hidden"
223
+ />
224
+
225
+ <p className="text-[10px] text-text-3/40 mt-1.5 px-1 select-none">
226
+ Shift+Enter for newline
227
+ </p>
228
+
229
+ {micError && (
230
+ <p className="text-[11px] text-danger/80 mt-2 px-1">{micError}</p>
231
+ )}
232
+ </div>
233
+ </div>
234
+ )
235
+ }
@@ -0,0 +1,206 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
4
+ import { api } from '@/lib/api-client'
5
+ import { useAppStore } from '@/stores/use-app-store'
6
+ import { Badge } from '@/components/ui/badge'
7
+ import type { MemoryEntry } from '@/types'
8
+
9
+ export function KnowledgeList() {
10
+ const [search, setSearch] = useState('')
11
+ const [entries, setEntries] = useState<MemoryEntry[]>([])
12
+ const [loaded, setLoaded] = useState(false)
13
+ const [error, setError] = useState<string | null>(null)
14
+ const [activeTag, setActiveTag] = useState<string | null>(null)
15
+ const [selectedId, setSelectedId] = useState<string | null>(null)
16
+ const searchRef = useRef(search)
17
+ const setKnowledgeSheetOpen = useAppStore((s) => s.setKnowledgeSheetOpen)
18
+ const setEditingKnowledgeId = useAppStore((s) => s.setEditingKnowledgeId)
19
+
20
+ const openSheet = useCallback((id?: string) => {
21
+ setEditingKnowledgeId(id ?? null)
22
+ setKnowledgeSheetOpen(true)
23
+ }, [setEditingKnowledgeId, setKnowledgeSheetOpen])
24
+
25
+ const load = useCallback(async (query: string, tag?: string | null) => {
26
+ try {
27
+ const params = new URLSearchParams()
28
+ if (query) params.set('q', query)
29
+ if (tag) params.set('tags', tag)
30
+ const qs = params.toString()
31
+ const results = await api<MemoryEntry[]>('GET', `/knowledge${qs ? `?${qs}` : ''}`)
32
+ setEntries(Array.isArray(results) ? results : [])
33
+ setError(null)
34
+ } catch {
35
+ setError('Unable to load knowledge entries.')
36
+ }
37
+ setLoaded(true)
38
+ }, [])
39
+
40
+ useEffect(() => { searchRef.current = search }, [search])
41
+
42
+ // Initial load
43
+ useEffect(() => {
44
+ const timer = setTimeout(() => { void load(searchRef.current, activeTag) }, 0)
45
+ return () => clearTimeout(timer)
46
+ }, [load, activeTag])
47
+
48
+ // Debounced search
49
+ useEffect(() => {
50
+ const timer = setTimeout(() => { void load(search, activeTag) }, 300)
51
+ return () => clearTimeout(timer)
52
+ }, [search, load, activeTag])
53
+
54
+ const uniqueTags = useMemo(() => {
55
+ const tags = new Set<string>()
56
+ for (const e of entries) {
57
+ const meta = e.metadata as { tags?: string[] } | undefined
58
+ if (meta?.tags) for (const t of meta.tags) tags.add(t)
59
+ }
60
+ return Array.from(tags).sort()
61
+ }, [entries])
62
+
63
+ const handleDelete = async (id: string) => {
64
+ try {
65
+ await api('DELETE', `/knowledge/${id}`)
66
+ setEntries((prev) => prev.filter((e) => e.id !== id))
67
+ if (selectedId === id) setSelectedId(null)
68
+ } catch {
69
+ // silent
70
+ }
71
+ }
72
+
73
+ const formatDate = (ts: number) => {
74
+ return new Date(ts).toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })
75
+ }
76
+
77
+ return (
78
+ <div className="flex-1 flex flex-col overflow-y-auto">
79
+ {/* Search — only show when there are entries */}
80
+ {entries.length > 0 && (
81
+ <div className="px-3 py-2 shrink-0">
82
+ <input
83
+ type="text"
84
+ value={search}
85
+ onChange={(e) => setSearch(e.target.value)}
86
+ placeholder="Search knowledge..."
87
+ className="w-full px-3 py-2 rounded-[10px] border border-white/[0.04] bg-surface text-text
88
+ text-[12px] outline-none transition-all duration-200 placeholder:text-text-3/70 focus-glow"
89
+ style={{ fontFamily: 'inherit' }}
90
+ />
91
+ </div>
92
+ )}
93
+
94
+ {/* Tag filters */}
95
+ {uniqueTags.length > 0 && (
96
+ <div className="px-3 pb-1.5 shrink-0">
97
+ <div className="flex gap-1 flex-wrap">
98
+ <button
99
+ onClick={() => setActiveTag(null)}
100
+ className={`px-2 py-0.5 rounded-[6px] text-[9px] font-600 cursor-pointer transition-all uppercase tracking-wider
101
+ ${!activeTag ? 'bg-white/[0.06] text-text-2' : 'bg-transparent text-text-3/70 hover:text-text-3'}`}
102
+ style={{ fontFamily: 'inherit' }}
103
+ >
104
+ all
105
+ </button>
106
+ {uniqueTags.map((tag) => (
107
+ <button
108
+ key={tag}
109
+ onClick={() => setActiveTag(activeTag === tag ? null : tag)}
110
+ className={`px-2 py-0.5 rounded-[6px] text-[9px] font-600 cursor-pointer transition-all uppercase tracking-wider
111
+ ${activeTag === tag ? 'bg-white/[0.06] text-text-2' : 'bg-transparent text-text-3/70 hover:text-text-3'}`}
112
+ style={{ fontFamily: 'inherit' }}
113
+ >
114
+ {tag}
115
+ </button>
116
+ ))}
117
+ </div>
118
+ </div>
119
+ )}
120
+
121
+ {/* Entries */}
122
+ {entries.length > 0 ? (
123
+ <div className="flex flex-col gap-0.5 px-2 pb-4">
124
+ {entries.map((entry) => {
125
+ const meta = entry.metadata as { tags?: string[] } | undefined
126
+ const tags = meta?.tags || []
127
+ return (
128
+ <div
129
+ key={entry.id}
130
+ onClick={() => setSelectedId(selectedId === entry.id ? null : entry.id)}
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
+ }`}
136
+ >
137
+ <div className="flex items-start justify-between gap-2 mb-1">
138
+ <span className="font-display text-[13px] font-600 text-text truncate">{entry.title}</span>
139
+ <span className="text-[10px] text-text-3/50 shrink-0">{formatDate(entry.createdAt)}</span>
140
+ </div>
141
+ <p className="text-[11px] text-text-3/60 line-clamp-2 mb-2">
142
+ {entry.content.slice(0, 200)}
143
+ </p>
144
+ {tags.length > 0 && (
145
+ <div className="flex gap-1 flex-wrap mb-2">
146
+ {tags.map((t) => (
147
+ <Badge key={t} variant="secondary" className="text-[9px] px-1.5 py-0">{t}</Badge>
148
+ ))}
149
+ </div>
150
+ )}
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
+ </div>
170
+ )
171
+ })}
172
+ </div>
173
+ ) : error ? (
174
+ <div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center">
175
+ <p className="font-display text-[14px] font-600 text-text-2">Couldn&apos;t load knowledge</p>
176
+ <p className="text-[12px] text-text-3/60">{error}</p>
177
+ <button
178
+ onClick={() => { void load(search, activeTag) }}
179
+ className="px-3 py-1.5 rounded-[8px] bg-accent-soft text-accent-bright text-[12px] font-600 cursor-pointer border-none"
180
+ style={{ fontFamily: 'inherit' }}
181
+ >
182
+ Retry
183
+ </button>
184
+ </div>
185
+ ) : loaded ? (
186
+ <div className="flex-1 flex flex-col items-center justify-center gap-4 text-text-3 p-8 text-center">
187
+ <div className="w-12 h-12 rounded-[14px] bg-accent-soft flex items-center justify-center mb-1">
188
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright">
189
+ <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
190
+ <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
191
+ </svg>
192
+ </div>
193
+ <p className="font-display text-[15px] font-600 text-text-2">No knowledge entries yet</p>
194
+ <p className="text-[13px] text-text-3/50">Add shared knowledge for your agents</p>
195
+ <button
196
+ onClick={() => openSheet()}
197
+ className="mt-1 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"
198
+ style={{ fontFamily: 'inherit' }}
199
+ >
200
+ + Add Knowledge
201
+ </button>
202
+ </div>
203
+ ) : null}
204
+ </div>
205
+ )
206
+ }