@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,202 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useMemo, useState } from 'react'
4
+ import { useAppStore } from '@/stores/use-app-store'
5
+ import { useChatStore } from '@/stores/use-chat-store'
6
+ import { SessionCard } from './session-card'
7
+ import { fetchMessages } from '@/lib/sessions'
8
+
9
+ interface Props {
10
+ inSidebar?: boolean
11
+ onSelect?: () => void
12
+ }
13
+
14
+ type SessionFilter = 'all' | 'active' | 'human' | 'orchestrated'
15
+ type SortMode = 'lastActive' | 'name' | 'messages'
16
+
17
+ export function SessionList({ inSidebar, onSelect }: Props) {
18
+ const sessions = useAppStore((s) => s.sessions)
19
+ const currentUser = useAppStore((s) => s.currentUser)
20
+ const currentSessionId = useAppStore((s) => s.currentSessionId)
21
+ const setCurrentSession = useAppStore((s) => s.setCurrentSession)
22
+ const loadSessions = useAppStore((s) => s.loadSessions)
23
+ const loadConnectors = useAppStore((s) => s.loadConnectors)
24
+ const setNewSessionOpen = useAppStore((s) => s.setNewSessionOpen)
25
+ const clearSessions = useAppStore((s) => s.clearSessions)
26
+ const togglePinSession = useAppStore((s) => s.togglePinSession)
27
+ const setMessages = useChatStore((s) => s.setMessages)
28
+ const [search, setSearch] = useState('')
29
+ const [typeFilter, setTypeFilter] = useState<SessionFilter>('all')
30
+ const [sortMode, setSortMode] = useState<SortMode>('lastActive')
31
+
32
+ useEffect(() => {
33
+ void loadConnectors()
34
+ }, [loadConnectors])
35
+
36
+ const allUserSessions = useMemo(() => {
37
+ return Object.values(sessions).filter((s) => {
38
+ if (s.name === '__main__') return false
39
+ const owner = (s.user || '').toLowerCase()
40
+ const isPlatformOwned = owner === 'system' || owner === 'connector' || owner === 'swarm'
41
+ const isCurrentUserOwned = !!currentUser && owner === currentUser.toLowerCase()
42
+ const isUnownedLegacy = !owner
43
+ if (!isCurrentUserOwned && !isPlatformOwned && !isUnownedLegacy) return false
44
+ return true
45
+ })
46
+ }, [sessions, currentUser])
47
+
48
+ const filtered = useMemo(() => {
49
+ return allUserSessions
50
+ .filter((s) => {
51
+ if (search && !s.name.toLowerCase().includes(search.toLowerCase())) return false
52
+ if (typeFilter === 'active' && !s.active) return false
53
+ if (typeFilter === 'human' && s.sessionType === 'orchestrated') return false
54
+ if (typeFilter === 'orchestrated' && s.sessionType !== 'orchestrated') return false
55
+ return true
56
+ })
57
+ .sort((a, b) => {
58
+ // Pinned always first
59
+ if (a.pinned && !b.pinned) return -1
60
+ if (!a.pinned && b.pinned) return 1
61
+ // Then by sort mode
62
+ if (sortMode === 'name') return a.name.localeCompare(b.name)
63
+ if (sortMode === 'messages') return (b.messages?.length || 0) - (a.messages?.length || 0)
64
+ return (b.lastActiveAt || 0) - (a.lastActiveAt || 0)
65
+ })
66
+ }, [allUserSessions, search, typeFilter, sortMode])
67
+
68
+ const handleSelect = async (id: string) => {
69
+ setCurrentSession(id)
70
+ if (typeof window !== 'undefined') {
71
+ window.dispatchEvent(new CustomEvent('swarmclaw:scroll-bottom'))
72
+ }
73
+ try {
74
+ const msgs = await fetchMessages(id)
75
+ setMessages(msgs)
76
+ } catch {
77
+ setMessages(sessions[id]?.messages || [])
78
+ }
79
+ await loadSessions()
80
+ onSelect?.()
81
+ }
82
+
83
+ // Truly empty — no sessions at all for this user
84
+ if (!allUserSessions.length) {
85
+ return (
86
+ <div className="flex-1 flex flex-col items-center justify-center gap-4 text-text-3 p-8 text-center">
87
+ <div className="w-12 h-12 rounded-[14px] bg-accent-soft flex items-center justify-center mb-1">
88
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" className="text-accent-bright">
89
+ <path d="M12 2L14.5 9.5L22 12L14.5 14.5L12 22L9.5 14.5L2 12L9.5 9.5L12 2Z" fill="currentColor" />
90
+ </svg>
91
+ </div>
92
+ <p className="font-display text-[15px] font-600 text-text-2">No sessions yet</p>
93
+ <p className="text-[13px] text-text-3/50">Create one to start chatting</p>
94
+ {!inSidebar && (
95
+ <button
96
+ onClick={() => setNewSessionOpen(true)}
97
+ className="mt-3 px-8 py-3 rounded-[14px] border-none bg-[#6366F1] text-white
98
+ text-[14px] font-600 cursor-pointer active:scale-95 transition-all duration-200
99
+ shadow-[0_4px_16px_rgba(99,102,241,0.2)]"
100
+ style={{ fontFamily: 'inherit' }}
101
+ >
102
+ + New Session
103
+ </button>
104
+ )}
105
+ </div>
106
+ )
107
+ }
108
+
109
+ return (
110
+ <div className="flex-1 flex flex-col overflow-y-auto">
111
+ {/* Filter tabs — always visible when sessions exist */}
112
+ <div className="flex items-center gap-1 px-4 pt-2 pb-1 shrink-0">
113
+ {(['all', 'active', 'human', 'orchestrated'] as SessionFilter[]).map((f) => (
114
+ <button
115
+ key={f}
116
+ onClick={() => setTypeFilter(f)}
117
+ className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all
118
+ ${typeFilter === f ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
119
+ style={{ fontFamily: 'inherit' }}
120
+ >
121
+ {f === 'all' ? 'All' : f === 'active' ? 'Active' : f === 'human' ? 'Human' : 'AI'}
122
+ </button>
123
+ ))}
124
+ {filtered.length > 0 && (
125
+ <button
126
+ onClick={async () => {
127
+ if (!window.confirm(`Delete ${filtered.length} session${filtered.length === 1 ? '' : 's'}?`)) return
128
+ await clearSessions(filtered.map((s) => s.id))
129
+ }}
130
+ className="ml-auto p-1.5 rounded-[8px] text-text-3/70 hover:text-red-400 hover:bg-red-400/[0.06]
131
+ cursor-pointer transition-all bg-transparent border-none"
132
+ title="Clear all sessions"
133
+ >
134
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
135
+ <polyline points="3 6 5 6 21 6" /><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
136
+ <path d="M10 11v6" /><path d="M14 11v6" /><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
137
+ </svg>
138
+ </button>
139
+ )}
140
+ </div>
141
+
142
+ {/* Search — always visible */}
143
+ <div className="px-4 py-2 shrink-0 flex gap-2">
144
+ <input
145
+ type="text"
146
+ value={search}
147
+ onChange={(e) => setSearch(e.target.value)}
148
+ placeholder="Search..."
149
+ className="flex-1 px-4 py-2.5 rounded-[12px] border border-white/[0.04] bg-surface text-text
150
+ text-[13px] outline-none transition-all duration-200 placeholder:text-text-3/70 focus-glow"
151
+ style={{ fontFamily: 'inherit' }}
152
+ />
153
+ {/* Sort dropdown */}
154
+ <select
155
+ value={sortMode}
156
+ onChange={(e) => setSortMode(e.target.value as SortMode)}
157
+ aria-label="Sort sessions"
158
+ className="px-2 py-2 rounded-[12px] border border-white/[0.04] bg-surface text-text
159
+ text-[11px] outline-none cursor-pointer"
160
+ style={{ fontFamily: 'inherit' }}
161
+ >
162
+ <option value="lastActive">Recent</option>
163
+ <option value="name">Name</option>
164
+ <option value="messages">Messages</option>
165
+ </select>
166
+ </div>
167
+
168
+ {filtered.length > 0 ? (
169
+ <div className="flex flex-col gap-1 px-2 pb-4">
170
+ {filtered.map((s) => (
171
+ <div key={s.id} className="group/pin relative">
172
+ <SessionCard
173
+ session={s}
174
+ active={s.id === currentSessionId}
175
+ onClick={() => handleSelect(s.id)}
176
+ />
177
+ <button
178
+ onClick={(e) => { e.stopPropagation(); togglePinSession(s.id) }}
179
+ aria-label={s.pinned ? 'Unpin session' : 'Pin session'}
180
+ className={`absolute top-2 right-2 p-1 rounded-[6px] border-none cursor-pointer transition-all
181
+ ${s.pinned
182
+ ? 'text-amber-400 bg-amber-400/10 opacity-100'
183
+ : 'text-text-3/50 bg-transparent opacity-0 group-hover/pin:opacity-100 hover:text-text-2 hover:bg-white/[0.04]'}`}
184
+ >
185
+ <svg width="12" height="12" viewBox="0 0 24 24" fill={s.pinned ? 'currentColor' : 'none'} stroke="currentColor" strokeWidth="2" strokeLinecap="round">
186
+ <path d="M12 17v5" />
187
+ <path d="M9 2h6l-1 7h4l-8 8 2-8H8z" />
188
+ </svg>
189
+ </button>
190
+ </div>
191
+ ))}
192
+ </div>
193
+ ) : (
194
+ <div className="flex-1 flex flex-col items-center justify-center gap-3 text-text-3 p-8 text-center">
195
+ <p className="text-[13px] text-text-3/50">
196
+ No {typeFilter === 'orchestrated' ? 'AI' : typeFilter === 'active' ? 'active' : typeFilter} sessions{search ? ` matching "${search}"` : ''}
197
+ </p>
198
+ </div>
199
+ )}
200
+ </div>
201
+ )
202
+ }
@@ -0,0 +1,77 @@
1
+ 'use client'
2
+
3
+ import { useState, useEffect } from 'react'
4
+ import { api } from '@/lib/api-client'
5
+
6
+ interface Props {
7
+ aiPrompt: string
8
+ setAiPrompt: (v: string) => void
9
+ generating: boolean
10
+ generated: boolean
11
+ genError: string
12
+ onGenerate: () => void
13
+ appSettings?: Record<string, any>
14
+ placeholder: string
15
+ }
16
+
17
+ export function AiGenBlock({ aiPrompt, setAiPrompt, generating, generated, genError, onGenerate, placeholder }: Props) {
18
+ const [expanded, setExpanded] = useState(false)
19
+ const [genInfo, setGenInfo] = useState<{ provider: string; model: string } | null>(null)
20
+
21
+ useEffect(() => {
22
+ if (expanded && !genInfo) {
23
+ api<{ provider: string; model: string }>('GET', '/generate/info')
24
+ .then(setGenInfo)
25
+ .catch((err) => console.error('AI gen info fetch failed:', err))
26
+ }
27
+ }, [expanded])
28
+
29
+ return (
30
+ <div className="mb-10">
31
+ <button
32
+ onClick={() => setExpanded(!expanded)}
33
+ className="flex items-center gap-2.5 px-4 py-3 rounded-[14px] border border-[#6366F1]/15 bg-[#6366F1]/[0.03] hover:bg-[#6366F1]/[0.06] transition-all cursor-pointer w-full text-left"
34
+ style={{ fontFamily: 'inherit' }}
35
+ >
36
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" className="text-accent-bright shrink-0">
37
+ <path d="M12 2L14.5 9.5L22 12L14.5 14.5L12 22L9.5 14.5L2 12L9.5 9.5L12 2Z" fill="currentColor" />
38
+ </svg>
39
+ <span className="font-display text-[13px] font-600 text-accent-bright flex-1">Generate with AI</span>
40
+ <svg
41
+ width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"
42
+ className={`text-accent-bright/50 transition-transform duration-200 ${expanded ? 'rotate-180' : ''}`}
43
+ >
44
+ <polyline points="6 9 12 15 18 9" />
45
+ </svg>
46
+ </button>
47
+
48
+ {expanded && (
49
+ <div className="mt-3 p-5 rounded-[18px] border border-[#6366F1]/15 bg-[#6366F1]/[0.03]"
50
+ style={{ animation: 'fade-in 0.2s ease' }}>
51
+ <textarea
52
+ value={aiPrompt}
53
+ onChange={(e) => setAiPrompt(e.target.value)}
54
+ placeholder={placeholder}
55
+ rows={2}
56
+ className="w-full px-4 py-3 rounded-[12px] border border-[#6366F1]/10 bg-[#6366F1]/[0.02] text-text text-[14px] outline-none transition-all duration-200 placeholder:text-text-3/70 focus:border-[#6366F1]/30 resize-none"
57
+ style={{ fontFamily: 'inherit' }}
58
+ autoFocus
59
+ />
60
+ <button
61
+ onClick={onGenerate}
62
+ disabled={generating || !aiPrompt.trim()}
63
+ className="mt-3 px-5 py-2.5 rounded-[12px] border-none bg-[#6366F1] text-white text-[13px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110 active:scale-[0.97] shadow-[0_2px_12px_rgba(99,102,241,0.2)]"
64
+ style={{ fontFamily: 'inherit' }}
65
+ >
66
+ {generating ? 'Generating...' : generated ? 'Regenerate' : 'Generate'}
67
+ </button>
68
+ {generated && <span className="ml-3 text-[12px] text-emerald-400/70">Fields populated — edit below</span>}
69
+ {genError && <p className="mt-2 text-[12px] text-red-400/80">{genError}</p>}
70
+ <p className="mt-3 text-[11px] text-text-3/50">
71
+ Using {genInfo ? `${genInfo.model} via ${genInfo.provider}` : 'auto-detected provider'}
72
+ </p>
73
+ </div>
74
+ )}
75
+ </div>
76
+ )
77
+ }
@@ -0,0 +1,48 @@
1
+ 'use client'
2
+
3
+ interface Props {
4
+ user: string
5
+ size?: 'xs' | 'sm' | 'md' | 'lg'
6
+ }
7
+
8
+ const sizes = {
9
+ xs: 'w-6 h-6 text-[9px] rounded-[7px]',
10
+ sm: 'w-7 h-7 text-[10px] rounded-[8px]',
11
+ md: 'w-9 h-9 text-[13px] rounded-[10px]',
12
+ lg: 'w-[72px] h-[72px] text-[24px] rounded-[22px]',
13
+ }
14
+
15
+ /** Generate a consistent gradient from a username */
16
+ function userGradient(name: string): string {
17
+ let hash = 0
18
+ for (let i = 0; i < name.length; i++) {
19
+ hash = name.charCodeAt(i) + ((hash << 5) - hash)
20
+ }
21
+ const hue = Math.abs(hash) % 360
22
+ return `linear-gradient(135deg, hsl(${hue}, 70%, 35%), hsl(${(hue + 30) % 360}, 75%, 50%))`
23
+ }
24
+
25
+ export function Avatar({ user, size = 'md' }: Props) {
26
+ const initial = (user || '?')[0].toUpperCase()
27
+ return (
28
+ <div
29
+ className={`${sizes[size]} flex items-center justify-center font-display font-600 tracking-tight shrink-0 text-white`}
30
+ style={{ background: userGradient(user) }}
31
+ >
32
+ {initial}
33
+ </div>
34
+ )
35
+ }
36
+
37
+ export function AiAvatar({ size = 'md' }: { size?: 'sm' | 'md' }) {
38
+ const s = size === 'sm' ? 'w-6 h-6' : 'w-8 h-8'
39
+ const iconSize = size === 'sm' ? 12 : 16
40
+ return (
41
+ <div className={`${s} rounded-[8px] bg-accent-soft flex items-center justify-center shrink-0`}>
42
+ <svg width={iconSize} height={iconSize} viewBox="0 0 24 24" fill="none" className="text-accent-bright">
43
+ <path d="M12 2L14.5 9.5L22 12L14.5 14.5L12 22L9.5 14.5L2 12L9.5 9.5L12 2Z"
44
+ fill="currentColor" />
45
+ </svg>
46
+ </div>
47
+ )
48
+ }
@@ -0,0 +1,30 @@
1
+ 'use client'
2
+
3
+ import type { ReactNode } from 'react'
4
+
5
+ interface Props {
6
+ open: boolean
7
+ onClose: () => void
8
+ children: ReactNode
9
+ wide?: boolean
10
+ }
11
+
12
+ export function BottomSheet({ open, onClose, children, wide }: Props) {
13
+ if (!open) return null
14
+
15
+ return (
16
+ <div className="fixed inset-0 z-100 flex items-center justify-center p-6">
17
+ <div className="absolute inset-0 bg-black/70 backdrop-blur-sm" onClick={onClose} />
18
+ <div
19
+ className={`relative bg-raised w-full ${wide ? 'max-w-[640px]' : 'max-w-[520px]'} max-h-[85vh] flex flex-col
20
+ rounded-[24px] border border-white/[0.06]
21
+ shadow-[0_24px_80px_rgba(0,0,0,0.6),0_0_1px_rgba(255,255,255,0.05)]`}
22
+ style={{ animation: 'modal-in 0.3s cubic-bezier(0.16, 1, 0.3, 1)' }}
23
+ >
24
+ <div className="flex-1 overflow-y-auto px-8 pt-8 pb-8">
25
+ {children}
26
+ </div>
27
+ </div>
28
+ </div>
29
+ )
30
+ }
@@ -0,0 +1,47 @@
1
+ 'use client'
2
+
3
+ interface Props {
4
+ open: boolean
5
+ title: string
6
+ message: string
7
+ confirmLabel?: string
8
+ danger?: boolean
9
+ onConfirm: () => void
10
+ onCancel: () => void
11
+ }
12
+
13
+ export function ConfirmDialog({ open, title, message, confirmLabel = 'Confirm', danger, onConfirm, onCancel }: Props) {
14
+ if (!open) return null
15
+
16
+ return (
17
+ <div className="fixed inset-0 z-100 flex items-center justify-center p-6">
18
+ <div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onCancel} />
19
+ <div className="relative glass rounded-[20px] p-6 w-full max-w-[380px]
20
+ shadow-[0_24px_80px_rgba(0,0,0,0.5)]"
21
+ style={{ animation: 'fade-in 0.2s cubic-bezier(0.16, 1, 0.3, 1)' }}>
22
+ <h3 className="font-display text-[18px] font-700 tracking-[-0.02em] mb-2">{title}</h3>
23
+ <p className="text-[13px] text-text-2 mb-6 leading-relaxed">{message}</p>
24
+ <div className="flex gap-2.5">
25
+ <button
26
+ onClick={onCancel}
27
+ className="flex-1 py-2.5 rounded-[12px] border border-white/[0.06] bg-transparent text-text-2 text-[13px] font-600 cursor-pointer
28
+ hover:bg-surface transition-all duration-200"
29
+ style={{ fontFamily: 'inherit' }}
30
+ >
31
+ Cancel
32
+ </button>
33
+ <button
34
+ onClick={onConfirm}
35
+ className={`flex-1 py-2.5 rounded-[12px] border-none text-[13px] font-600 cursor-pointer active:scale-[0.97] transition-all duration-200
36
+ ${danger
37
+ ? 'bg-danger text-white shadow-[0_4px_20px_rgba(244,63,94,0.2)]'
38
+ : 'bg-[#6366F1] text-white shadow-[0_4px_20px_rgba(99,102,241,0.2)]'}`}
39
+ style={{ fontFamily: 'inherit' }}
40
+ >
41
+ {confirmLabel}
42
+ </button>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ )
47
+ }
@@ -0,0 +1,113 @@
1
+ import type { Connector, ConnectorPlatform, Session } from '@/types'
2
+ import { cn } from '@/lib/utils'
3
+
4
+ export const CONNECTOR_PLATFORM_META: Record<ConnectorPlatform, { label: string; color: string }> = {
5
+ discord: { label: 'Discord', color: '#5865F2' },
6
+ telegram: { label: 'Telegram', color: '#229ED9' },
7
+ slack: { label: 'Slack', color: '#4A154B' },
8
+ whatsapp: { label: 'WhatsApp', color: '#25D366' },
9
+ openclaw: { label: 'OpenClaw', color: '#F97316' },
10
+ signal: { label: 'Signal', color: '#3A76F0' },
11
+ teams: { label: 'Teams', color: '#6264A7' },
12
+ googlechat: { label: 'Google Chat', color: '#00AC47' },
13
+ matrix: { label: 'Matrix', color: '#0DBD8B' },
14
+ }
15
+
16
+ export function getConnectorPlatformLabel(platform: ConnectorPlatform): string {
17
+ return CONNECTOR_PLATFORM_META[platform]?.label || platform
18
+ }
19
+
20
+ export function getConnectorIdFromSessionName(sessionName?: string | null): string | null {
21
+ if (!sessionName || !sessionName.startsWith('connector:')) return null
22
+ const parts = sessionName.split(':')
23
+ return parts.length >= 2 && parts[1] ? parts[1] : null
24
+ }
25
+
26
+ export function getSessionConnector(
27
+ session: Pick<Session, 'name'>,
28
+ connectors: Record<string, Connector>,
29
+ ): Connector | null {
30
+ const connectorId = getConnectorIdFromSessionName(session.name)
31
+ if (!connectorId) return null
32
+ return connectors[connectorId] || null
33
+ }
34
+
35
+ interface ConnectorPlatformIconProps {
36
+ platform: ConnectorPlatform
37
+ size?: number
38
+ className?: string
39
+ }
40
+
41
+ export function ConnectorPlatformIcon({
42
+ platform,
43
+ size = 14,
44
+ className,
45
+ }: ConnectorPlatformIconProps) {
46
+ switch (platform) {
47
+ case 'discord':
48
+ return (
49
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" className={className}>
50
+ <path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z" />
51
+ </svg>
52
+ )
53
+ case 'telegram':
54
+ return (
55
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" className={className}>
56
+ <path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z" />
57
+ </svg>
58
+ )
59
+ case 'slack':
60
+ return (
61
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" className={className}>
62
+ <path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z" />
63
+ </svg>
64
+ )
65
+ case 'whatsapp':
66
+ return (
67
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" className={className}>
68
+ <path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z" />
69
+ </svg>
70
+ )
71
+ case 'openclaw':
72
+ return (
73
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
74
+ <path d="M4 17l2-5 2 5" /><path d="M12 17l2-5 2 5" /><path d="M20 17l-2-5-2 5" />
75
+ <path d="M2 7l4-4 3 3" /><path d="M22 7l-4-4-3 3" />
76
+ <line x1="12" y1="3" x2="12" y2="8" />
77
+ </svg>
78
+ )
79
+ default:
80
+ return null
81
+ }
82
+ }
83
+
84
+ interface ConnectorPlatformBadgeProps {
85
+ platform: ConnectorPlatform
86
+ size?: number
87
+ iconSize?: number
88
+ className?: string
89
+ roundedClassName?: string
90
+ title?: string
91
+ }
92
+
93
+ export function ConnectorPlatformBadge({
94
+ platform,
95
+ size = 36,
96
+ iconSize,
97
+ className,
98
+ roundedClassName = 'rounded-[10px]',
99
+ title,
100
+ }: ConnectorPlatformBadgeProps) {
101
+ const meta = CONNECTOR_PLATFORM_META[platform]
102
+ const glyphSize = iconSize ?? Math.max(12, Math.floor(size * 0.52))
103
+
104
+ return (
105
+ <span
106
+ title={title || `${meta.label} connector`}
107
+ className={cn('inline-flex items-center justify-center shrink-0', roundedClassName, className)}
108
+ style={{ width: size, height: size, backgroundColor: meta.color }}
109
+ >
110
+ <ConnectorPlatformIcon platform={platform} size={glyphSize} className="text-white" />
111
+ </span>
112
+ )
113
+ }