@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,88 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useAppStore } from '@/stores/use-app-store'
5
+ import { api } from '@/lib/api-client'
6
+
7
+ export function UserPicker() {
8
+ const setUser = useAppStore((s) => s.setUser)
9
+ const [name, setName] = useState('')
10
+
11
+ const handleSubmit = async (e: React.FormEvent) => {
12
+ e.preventDefault()
13
+ const trimmed = name.trim()
14
+ if (!trimmed) return
15
+ const userName = trimmed.toLowerCase()
16
+ // Save server-side so it persists across devices
17
+ try {
18
+ await api('PUT', '/settings', { userName })
19
+ } catch { /* still set locally */ }
20
+ setUser(userName)
21
+ }
22
+
23
+ return (
24
+ <div className="h-full flex flex-col items-center justify-center px-8 bg-bg relative overflow-hidden">
25
+ {/* Atmospheric gradient mesh */}
26
+ <div className="absolute inset-0 pointer-events-none">
27
+ <div className="absolute top-[30%] left-[50%] -translate-x-1/2 -translate-y-1/2 w-[600px] h-[400px]"
28
+ style={{
29
+ background: 'radial-gradient(ellipse at center, rgba(99,102,241,0.06) 0%, transparent 70%)',
30
+ animation: 'glow-pulse 6s ease-in-out infinite',
31
+ }} />
32
+ <div className="absolute bottom-[20%] left-[30%] w-[300px] h-[300px]"
33
+ style={{
34
+ background: 'radial-gradient(circle, rgba(236,72,153,0.03) 0%, transparent 70%)',
35
+ animation: 'glow-pulse 8s ease-in-out infinite 2s',
36
+ }} />
37
+ </div>
38
+
39
+ <div className="relative max-w-[420px] w-full text-center"
40
+ style={{ animation: 'fade-in 0.6s cubic-bezier(0.16, 1, 0.3, 1)' }}>
41
+
42
+ {/* Sparkle icon */}
43
+ <div className="flex justify-center mb-6">
44
+ <div className="relative w-12 h-12">
45
+ <svg width="48" height="48" viewBox="0 0 48 48" fill="none" className="text-accent-bright"
46
+ style={{ animation: 'sparkle-spin 8s linear infinite' }}>
47
+ <path d="M24 4L27.5 18.5L42 24L27.5 29.5L24 44L20.5 29.5L6 24L20.5 18.5L24 4Z"
48
+ fill="currentColor" opacity="0.9" />
49
+ </svg>
50
+ <div className="absolute inset-0 blur-xl bg-accent-bright/20" />
51
+ </div>
52
+ </div>
53
+
54
+ <h1 className="font-display text-[42px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
55
+ Welcome
56
+ </h1>
57
+ <p className="text-[15px] text-text-2 mb-10">
58
+ What should we call you?
59
+ </p>
60
+
61
+ <form onSubmit={handleSubmit} className="flex flex-col items-center gap-5">
62
+ <input
63
+ type="text"
64
+ value={name}
65
+ onChange={(e) => setName(e.target.value)}
66
+ placeholder="Your name"
67
+ autoFocus
68
+ className="w-full max-w-[280px] px-6 py-4 rounded-[16px] border border-white/[0.08] bg-surface
69
+ text-text text-[18px] text-center font-display font-600 outline-none
70
+ transition-all duration-200 placeholder:text-text-3/70
71
+ focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
72
+ style={{ fontFamily: 'inherit' }}
73
+ />
74
+ <button
75
+ type="submit"
76
+ disabled={!name.trim()}
77
+ className="px-12 py-4 rounded-[16px] border-none bg-[#6366F1] text-white text-[16px] font-display font-600
78
+ cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
79
+ shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
80
+ style={{ fontFamily: 'inherit' }}
81
+ >
82
+ Get Started
83
+ </button>
84
+ </form>
85
+ </div>
86
+ </div>
87
+ )
88
+ }
@@ -0,0 +1,406 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useCallback, useState, useRef } from 'react'
4
+ import { useAppStore } from '@/stores/use-app-store'
5
+ import { useChatStore } from '@/stores/use-chat-store'
6
+ import { fetchMessages, clearMessages, deleteSession, devServer, checkBrowser, stopBrowser } from '@/lib/sessions'
7
+ import { uploadImage } from '@/lib/upload'
8
+ import { deleteAgent } from '@/lib/agents'
9
+ import { useMediaQuery } from '@/hooks/use-media-query'
10
+ import { ChatHeader } from './chat-header'
11
+ import { DevServerBar } from './dev-server-bar'
12
+ import { MessageList } from './message-list'
13
+ import { SessionDebugPanel } from './session-debug-panel'
14
+ import { ChatInput } from '@/components/input/chat-input'
15
+ import { Dropdown, DropdownItem } from '@/components/shared/dropdown'
16
+ import { ConfirmDialog } from '@/components/shared/confirm-dialog'
17
+ import { speak } from '@/lib/tts'
18
+
19
+ const PROMPT_SUGGESTIONS = [
20
+ { text: 'List all my sessions and agents', icon: 'book', gradient: 'from-[#6366F1]/10 to-[#818CF8]/5' },
21
+ { text: 'Help me set up a new connector', icon: 'link', gradient: 'from-[#EC4899]/10 to-[#F472B6]/5' },
22
+ { text: 'Create a new agent for me', icon: 'bot', gradient: 'from-[#34D399]/10 to-[#6EE7B7]/5' },
23
+ { text: 'Schedule a recurring task', icon: 'check', gradient: 'from-[#F59E0B]/10 to-[#FBBF24]/5' },
24
+ ]
25
+
26
+ export function ChatArea() {
27
+ const session = useAppStore((s) => {
28
+ const id = s.currentSessionId
29
+ return id ? s.sessions[id] : null
30
+ })
31
+ const sessionId = useAppStore((s) => s.currentSessionId)
32
+ const currentUser = useAppStore((s) => s.currentUser)
33
+ const setCurrentSession = useAppStore((s) => s.setCurrentSession)
34
+ const removeSessionFromStore = useAppStore((s) => s.removeSession)
35
+ const loadSessions = useAppStore((s) => s.loadSessions)
36
+ const appSettings = useAppStore((s) => s.appSettings)
37
+ const { messages, setMessages, streaming, streamingSessionId, sendMessage, stopStreaming, devServer: devServerStatus, setDevServer, debugOpen, setDebugOpen, ttsEnabled } = useChatStore()
38
+ const isDesktop = useMediaQuery('(min-width: 768px)')
39
+
40
+ const agents = useAppStore((s) => s.agents)
41
+ const loadAgents = useAppStore((s) => s.loadAgents)
42
+ const setEditingAgentId = useAppStore((s) => s.setEditingAgentId)
43
+ const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
44
+
45
+ const [menuOpen, setMenuOpen] = useState(false)
46
+ const [confirmDelete, setConfirmDelete] = useState(false)
47
+ const [confirmClear, setConfirmClear] = useState(false)
48
+ const [confirmDeleteAgent, setConfirmDeleteAgent] = useState(false)
49
+ const [browserActive, setBrowserActive] = useState(false)
50
+ const [isDragging, setIsDragging] = useState(false)
51
+ const dragCounter = useRef(0)
52
+ const setPendingImage = useChatStore((s) => s.setPendingImage)
53
+
54
+ useEffect(() => {
55
+ if (!sessionId) return
56
+ const chatState = useChatStore.getState()
57
+ const preserveLocalStream = chatState.streaming && chatState.streamingSessionId === sessionId
58
+ // Clear stale state from the previous session, but keep active local stream state for this session.
59
+ setMessages([])
60
+ if (!preserveLocalStream) {
61
+ useChatStore.setState({ streaming: false, streamingSessionId: null, streamText: '', toolEvents: [] })
62
+ }
63
+ fetchMessages(sessionId).then(setMessages).catch((err) => {
64
+ console.error('Failed to load messages:', err)
65
+ setMessages(session?.messages || [])
66
+ })
67
+ // If server reports session is still active, show streaming state
68
+ if (session?.active) {
69
+ useChatStore.setState({ streaming: true, streamingSessionId: sessionId, streamText: '' })
70
+ }
71
+ // Refresh active state from server so returning to a session restores typing indicator.
72
+ loadSessions().then(() => {
73
+ const refreshed = useAppStore.getState().sessions[sessionId]
74
+ if (refreshed?.active) {
75
+ useChatStore.setState({ streaming: true, streamingSessionId: sessionId, streamText: '' })
76
+ }
77
+ }).catch((err) => console.error('Failed to refresh messages:', err))
78
+ devServer(sessionId, 'status').then((r) => {
79
+ setDevServer(r.running ? r : null)
80
+ }).catch(() => setDevServer(null))
81
+ // Check browser status
82
+ if (session?.tools?.includes('browser')) {
83
+ checkBrowser(sessionId).then((r) => setBrowserActive(r.active)).catch((err) => { console.error('Browser check failed:', err); setBrowserActive(false) })
84
+ } else {
85
+ setBrowserActive(false)
86
+ }
87
+ }, [sessionId])
88
+
89
+ // Auto-poll messages for orchestrated or server-active sessions
90
+ const isOrchestrated = session?.sessionType === 'orchestrated'
91
+ const isServerActive = session?.active === true
92
+ const isOngoingMonitored = appSettings.loopMode === 'ongoing' && !!session?.tools?.length
93
+ useEffect(() => {
94
+ if (!sessionId || (!isOrchestrated && !isServerActive && !isOngoingMonitored)) return
95
+ const interval = setInterval(async () => {
96
+ try {
97
+ const msgs = await fetchMessages(sessionId)
98
+ if (msgs.length > messages.length) {
99
+ const newMsgs = msgs.slice(messages.length)
100
+ setMessages(msgs)
101
+ if (ttsEnabled && typeof document !== 'undefined' && document.visibilityState === 'visible') {
102
+ const latestAssistant = [...newMsgs].reverse().find((m) => {
103
+ if (m.role !== 'assistant') return false
104
+ const isHeartbeat = m.kind === 'heartbeat' || /^\s*HEARTBEAT_OK\b/i.test(m.text || '')
105
+ return !isHeartbeat && !!m.text?.trim()
106
+ })
107
+ if (latestAssistant?.text) {
108
+ void speak(latestAssistant.text)
109
+ }
110
+ }
111
+ }
112
+ // Check if session is still active on the server
113
+ if (isServerActive) {
114
+ await loadSessions()
115
+ }
116
+ } catch (err) { console.error('Failed to refresh messages:', err) }
117
+ }, 2000)
118
+ return () => clearInterval(interval)
119
+ }, [sessionId, isOrchestrated, isServerActive, isOngoingMonitored, messages.length])
120
+
121
+ // When server-active flag drops, stop the streaming indicator
122
+ useEffect(() => {
123
+ if (!sessionId) return
124
+ const state = useChatStore.getState()
125
+ if (
126
+ !isServerActive
127
+ && state.streaming
128
+ && (state.streamingSessionId === sessionId || state.streamingSessionId == null)
129
+ && !state.streamText
130
+ ) {
131
+ // Server finished but we weren't the ones streaming — clear the indicator
132
+ fetchMessages(sessionId).then(setMessages).catch(() => {})
133
+ useChatStore.setState({ streaming: false, streamingSessionId: null, streamText: '' })
134
+ }
135
+ }, [isServerActive, sessionId])
136
+
137
+ // Poll browser status while session has browser tools
138
+ const hasBrowserTool = session?.tools?.includes('browser')
139
+ useEffect(() => {
140
+ if (!sessionId || !hasBrowserTool) return
141
+ const interval = setInterval(() => {
142
+ checkBrowser(sessionId).then((r) => setBrowserActive(r.active)).catch(() => {})
143
+ }, 5000)
144
+ return () => clearInterval(interval)
145
+ }, [sessionId, hasBrowserTool])
146
+
147
+ const handleStopBrowser = useCallback(async () => {
148
+ if (!sessionId) return
149
+ await stopBrowser(sessionId)
150
+ setBrowserActive(false)
151
+ }, [sessionId])
152
+
153
+ const handleStopDevServer = useCallback(async () => {
154
+ if (!sessionId) return
155
+ await devServer(sessionId, 'stop')
156
+ setDevServer(null)
157
+ }, [sessionId])
158
+
159
+ const handleClear = useCallback(async () => {
160
+ setConfirmClear(false)
161
+ if (!sessionId) return
162
+ await clearMessages(sessionId)
163
+ setMessages([])
164
+ loadSessions()
165
+ }, [sessionId])
166
+
167
+ const handleDelete = useCallback(async () => {
168
+ setConfirmDelete(false)
169
+ if (!sessionId) return
170
+ await deleteSession(sessionId)
171
+ removeSessionFromStore(sessionId)
172
+ setCurrentSession(null)
173
+ }, [sessionId])
174
+
175
+ const handleBack = useCallback(() => {
176
+ setCurrentSession(null)
177
+ }, [])
178
+
179
+ const handlePrompt = useCallback((text: string) => {
180
+ sendMessage(text)
181
+ }, [sendMessage])
182
+
183
+ const handleDragOver = useCallback((e: React.DragEvent) => {
184
+ e.preventDefault()
185
+ e.stopPropagation()
186
+ }, [])
187
+
188
+ const handleDragEnter = useCallback((e: React.DragEvent) => {
189
+ e.preventDefault()
190
+ e.stopPropagation()
191
+ dragCounter.current++
192
+ if (e.dataTransfer.types.includes('Files')) setIsDragging(true)
193
+ }, [])
194
+
195
+ const handleDragLeave = useCallback((e: React.DragEvent) => {
196
+ e.preventDefault()
197
+ e.stopPropagation()
198
+ dragCounter.current--
199
+ if (dragCounter.current === 0) setIsDragging(false)
200
+ }, [])
201
+
202
+ const handleDrop = useCallback(async (e: React.DragEvent) => {
203
+ e.preventDefault()
204
+ e.stopPropagation()
205
+ dragCounter.current = 0
206
+ setIsDragging(false)
207
+ const file = e.dataTransfer.files?.[0]
208
+ if (!file) return
209
+ try {
210
+ const result = await uploadImage(file)
211
+ setPendingImage({ file, path: result.path, url: result.url })
212
+ } catch {
213
+ // ignore
214
+ }
215
+ }, [setPendingImage])
216
+
217
+ if (!session) return null
218
+
219
+ const streamingForThisSession = streaming && (!streamingSessionId || streamingSessionId === session.id)
220
+ const isMainChat = session.name === '__main__'
221
+ const isEmpty = !messages.length && !streamingForThisSession
222
+
223
+ return (
224
+ <div
225
+ className="flex-1 flex flex-col h-full min-h-0 relative"
226
+ onDragOver={handleDragOver}
227
+ onDragEnter={handleDragEnter}
228
+ onDragLeave={handleDragLeave}
229
+ onDrop={handleDrop}
230
+ >
231
+ {isDesktop && (
232
+ <ChatHeader
233
+ session={session}
234
+ streaming={streamingForThisSession}
235
+ onStop={stopStreaming}
236
+ onMenuToggle={() => setMenuOpen(!menuOpen)}
237
+ onBack={handleBack}
238
+ browserActive={browserActive}
239
+ onStopBrowser={handleStopBrowser}
240
+ />
241
+ )}
242
+ {!isDesktop && (
243
+ <ChatHeader
244
+ session={session}
245
+ streaming={streamingForThisSession}
246
+ onStop={stopStreaming}
247
+ onMenuToggle={() => setMenuOpen(!menuOpen)}
248
+ mobile
249
+ browserActive={browserActive}
250
+ onStopBrowser={handleStopBrowser}
251
+ />
252
+ )}
253
+ <DevServerBar status={devServerStatus} onStop={handleStopDevServer} />
254
+
255
+ {isEmpty ? (
256
+ <div className="flex-1 flex flex-col items-center justify-center px-6 pb-4 relative">
257
+ {/* Atmospheric background glow */}
258
+ <div className="absolute inset-0 pointer-events-none overflow-hidden">
259
+ <div className="absolute top-[20%] left-[50%] -translate-x-1/2 w-[500px] h-[300px]"
260
+ style={{
261
+ background: 'radial-gradient(ellipse at center, rgba(99,102,241,0.05) 0%, transparent 70%)',
262
+ animation: 'glow-pulse 6s ease-in-out infinite',
263
+ }} />
264
+ </div>
265
+
266
+ <div className="relative max-w-[560px] w-full text-center mb-10"
267
+ style={{ animation: 'fade-in 0.5s cubic-bezier(0.16, 1, 0.3, 1)' }}>
268
+ {/* Sparkle */}
269
+ <div className="flex justify-center mb-5">
270
+ <div className="relative">
271
+ <svg width="32" height="32" viewBox="0 0 48 48" fill="none" className="text-accent-bright"
272
+ style={{ animation: 'sparkle-spin 8s linear infinite' }}>
273
+ <path d="M24 4L27.5 18.5L42 24L27.5 29.5L24 44L20.5 29.5L6 24L20.5 18.5L24 4Z"
274
+ fill="currentColor" opacity="0.8" />
275
+ </svg>
276
+ <div className="absolute inset-0 blur-lg bg-accent-bright/20" />
277
+ </div>
278
+ </div>
279
+
280
+ <h1 className="font-display text-[28px] md:text-[36px] font-800 leading-[1.1] tracking-[-0.04em] mb-3">
281
+ Hi{currentUser ? ', ' : ' '}<span className="text-accent-bright">{currentUser || 'there'}</span>
282
+ <br />
283
+ <span className="text-text-2">How can I help?</span>
284
+ </h1>
285
+ <p className="text-[13px] text-text-3 mt-2">
286
+ Pick a prompt or type your own below
287
+ </p>
288
+ </div>
289
+
290
+ <div className="relative grid grid-cols-2 md:grid-cols-4 gap-3 max-w-[640px] w-full mb-6">
291
+ {PROMPT_SUGGESTIONS.map((prompt, i) => (
292
+ <button
293
+ key={prompt.text}
294
+ onClick={() => handlePrompt(prompt.text)}
295
+ className={`suggestion-card p-4 rounded-[14px] border border-white/[0.04] bg-gradient-to-br ${prompt.gradient}
296
+ text-left cursor-pointer flex flex-col gap-3 min-h-[110px] active:scale-[0.97]`}
297
+ style={{ fontFamily: 'inherit', animation: `fade-in 0.4s cubic-bezier(0.16, 1, 0.3, 1) ${i * 0.07 + 0.15}s both` }}
298
+ >
299
+ <PromptIcon type={prompt.icon} />
300
+ <span className="text-[12px] text-text-2/80 leading-snug flex-1">{prompt.text}</span>
301
+ </button>
302
+ ))}
303
+ </div>
304
+ </div>
305
+ ) : (
306
+ <MessageList messages={messages} streaming={streamingForThisSession} />
307
+ )}
308
+
309
+ <SessionDebugPanel
310
+ messages={messages}
311
+ open={debugOpen}
312
+ onClose={() => setDebugOpen(false)}
313
+ />
314
+
315
+ <ChatInput
316
+ streaming={streamingForThisSession}
317
+ onSend={sendMessage}
318
+ onStop={stopStreaming}
319
+ />
320
+
321
+ <Dropdown open={menuOpen} onClose={() => setMenuOpen(false)}>
322
+ {session.agentId && agents[session.agentId] && (
323
+ <DropdownItem onClick={() => { setMenuOpen(false); setEditingAgentId(session.agentId!); setAgentSheetOpen(true) }}>
324
+ Edit Agent
325
+ </DropdownItem>
326
+ )}
327
+ <DropdownItem onClick={() => { setMenuOpen(false); setConfirmClear(true) }}>
328
+ Clear History
329
+ </DropdownItem>
330
+ {session.agentId && agents[session.agentId] && !isMainChat && (
331
+ <DropdownItem danger onClick={() => { setMenuOpen(false); setConfirmDeleteAgent(true) }}>
332
+ Delete Agent
333
+ </DropdownItem>
334
+ )}
335
+ {!isMainChat && (
336
+ <DropdownItem danger onClick={() => { setMenuOpen(false); setConfirmDelete(true) }}>
337
+ Delete Session
338
+ </DropdownItem>
339
+ )}
340
+ </Dropdown>
341
+
342
+ <ConfirmDialog
343
+ open={confirmClear}
344
+ title="Clear History"
345
+ message="This will delete all messages in this session. This cannot be undone."
346
+ confirmLabel="Clear"
347
+ danger
348
+ onConfirm={handleClear}
349
+ onCancel={() => setConfirmClear(false)}
350
+ />
351
+ <ConfirmDialog
352
+ open={confirmDelete}
353
+ title="Delete Session"
354
+ message={`Delete "${session.name}"? This cannot be undone.`}
355
+ confirmLabel="Delete"
356
+ danger
357
+ onConfirm={handleDelete}
358
+ onCancel={() => setConfirmDelete(false)}
359
+ />
360
+ {session.agentId && agents[session.agentId] && (
361
+ <ConfirmDialog
362
+ open={confirmDeleteAgent}
363
+ title="Delete Agent"
364
+ message={`Delete agent "${agents[session.agentId].name}"? This cannot be undone.`}
365
+ confirmLabel="Delete"
366
+ danger
367
+ onConfirm={async () => {
368
+ setConfirmDeleteAgent(false)
369
+ await deleteAgent(session.agentId!)
370
+ await loadAgents()
371
+ }}
372
+ onCancel={() => setConfirmDeleteAgent(false)}
373
+ />
374
+ )}
375
+
376
+ {isDragging && (
377
+ <div className="absolute inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm pointer-events-none">
378
+ <div className="px-8 py-6 rounded-[20px] border-2 border-dashed border-accent-bright/50 bg-surface/80 text-center">
379
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-accent-bright mx-auto mb-3">
380
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
381
+ <polyline points="17 8 12 3 7 8" />
382
+ <line x1="12" y1="3" x2="12" y2="15" />
383
+ </svg>
384
+ <p className="text-[15px] font-600 text-text">Drop file to attach</p>
385
+ </div>
386
+ </div>
387
+ )}
388
+ </div>
389
+ )
390
+ }
391
+
392
+ function PromptIcon({ type }: { type: string }) {
393
+ const cls = "w-5 h-5"
394
+ switch (type) {
395
+ case 'book':
396
+ return <svg className={cls} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" style={{ color: '#818CF8' }}><rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="7" rx="1" /><rect x="3" y="14" width="7" height="7" rx="1" /><rect x="14" y="14" width="7" height="7" rx="1" /></svg>
397
+ case 'link':
398
+ return <svg className={cls} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" style={{ color: '#F472B6' }}><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3" /><line x1="8" y1="12" x2="16" y2="12" /></svg>
399
+ case 'bot':
400
+ return <svg className={cls} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" style={{ color: '#34D399' }}><path d="M12 2a2 2 0 0 1 2 2c0 .74-.4 1.39-1 1.73V7h1a7 7 0 0 1 7 7v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-3a7 7 0 0 1 7-7h1V5.73c-.6-.34-1-.99-1-1.73a2 2 0 0 1 2-2z" /><circle cx="9" cy="13" r="1.25" fill="currentColor" /><circle cx="15" cy="13" r="1.25" fill="currentColor" /><path d="M10 17h4" /></svg>
401
+ case 'check':
402
+ return <svg className={cls} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" style={{ color: '#FBBF24' }}><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></svg>
403
+ default:
404
+ return null
405
+ }
406
+ }