@swarmclawai/swarmclaw 0.5.3 → 0.6.2

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 (224) hide show
  1. package/README.md +53 -9
  2. package/bin/server-cmd.js +1 -0
  3. package/bin/swarmclaw.js +76 -16
  4. package/next.config.ts +11 -1
  5. package/package.json +5 -2
  6. package/scripts/postinstall.mjs +18 -0
  7. package/src/app/api/canvas/[sessionId]/route.ts +31 -0
  8. package/src/app/api/chatrooms/[id]/chat/route.ts +284 -0
  9. package/src/app/api/chatrooms/[id]/members/route.ts +82 -0
  10. package/src/app/api/chatrooms/[id]/pins/route.ts +39 -0
  11. package/src/app/api/chatrooms/[id]/reactions/route.ts +42 -0
  12. package/src/app/api/chatrooms/[id]/route.ts +84 -0
  13. package/src/app/api/chatrooms/route.ts +50 -0
  14. package/src/app/api/connectors/[id]/route.ts +1 -0
  15. package/src/app/api/connectors/route.ts +2 -1
  16. package/src/app/api/credentials/route.ts +2 -3
  17. package/src/app/api/files/open/route.ts +43 -0
  18. package/src/app/api/knowledge/[id]/route.ts +13 -2
  19. package/src/app/api/knowledge/route.ts +8 -1
  20. package/src/app/api/memory/route.ts +8 -0
  21. package/src/app/api/notifications/route.ts +4 -0
  22. package/src/app/api/orchestrator/run/route.ts +1 -1
  23. package/src/app/api/plugins/install/route.ts +2 -2
  24. package/src/app/api/search/route.ts +53 -1
  25. package/src/app/api/sessions/[id]/chat/route.ts +2 -0
  26. package/src/app/api/sessions/[id]/edit-resend/route.ts +1 -1
  27. package/src/app/api/sessions/[id]/fork/route.ts +1 -1
  28. package/src/app/api/sessions/[id]/messages/route.ts +70 -2
  29. package/src/app/api/sessions/[id]/route.ts +4 -0
  30. package/src/app/api/sessions/route.ts +3 -3
  31. package/src/app/api/settings/route.ts +9 -0
  32. package/src/app/api/setup/check-provider/route.ts +3 -16
  33. package/src/app/api/skills/[id]/route.ts +6 -0
  34. package/src/app/api/skills/route.ts +6 -0
  35. package/src/app/api/tasks/[id]/route.ts +12 -0
  36. package/src/app/api/tasks/bulk/route.ts +100 -0
  37. package/src/app/api/tasks/metrics/route.ts +101 -0
  38. package/src/app/api/tasks/route.ts +18 -2
  39. package/src/app/api/tts/route.ts +3 -2
  40. package/src/app/api/tts/stream/route.ts +3 -2
  41. package/src/app/api/uploads/[filename]/route.ts +19 -34
  42. package/src/app/api/uploads/route.ts +94 -0
  43. package/src/app/api/webhooks/[id]/route.ts +15 -1
  44. package/src/app/globals.css +63 -15
  45. package/src/app/page.tsx +142 -13
  46. package/src/cli/index.js +40 -1
  47. package/src/cli/index.test.js +30 -0
  48. package/src/cli/spec.js +42 -0
  49. package/src/components/agents/agent-avatar.tsx +57 -10
  50. package/src/components/agents/agent-card.tsx +50 -17
  51. package/src/components/agents/agent-chat-list.tsx +148 -12
  52. package/src/components/agents/agent-list.tsx +50 -19
  53. package/src/components/agents/agent-sheet.tsx +120 -65
  54. package/src/components/agents/inspector-panel.tsx +81 -6
  55. package/src/components/agents/openclaw-skills-panel.tsx +32 -3
  56. package/src/components/agents/personality-builder.tsx +42 -14
  57. package/src/components/agents/soul-library-picker.tsx +89 -0
  58. package/src/components/auth/access-key-gate.tsx +10 -3
  59. package/src/components/auth/setup-wizard.tsx +2 -2
  60. package/src/components/auth/user-picker.tsx +31 -3
  61. package/src/components/canvas/canvas-panel.tsx +96 -0
  62. package/src/components/chat/activity-moment.tsx +173 -0
  63. package/src/components/chat/chat-area.tsx +46 -22
  64. package/src/components/chat/chat-header.tsx +457 -286
  65. package/src/components/chat/chat-preview-panel.tsx +1 -2
  66. package/src/components/chat/chat-tool-toggles.tsx +1 -1
  67. package/src/components/chat/delegation-banner.tsx +371 -0
  68. package/src/components/chat/file-path-chip.tsx +146 -0
  69. package/src/components/chat/heartbeat-history-panel.tsx +269 -0
  70. package/src/components/chat/markdown-utils.ts +9 -0
  71. package/src/components/chat/message-bubble.tsx +356 -315
  72. package/src/components/chat/message-list.tsx +230 -8
  73. package/src/components/chat/streaming-bubble.tsx +104 -47
  74. package/src/components/chat/suggestions-bar.tsx +1 -1
  75. package/src/components/chat/thinking-indicator.tsx +72 -10
  76. package/src/components/chat/tool-call-bubble.tsx +111 -73
  77. package/src/components/chat/tool-request-banner.tsx +31 -7
  78. package/src/components/chat/transfer-agent-picker.tsx +63 -0
  79. package/src/components/chatrooms/agent-hover-card.tsx +124 -0
  80. package/src/components/chatrooms/chatroom-input.tsx +320 -0
  81. package/src/components/chatrooms/chatroom-list.tsx +130 -0
  82. package/src/components/chatrooms/chatroom-message.tsx +432 -0
  83. package/src/components/chatrooms/chatroom-sheet.tsx +215 -0
  84. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +134 -0
  85. package/src/components/chatrooms/chatroom-typing-bar.tsx +88 -0
  86. package/src/components/chatrooms/chatroom-view.tsx +344 -0
  87. package/src/components/chatrooms/reaction-picker.tsx +273 -0
  88. package/src/components/connectors/connector-list.tsx +168 -90
  89. package/src/components/connectors/connector-sheet.tsx +95 -56
  90. package/src/components/home/home-view.tsx +501 -0
  91. package/src/components/input/chat-input.tsx +107 -43
  92. package/src/components/knowledge/knowledge-list.tsx +31 -1
  93. package/src/components/knowledge/knowledge-sheet.tsx +83 -2
  94. package/src/components/layout/app-layout.tsx +194 -97
  95. package/src/components/layout/update-banner.tsx +2 -2
  96. package/src/components/logs/log-list.tsx +2 -2
  97. package/src/components/mcp-servers/mcp-server-sheet.tsx +1 -1
  98. package/src/components/memory/memory-agent-list.tsx +143 -0
  99. package/src/components/memory/memory-browser.tsx +205 -0
  100. package/src/components/memory/memory-card.tsx +34 -7
  101. package/src/components/memory/memory-detail.tsx +359 -120
  102. package/src/components/memory/memory-sheet.tsx +157 -23
  103. package/src/components/plugins/plugin-list.tsx +1 -1
  104. package/src/components/plugins/plugin-sheet.tsx +1 -1
  105. package/src/components/projects/project-detail.tsx +509 -0
  106. package/src/components/projects/project-list.tsx +195 -59
  107. package/src/components/providers/provider-list.tsx +2 -2
  108. package/src/components/providers/provider-sheet.tsx +3 -3
  109. package/src/components/schedules/schedule-card.tsx +1 -1
  110. package/src/components/schedules/schedule-list.tsx +1 -1
  111. package/src/components/schedules/schedule-sheet.tsx +259 -126
  112. package/src/components/secrets/secret-sheet.tsx +47 -24
  113. package/src/components/secrets/secrets-list.tsx +18 -8
  114. package/src/components/sessions/new-session-sheet.tsx +33 -65
  115. package/src/components/sessions/session-card.tsx +45 -14
  116. package/src/components/sessions/session-list.tsx +35 -18
  117. package/src/components/settings/gateway-disconnect-overlay.tsx +80 -0
  118. package/src/components/shared/agent-picker-list.tsx +90 -0
  119. package/src/components/shared/agent-switch-dialog.tsx +156 -0
  120. package/src/components/shared/attachment-chip.tsx +165 -0
  121. package/src/components/shared/avatar.tsx +10 -1
  122. package/src/components/shared/chatroom-picker-list.tsx +61 -0
  123. package/src/components/shared/check-icon.tsx +12 -0
  124. package/src/components/shared/confirm-dialog.tsx +1 -1
  125. package/src/components/shared/connector-platform-icon.tsx +51 -4
  126. package/src/components/shared/empty-state.tsx +32 -0
  127. package/src/components/shared/file-preview.tsx +34 -0
  128. package/src/components/shared/form-styles.ts +2 -0
  129. package/src/components/shared/icon-button.tsx +16 -2
  130. package/src/components/shared/keyboard-shortcuts-dialog.tsx +116 -0
  131. package/src/components/shared/notification-center.tsx +44 -6
  132. package/src/components/shared/profile-sheet.tsx +115 -0
  133. package/src/components/shared/reply-quote.tsx +26 -0
  134. package/src/components/shared/search-dialog.tsx +31 -15
  135. package/src/components/shared/section-label.tsx +12 -0
  136. package/src/components/shared/settings/plugin-manager.tsx +1 -1
  137. package/src/components/shared/settings/section-embedding.tsx +48 -13
  138. package/src/components/shared/settings/section-orchestrator.tsx +46 -15
  139. package/src/components/shared/settings/section-providers.tsx +1 -1
  140. package/src/components/shared/settings/section-secrets.tsx +1 -1
  141. package/src/components/shared/settings/section-storage.tsx +206 -0
  142. package/src/components/shared/settings/section-theme.tsx +95 -0
  143. package/src/components/shared/settings/section-user-preferences.tsx +57 -0
  144. package/src/components/shared/settings/section-voice.tsx +42 -21
  145. package/src/components/shared/settings/section-web-search.tsx +30 -6
  146. package/src/components/shared/settings/settings-page.tsx +182 -27
  147. package/src/components/shared/settings/settings-sheet.tsx +9 -73
  148. package/src/components/shared/settings/storage-browser.tsx +259 -0
  149. package/src/components/shared/sheet-footer.tsx +33 -0
  150. package/src/components/skills/skill-list.tsx +61 -30
  151. package/src/components/skills/skill-sheet.tsx +81 -2
  152. package/src/components/tasks/task-board.tsx +448 -26
  153. package/src/components/tasks/task-card.tsx +59 -9
  154. package/src/components/tasks/task-column.tsx +62 -3
  155. package/src/components/tasks/task-list.tsx +12 -4
  156. package/src/components/tasks/task-sheet.tsx +416 -74
  157. package/src/components/ui/hover-card.tsx +52 -0
  158. package/src/components/usage/metrics-dashboard.tsx +90 -6
  159. package/src/components/usage/usage-list.tsx +1 -1
  160. package/src/components/webhooks/webhook-sheet.tsx +1 -1
  161. package/src/hooks/use-continuous-speech.ts +10 -4
  162. package/src/hooks/use-view-router.ts +69 -19
  163. package/src/hooks/use-voice-conversation.ts +53 -10
  164. package/src/hooks/use-ws.ts +4 -2
  165. package/src/instrumentation.ts +15 -1
  166. package/src/lib/chat.ts +2 -0
  167. package/src/lib/memory.ts +3 -0
  168. package/src/lib/providers/anthropic.ts +13 -7
  169. package/src/lib/providers/index.ts +1 -0
  170. package/src/lib/providers/openai.ts +13 -7
  171. package/src/lib/server/chat-execution.ts +75 -15
  172. package/src/lib/server/chatroom-helpers.ts +146 -0
  173. package/src/lib/server/connectors/manager.ts +229 -7
  174. package/src/lib/server/context-manager.ts +225 -13
  175. package/src/lib/server/create-notification.ts +14 -2
  176. package/src/lib/server/daemon-state.ts +157 -10
  177. package/src/lib/server/execution-log.ts +1 -0
  178. package/src/lib/server/heartbeat-service.ts +48 -6
  179. package/src/lib/server/heartbeat-wake.ts +110 -0
  180. package/src/lib/server/langgraph-checkpoint.ts +1 -0
  181. package/src/lib/server/main-agent-loop.ts +1 -1
  182. package/src/lib/server/memory-consolidation.ts +105 -0
  183. package/src/lib/server/memory-db.ts +183 -10
  184. package/src/lib/server/mime.ts +51 -0
  185. package/src/lib/server/openclaw-gateway.ts +9 -1
  186. package/src/lib/server/orchestrator-lg.ts +2 -0
  187. package/src/lib/server/orchestrator.ts +5 -2
  188. package/src/lib/server/playwright-proxy.mjs +2 -3
  189. package/src/lib/server/prompt-runtime-context.ts +53 -0
  190. package/src/lib/server/provider-health.ts +125 -0
  191. package/src/lib/server/queue.ts +56 -10
  192. package/src/lib/server/scheduler.ts +8 -0
  193. package/src/lib/server/session-run-manager.ts +4 -0
  194. package/src/lib/server/session-tools/canvas.ts +67 -0
  195. package/src/lib/server/session-tools/chatroom.ts +136 -0
  196. package/src/lib/server/session-tools/connector.ts +83 -9
  197. package/src/lib/server/session-tools/context-mgmt.ts +36 -18
  198. package/src/lib/server/session-tools/crud.ts +21 -0
  199. package/src/lib/server/session-tools/delegate.ts +68 -4
  200. package/src/lib/server/session-tools/git.ts +71 -0
  201. package/src/lib/server/session-tools/http.ts +57 -0
  202. package/src/lib/server/session-tools/index.ts +10 -0
  203. package/src/lib/server/session-tools/memory.ts +7 -1
  204. package/src/lib/server/session-tools/search-providers.ts +16 -8
  205. package/src/lib/server/session-tools/subagent.ts +106 -0
  206. package/src/lib/server/session-tools/web.ts +115 -4
  207. package/src/lib/server/storage.ts +53 -29
  208. package/src/lib/server/stream-agent-chat.ts +185 -57
  209. package/src/lib/server/system-events.ts +49 -0
  210. package/src/lib/server/task-mention.ts +41 -0
  211. package/src/lib/server/ws-hub.ts +11 -0
  212. package/src/lib/sessions.ts +10 -0
  213. package/src/lib/soul-library.ts +103 -0
  214. package/src/lib/soul-suggestions.ts +109 -0
  215. package/src/lib/task-dedupe.ts +26 -0
  216. package/src/lib/tasks.ts +4 -1
  217. package/src/lib/tool-definitions.ts +2 -0
  218. package/src/lib/tts.ts +2 -2
  219. package/src/lib/view-routes.ts +36 -1
  220. package/src/lib/ws-client.ts +14 -4
  221. package/src/stores/use-app-store.ts +41 -3
  222. package/src/stores/use-chat-store.ts +113 -5
  223. package/src/stores/use-chatroom-store.ts +276 -0
  224. package/src/types/index.ts +88 -4
@@ -0,0 +1,273 @@
1
+ 'use client'
2
+
3
+ import { useState, useEffect, useRef, useMemo } from 'react'
4
+
5
+ const CATEGORIES: Array<{ id: string; label: string; icon: string; emojis: string[] }> = [
6
+ {
7
+ id: 'frequent',
8
+ label: 'Frequently Used',
9
+ icon: '🕐',
10
+ emojis: ['👍', '❤️', '😂', '🔥', '🎉', '👀', '🚀', '✅', '💯', '🤔'],
11
+ },
12
+ {
13
+ id: 'smileys',
14
+ label: 'Smileys & People',
15
+ icon: '😀',
16
+ emojis: [
17
+ '😀', '😃', '😄', '😁', '😆', '😅', '🤣', '😂', '🙂', '😊',
18
+ '😇', '🥰', '😍', '🤩', '😘', '😗', '😚', '😙', '🥲', '😋',
19
+ '😛', '😜', '🤪', '😝', '🤑', '🤗', '🤭', '🫢', '🤫', '🤔',
20
+ '🫡', '🤐', '🤨', '😐', '😑', '😶', '🫥', '😏', '😒', '🙄',
21
+ '😬', '🤥', '🫨', '😌', '😔', '😪', '🤤', '😴', '😷', '🤒',
22
+ '🤕', '🤢', '🤮', '🥴', '😵', '🤯', '🥳', '🥸', '😎', '🤓',
23
+ '🧐', '😕', '🫤', '😟', '🙁', '😮', '😯', '😲', '😳', '🥺',
24
+ '🥹', '😦', '😧', '😨', '😰', '😥', '😢', '😭', '😱', '😖',
25
+ '😣', '😞', '😓', '😩', '😫', '🥱', '😤', '😡', '😠', '🤬',
26
+ '😈', '👿', '💀', '☠️', '💩', '🤡', '👹', '👺', '👻', '👽',
27
+ '🤖', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾',
28
+ '🙈', '🙉', '🙊', '👋', '🤚', '🖐️', '✋', '🖖', '🫱', '🫲',
29
+ '🫳', '🫴', '👌', '🤌', '🤏', '✌️', '🤞', '🫰', '🤟', '🤘',
30
+ '🤙', '👈', '👉', '👆', '🖕', '👇', '☝️', '🫵', '👍', '👎',
31
+ '✊', '👊', '🤛', '🤜', '👏', '🙌', '🫶', '👐', '🤲', '🤝',
32
+ '🙏', '✍️', '💪', '🦾', '🧠', '👀', '👁️', '👅', '👄', '🫦',
33
+ ],
34
+ },
35
+ {
36
+ id: 'nature',
37
+ label: 'Animals & Nature',
38
+ icon: '🐶',
39
+ emojis: [
40
+ '🐶', '🐱', '🐭', '🐹', '🐰', '🦊', '🐻', '🐼', '🐻‍❄️', '🐨',
41
+ '🐯', '🦁', '🐮', '🐷', '🐸', '🐵', '🐔', '🐧', '🐦', '🐤',
42
+ '🦆', '🦅', '🦉', '🦇', '🐺', '🐗', '🐴', '🦄', '🐝', '🪱',
43
+ '🐛', '🦋', '🐌', '🐞', '🐜', '🪲', '🪳', '🕷️', '🦂', '🐢',
44
+ '🐍', '🦎', '🐙', '🦑', '🦐', '🦞', '🦀', '🐡', '🐠', '🐟',
45
+ '🐬', '🐳', '🐋', '🦈', '🪸', '🐊', '🐅', '🐆', '🦓', '🦍',
46
+ '🐘', '🦛', '🦏', '🐪', '🐫', '🦒', '🦘', '🦬', '🐃', '🐂',
47
+ '🐄', '🐎', '🐖', '🐏', '🐑', '🦙', '🐐', '🦌', '🐕', '🐩',
48
+ '🌵', '🎄', '🌲', '🌳', '🌴', '🪵', '🌱', '🌿', '☘️', '🍀',
49
+ '🍁', '🍂', '🍃', '🪹', '🪺', '🌺', '🌻', '🌹', '🥀', '🌷',
50
+ '🌼', '🌸', '💐', '🍄', '🌰', '🎃', '🌍', '🌙', '⭐', '🌟',
51
+ '💫', '✨', '⚡', '☀️', '🌤️', '🌈', '☁️', '🌧️', '❄️', '🔥',
52
+ ],
53
+ },
54
+ {
55
+ id: 'food',
56
+ label: 'Food & Drink',
57
+ icon: '🍕',
58
+ emojis: [
59
+ '🍎', '🍐', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🫐', '🍈',
60
+ '🍒', '🍑', '🥭', '🍍', '🥥', '🥝', '🍅', '🥑', '🍆', '🥦',
61
+ '🥬', '🥒', '🌶️', '🫑', '🌽', '🥕', '🧄', '🧅', '🥔', '🍠',
62
+ '🥐', '🍞', '🥖', '🥨', '🧀', '🥚', '🍳', '🥞', '🧇', '🥓',
63
+ '🥩', '🍗', '🍖', '🌭', '🍔', '🍟', '🍕', '🫓', '🥪', '🥙',
64
+ '🧆', '🌮', '🌯', '🫔', '🥗', '🍝', '🍜', '🍲', '🍛', '🍣',
65
+ '🍱', '🥟', '🍤', '🍙', '🍚', '🍘', '🍥', '🥠', '🥮', '🍡',
66
+ '🍧', '🍨', '🍦', '🥧', '🧁', '🍰', '🎂', '🍮', '🍭', '🍬',
67
+ '🍫', '🍿', '🧈', '🥤', '☕', '🍵', '🧃', '🧉', '🍶', '🍺',
68
+ '🍻', '🥂', '🍷', '🍸', '🍹', '🍾', '🧊', '🥄', '🍴', '🥢',
69
+ ],
70
+ },
71
+ {
72
+ id: 'activity',
73
+ label: 'Activities',
74
+ icon: '⚽',
75
+ emojis: [
76
+ '⚽', '🏀', '🏈', '⚾', '🥎', '🎾', '🏐', '🏉', '🥏', '🎱',
77
+ '🏓', '🏸', '🏒', '🥊', '🥋', '🥅', '⛳', '⛸️', '🎣', '🤿',
78
+ '🎿', '🛷', '🥌', '🎯', '🪀', '🪁', '🎮', '🕹️', '🎰', '🧩',
79
+ '♟️', '🎲', '🎭', '🎨', '🎬', '🎤', '🎧', '🎼', '🎹', '🥁',
80
+ '🎷', '🎺', '🪗', '🎸', '🎻', '🎪', '🎫', '🎟️', '🏆', '🥇',
81
+ '🥈', '🥉', '🏅', '🎖️', '🏵️', '🎗️', '🎁', '🎀', '🎈', '🎊',
82
+ ],
83
+ },
84
+ {
85
+ id: 'travel',
86
+ label: 'Travel & Places',
87
+ icon: '✈️',
88
+ emojis: [
89
+ '🚗', '🚕', '🚙', '🚌', '🚎', '🏎️', '🚓', '🚑', '🚒', '🚐',
90
+ '🛻', '🚚', '🚛', '🚜', '🏍️', '🛵', '🚲', '🛴', '🛺', '🚔',
91
+ '🚍', '🚘', '🚖', '✈️', '🚀', '🛸', '🚁', '🛶', '⛵', '🚢',
92
+ '🏠', '🏡', '🏘️', '🏢', '🏣', '🏥', '🏦', '🏪', '🏫', '🏩',
93
+ '💒', '🏛️', '⛪', '🕌', '🛕', '🕍', '⛩️', '🏰', '🏯', '🗼',
94
+ '🗽', '🗿', '🏟️', '🎡', '🎢', '🎠', '⛲', '⛱️', '🏖️', '🏝️',
95
+ '🏔️', '🗻', '🌋', '🏕️', '🛤️', '🛣️', '🌅', '🌄', '🌃', '🌉',
96
+ ],
97
+ },
98
+ {
99
+ id: 'objects',
100
+ label: 'Objects',
101
+ icon: '💡',
102
+ emojis: [
103
+ '⌚', '📱', '💻', '⌨️', '🖥️', '🖨️', '🖱️', '🖲️', '💽', '💾',
104
+ '💿', '📀', '🎥', '📷', '📸', '📹', '📼', '🔍', '🔎', '🕯️',
105
+ '💡', '🔦', '🏮', '🪔', '📔', '📕', '📖', '📗', '📘', '📙',
106
+ '📚', '📓', '📒', '📃', '📜', '📄', '📰', '📑', '🔖', '💰',
107
+ '🪙', '💴', '💵', '💶', '💷', '💸', '💳', '✉️', '📧', '📨',
108
+ '📩', '📤', '📥', '📦', '📫', '📪', '📬', '📭', '📮', '🗳️',
109
+ '✏️', '✒️', '🖋️', '🖊️', '🖌️', '🖍️', '📝', '📁', '📂', '🗂️',
110
+ '📅', '📆', '📇', '📈', '📉', '📊', '📋', '📌', '📍', '📎',
111
+ '🔐', '🔑', '🗝️', '🔨', '🪓', '⛏️', '⚒️', '🛠️', '🗡️', '⚔️',
112
+ '🔧', '🪛', '🔩', '⚙️', '🗜️', '⚖️', '🦯', '🔗', '⛓️', '🪝',
113
+ ],
114
+ },
115
+ {
116
+ id: 'symbols',
117
+ label: 'Symbols',
118
+ icon: '❤️',
119
+ emojis: [
120
+ '❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍', '🤎', '💔',
121
+ '❤️‍🔥', '❤️‍🩹', '❣️', '💕', '💞', '💓', '💗', '💖', '💘', '💝',
122
+ '💟', '☮️', '✝️', '☪️', '🕉️', '☸️', '✡️', '🔯', '🕎', '☯️',
123
+ '♈', '♉', '♊', '♋', '♌', '♍', '♎', '♏', '♐', '♑',
124
+ '♒', '♓', '⛎', '🔀', '🔁', '🔂', '▶️', '⏩', '⏭️', '⏯️',
125
+ '◀️', '⏪', '⏮️', '🔼', '⏫', '🔽', '⏬', '⏸️', '⏹️', '⏺️',
126
+ '⏏️', '🎦', '🔅', '🔆', '📶', '🛜', '📳', '📴', '♀️', '♂️',
127
+ '⚧️', '✖️', '➕', '➖', '➗', '🟰', '♾️', '‼️', '⁉️', '❓',
128
+ '❔', '❕', '❗', '〰️', '💱', '💲', '⚕️', '♻️', '⚜️', '🔱',
129
+ '✔️', '☑️', '✅', '❌', '❎', '➰', '➿', '〽️', '✳️', '✴️',
130
+ '❇️', '©️', '®️', '™️', '#️⃣', '*️⃣', '0️⃣', '1️⃣', '2️⃣', '3️⃣',
131
+ '🔴', '🟠', '🟡', '🟢', '🔵', '🟣', '⚫', '⚪', '🟤', '🔶',
132
+ '🔷', '🔸', '🔹', '🔺', '🔻', '💠', '🔘', '🔳', '🔲', '🏁',
133
+ '🚩', '🎌', '🏴', '🏳️', '🏳️‍🌈', '🏳️‍⚧️', '🏴‍☠️', '🇺🇸', '🇬🇧', '🇯🇵',
134
+ ],
135
+ },
136
+ ]
137
+
138
+ interface Props {
139
+ onSelect: (emoji: string) => void
140
+ onClose: () => void
141
+ }
142
+
143
+ export function ReactionPicker({ onSelect, onClose }: Props) {
144
+ const ref = useRef<HTMLDivElement>(null)
145
+ const searchRef = useRef<HTMLInputElement>(null)
146
+ const [search, setSearch] = useState('')
147
+ const [activeCategory, setActiveCategory] = useState('frequent')
148
+
149
+ useEffect(() => {
150
+ const handler = (e: MouseEvent) => {
151
+ if (ref.current && !ref.current.contains(e.target as Node)) {
152
+ onClose()
153
+ }
154
+ }
155
+ document.addEventListener('mousedown', handler)
156
+ return () => document.removeEventListener('mousedown', handler)
157
+ }, [onClose])
158
+
159
+ // Auto-focus search on open
160
+ useEffect(() => {
161
+ setTimeout(() => searchRef.current?.focus(), 50)
162
+ }, [])
163
+
164
+ const filteredEmojis = useMemo(() => {
165
+ if (!search.trim()) return null
166
+ const q = search.toLowerCase()
167
+ // Simple search: match against category names or just return all emojis that are visible
168
+ const results: string[] = []
169
+ const seen = new Set<string>()
170
+ for (const cat of CATEGORIES) {
171
+ if (cat.id === 'frequent') continue
172
+ for (const emoji of cat.emojis) {
173
+ if (!seen.has(emoji)) {
174
+ seen.add(emoji)
175
+ results.push(emoji)
176
+ }
177
+ }
178
+ }
179
+ // For basic emoji search, filter by category label matching
180
+ // Since emoji don't have text names in this simple implementation,
181
+ // we filter categories that match and show all their emojis
182
+ const matchingCats = CATEGORIES.filter(
183
+ (c) => c.id !== 'frequent' && c.label.toLowerCase().includes(q)
184
+ )
185
+ if (matchingCats.length > 0) {
186
+ const catResults: string[] = []
187
+ const catSeen = new Set<string>()
188
+ for (const cat of matchingCats) {
189
+ for (const emoji of cat.emojis) {
190
+ if (!catSeen.has(emoji)) {
191
+ catSeen.add(emoji)
192
+ catResults.push(emoji)
193
+ }
194
+ }
195
+ }
196
+ return catResults
197
+ }
198
+ // If no category match, just return all emojis (user can visually scan)
199
+ return results
200
+ }, [search])
201
+
202
+ return (
203
+ <div
204
+ ref={ref}
205
+ className="absolute right-0 bottom-8 z-50 bg-[#13131e] border border-white/[0.1] rounded-[12px] shadow-[0_8px_40px_rgba(0,0,0,0.6)] w-[320px] flex flex-col overflow-hidden"
206
+ style={{ animation: 'msg-in 0.15s ease-out both' }}
207
+ >
208
+ {/* Search */}
209
+ <div className="px-3 pt-3 pb-2">
210
+ <input
211
+ ref={searchRef}
212
+ type="text"
213
+ value={search}
214
+ onChange={(e) => setSearch(e.target.value)}
215
+ placeholder="Search emoji..."
216
+ className="w-full px-2.5 py-1.5 rounded-[8px] bg-white/[0.06] border border-white/[0.08] text-[12px] text-text placeholder:text-text-3 focus:outline-none focus:border-accent-bright/40"
217
+ />
218
+ </div>
219
+
220
+ {/* Category tabs */}
221
+ {!search.trim() && (
222
+ <div className="flex px-2 gap-0.5 pb-1">
223
+ {CATEGORIES.map((cat) => (
224
+ <button
225
+ key={cat.id}
226
+ onClick={() => setActiveCategory(cat.id)}
227
+ title={cat.label}
228
+ className={`flex-1 py-1 flex items-center justify-center rounded-[6px] text-[14px] cursor-pointer transition-all ${
229
+ activeCategory === cat.id ? 'bg-white/[0.08]' : 'hover:bg-white/[0.04]'
230
+ }`}
231
+ >
232
+ {cat.icon}
233
+ </button>
234
+ ))}
235
+ </div>
236
+ )}
237
+
238
+ {/* Emoji grid */}
239
+ <div className="px-2 pb-2 max-h-[220px] overflow-y-auto">
240
+ {search.trim() ? (
241
+ <div className="grid grid-cols-8 gap-0.5">
242
+ {filteredEmojis?.map((emoji, i) => (
243
+ <button
244
+ key={`${emoji}-${i}`}
245
+ onClick={() => onSelect(emoji)}
246
+ className="w-[34px] h-[34px] flex items-center justify-center rounded-[6px] hover:bg-white/[0.08] transition-all cursor-pointer text-[18px]"
247
+ >
248
+ {emoji}
249
+ </button>
250
+ ))}
251
+ </div>
252
+ ) : (
253
+ CATEGORIES.filter((c) => c.id === activeCategory).map((cat) => (
254
+ <div key={cat.id}>
255
+ <div className="text-[10px] font-600 text-text-3 uppercase tracking-wider px-1 py-1.5">{cat.label}</div>
256
+ <div className="grid grid-cols-8 gap-0.5">
257
+ {cat.emojis.map((emoji, i) => (
258
+ <button
259
+ key={`${emoji}-${i}`}
260
+ onClick={() => onSelect(emoji)}
261
+ className="w-[34px] h-[34px] flex items-center justify-center rounded-[6px] hover:bg-white/[0.08] transition-all cursor-pointer text-[18px]"
262
+ >
263
+ {emoji}
264
+ </button>
265
+ ))}
266
+ </div>
267
+ </div>
268
+ ))
269
+ )}
270
+ </div>
271
+ </div>
272
+ )
273
+ }
@@ -2,27 +2,40 @@
2
2
 
3
3
  import { useCallback, useEffect, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
+ import { useChatroomStore } from '@/stores/use-chatroom-store'
5
6
  import { useWs } from '@/hooks/use-ws'
6
7
  import { api } from '@/lib/api-client'
7
8
  import type { Connector } from '@/types'
8
- import { ConnectorPlatformBadge, getConnectorPlatformLabel } from '@/components/shared/connector-platform-icon'
9
+ import { ConnectorPlatformIcon, ConnectorPlatformBadge, CONNECTOR_PLATFORM_META, getConnectorPlatformLabel } from '@/components/shared/connector-platform-icon'
10
+ import { AgentAvatar } from '@/components/agents/agent-avatar'
9
11
 
10
- export function ConnectorList({ inSidebar: _inSidebar }: { inSidebar?: boolean }) {
11
- void _inSidebar
12
+ function relativeTime(ts: number): string {
13
+ const diff = Date.now() - ts
14
+ if (diff < 60_000) return 'just now'
15
+ if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`
16
+ if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`
17
+ const d = new Date(ts)
18
+ return d.toLocaleDateString([], { month: 'short', day: 'numeric' })
19
+ }
20
+
21
+ export function ConnectorList({ inSidebar }: { inSidebar?: boolean }) {
12
22
  const connectors = useAppStore((s) => s.connectors)
13
23
  const loadConnectors = useAppStore((s) => s.loadConnectors)
14
24
  const setConnectorSheetOpen = useAppStore((s) => s.setConnectorSheetOpen)
15
25
  const setEditingConnectorId = useAppStore((s) => s.setEditingConnectorId)
16
26
  const agents = useAppStore((s) => s.agents)
17
27
  const loadAgents = useAppStore((s) => s.loadAgents)
28
+ const chatrooms = useChatroomStore((s) => s.chatrooms)
29
+ const loadChatrooms = useChatroomStore((s) => s.loadChatrooms)
18
30
  const [toggling, setToggling] = useState<string | null>(null)
19
31
  const [reconnecting, setReconnecting] = useState<string | null>(null)
20
32
  const [loaded, setLoaded] = useState(false)
21
33
  const [error, setError] = useState<string | null>(null)
22
34
 
23
35
  const refresh = useCallback(async () => {
24
- await Promise.all([loadConnectors(), loadAgents()])
36
+ await Promise.all([loadConnectors(), loadAgents(), loadChatrooms()])
25
37
  setLoaded(true)
38
+ // eslint-disable-next-line react-hooks/exhaustive-deps
26
39
  }, [loadConnectors, loadAgents])
27
40
 
28
41
  useEffect(() => { void refresh() }, [refresh])
@@ -55,7 +68,6 @@ export function ConnectorList({ inSidebar: _inSidebar }: { inSidebar?: boolean }
55
68
  setReconnecting(c.id)
56
69
  setError(null)
57
70
  try {
58
- // Stop then start to reconnect
59
71
  try { await api('PUT', `/connectors/${c.id}`, { action: 'stop' }) } catch { /* may already be stopped */ }
60
72
  await api('PUT', `/connectors/${c.id}`, { action: 'start' })
61
73
  await refresh()
@@ -92,104 +104,170 @@ export function ConnectorList({ inSidebar: _inSidebar }: { inSidebar?: boolean }
92
104
  )
93
105
  }
94
106
 
107
+ // Sidebar: compact list layout
108
+ if (inSidebar) {
109
+ return (
110
+ <div className="flex-1 overflow-y-auto pb-20">
111
+ {error && (
112
+ <div className="mx-4 mt-2 mb-1 px-3 py-2 rounded-[8px] bg-red-500/10 border border-red-500/20 text-red-400 text-[11px] leading-snug">
113
+ {error}
114
+ </div>
115
+ )}
116
+ {list.map((c) => {
117
+ const agent = c.agentId ? agents[c.agentId] : null
118
+ const chatroom = c.chatroomId ? chatrooms[c.chatroomId] : null
119
+ const isRunning = c.status === 'running'
120
+ const meta = CONNECTOR_PLATFORM_META[c.platform]
121
+ return (
122
+ <button
123
+ key={c.id}
124
+ onClick={() => { setEditingConnectorId(c.id); setConnectorSheetOpen(true) }}
125
+ className="w-full flex items-center gap-3 px-5 py-2.5 hover:bg-white/[0.02] transition-colors cursor-pointer bg-transparent border-none text-left"
126
+ >
127
+ <ConnectorPlatformIcon platform={c.platform} size={16} />
128
+ <div className="flex-1 min-w-0">
129
+ <span className="text-[13px] font-600 text-text truncate block">{c.name}</span>
130
+ <span className="text-[11px] text-text-3 truncate block">
131
+ {chatroom ? chatroom.name : agent?.name || meta?.label}
132
+ </span>
133
+ </div>
134
+ <span className={`shrink-0 w-2 h-2 rounded-full ${
135
+ isRunning ? 'bg-green-400' : c.status === 'error' ? 'bg-red-400' : 'bg-white/20'
136
+ }`} />
137
+ </button>
138
+ )
139
+ })}
140
+ </div>
141
+ )
142
+ }
143
+
144
+ // Main view: card grid
95
145
  return (
96
- <div className="flex-1 overflow-y-auto pb-20">
146
+ <div className="flex-1 overflow-y-auto pb-20 px-5 pt-2">
97
147
  {error && (
98
- <div className="mx-4 mt-2 mb-1 px-3 py-2 rounded-[8px] bg-red-500/10 border border-red-500/20 text-red-400 text-[11px] leading-snug">
148
+ <div className="mb-3 px-3 py-2 rounded-[8px] bg-red-500/10 border border-red-500/20 text-red-400 text-[11px] leading-snug">
99
149
  {error}
100
150
  </div>
101
151
  )}
102
- {list.map((c) => {
103
- const platformLabel = getConnectorPlatformLabel(c.platform)
104
- const agent = agents[c.agentId]
105
- const isRunning = c.status === 'running'
106
- const isToggling = toggling === c.id
107
- // Can only toggle if connector has credentials (or uses non-token auth modes).
108
- const hasCredentials = c.platform === 'whatsapp'
109
- || c.platform === 'openclaw'
110
- || c.platform === 'signal'
111
- || (c.platform === 'bluebubbles' && (!!c.credentialId || !!c.config?.password))
112
- || !!c.credentialId
113
- return (
114
- <div
115
- key={c.id}
116
- className="w-full flex items-center gap-3 px-5 py-3 hover:bg-white/[0.02] transition-colors group"
117
- >
118
- {/* Clickable area — opens editor */}
152
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
153
+ {list.map((c) => {
154
+ const platformLabel = getConnectorPlatformLabel(c.platform)
155
+ const agent = c.agentId ? agents[c.agentId] : null
156
+ const chatroom = c.chatroomId ? chatrooms[c.chatroomId] : null
157
+ const isRunning = c.status === 'running'
158
+ const isToggling = toggling === c.id
159
+ const hasCredentials = c.platform === 'whatsapp'
160
+ || c.platform === 'openclaw'
161
+ || c.platform === 'signal'
162
+ || (c.platform === 'bluebubbles' && (!!c.credentialId || !!c.config?.password))
163
+ || !!c.credentialId
164
+ const lastMsg = c.presence?.lastMessageAt
165
+
166
+ return (
119
167
  <button
168
+ key={c.id}
120
169
  onClick={() => { setEditingConnectorId(c.id); setConnectorSheetOpen(true) }}
121
- className="flex items-center gap-3 flex-1 min-w-0 cursor-pointer bg-transparent border-none text-left p-0"
170
+ className="group relative flex flex-col rounded-[14px] border border-white/[0.06] bg-surface p-4 cursor-pointer transition-all hover:border-white/[0.12] hover:bg-white/[0.02] text-left w-full"
171
+ style={{ fontFamily: 'inherit' }}
122
172
  >
123
- <ConnectorPlatformBadge platform={c.platform} size={36} iconSize={16} />
173
+ {/* Header: platform badge + status */}
174
+ <div className="flex items-center gap-3 mb-3">
175
+ <ConnectorPlatformBadge platform={c.platform} size={40} iconSize={20} roundedClassName="rounded-[10px]" />
176
+ <div className="flex-1 min-w-0">
177
+ <div className="flex items-center gap-2">
178
+ <span className="text-[14px] font-600 text-text truncate">{c.name}</span>
179
+ <span className={`shrink-0 w-2 h-2 rounded-full ${
180
+ isRunning ? 'bg-green-400' : c.status === 'error' ? 'bg-red-400' : 'bg-white/20'
181
+ }`} />
182
+ </div>
183
+ <span className="text-[11px] text-text-3 block">
184
+ {isRunning ? 'Connected' : c.status === 'error' ? 'Error' : 'Stopped'}
185
+ {c.qrDataUrl && ' · QR ready'}
186
+ </span>
187
+ </div>
188
+ </div>
124
189
 
125
- <div className="flex-1 min-w-0">
126
- <div className="flex items-center gap-2">
127
- <span className="text-[13px] font-600 text-text truncate">{c.name}</span>
128
- <span
129
- className={`shrink-0 w-2 h-2 rounded-full ${
130
- isRunning ? 'bg-green-400' :
131
- c.status === 'error' ? 'bg-red-400' : 'bg-white/20'
132
- }`}
133
- />
134
- {c.qrDataUrl && (
135
- <span className="shrink-0 text-[10px] font-600 uppercase tracking-wider text-violet-400/80 bg-violet-400/[0.08] px-1.5 py-0.5 rounded-[5px]">
136
- QR Ready
137
- </span>
190
+ {/* Route target: agent or chatroom */}
191
+ <div className="flex items-center gap-2.5 mb-2.5 px-0.5">
192
+ {chatroom ? (
193
+ <>
194
+ <div className="w-6 h-6 rounded-full bg-white/[0.06] flex items-center justify-center shrink-0">
195
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3">
196
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
197
+ </svg>
198
+ </div>
199
+ <div className="flex-1 min-w-0">
200
+ <span className="text-[12px] font-600 text-text-2 block truncate">{chatroom.name}</span>
201
+ <span className="text-[10px] text-text-3/60 block">
202
+ {chatroom.agentIds.length} agent{chatroom.agentIds.length !== 1 ? 's' : ''}
203
+ {chatroom.chatMode === 'parallel' ? ' · parallel' : ' · sequential'}
204
+ </span>
205
+ </div>
206
+ </>
207
+ ) : agent ? (
208
+ <>
209
+ <AgentAvatar seed={agent.avatarSeed || null} name={agent.name} size={24} />
210
+ <div className="flex-1 min-w-0">
211
+ <span className="text-[12px] font-600 text-text-2 block truncate">{agent.name}</span>
212
+ <span className="text-[10px] text-text-3/60 block">{agent.provider}/{agent.model}</span>
213
+ </div>
214
+ </>
215
+ ) : (
216
+ <span className="text-[11px] text-text-3/50">{platformLabel}</span>
217
+ )}
218
+ </div>
219
+
220
+ {/* Footer: last message time + error */}
221
+ <div className="flex items-center gap-2 mt-auto pt-2 border-t border-white/[0.04]">
222
+ {c.lastError ? (
223
+ <span className="text-[10px] text-red-400 truncate flex-1">
224
+ {c.lastError.slice(0, 50)}{c.lastError.length > 50 ? '...' : ''}
225
+ </span>
226
+ ) : lastMsg ? (
227
+ <span className="text-[10px] text-text-3/60 flex-1">Last message {relativeTime(lastMsg)}</span>
228
+ ) : (
229
+ <span className="text-[10px] text-text-3/40 flex-1">No messages yet</span>
230
+ )}
231
+
232
+ {/* Action buttons */}
233
+ <div className="flex gap-1 shrink-0" onClick={(e) => e.stopPropagation()}>
234
+ {c.status === 'error' && hasCredentials && (
235
+ <button
236
+ onClick={(e) => handleReconnect(e, c)}
237
+ disabled={reconnecting === c.id}
238
+ title="Reconnect"
239
+ className="px-2 py-1 rounded-[6px] text-[10px] font-600 transition-all cursor-pointer border-none opacity-0 group-hover:opacity-100 bg-amber-500/10 text-amber-400 hover:bg-amber-500/20 disabled:opacity-50"
240
+ >
241
+ {reconnecting === c.id ? '...' : 'Reconnect'}
242
+ </button>
243
+ )}
244
+ {hasCredentials && (
245
+ <button
246
+ onClick={(e) => handleToggle(e, c)}
247
+ disabled={isToggling}
248
+ title={isRunning ? 'Stop' : 'Start'}
249
+ className={`w-7 h-7 rounded-[6px] flex items-center justify-center transition-all cursor-pointer border-none ${
250
+ isToggling ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
251
+ } ${isRunning
252
+ ? 'bg-red-500/10 text-red-400 hover:bg-red-500/20'
253
+ : 'bg-green-500/10 text-green-400 hover:bg-green-500/20'
254
+ } disabled:opacity-50`}
255
+ >
256
+ {isToggling ? (
257
+ <span className="w-3 h-3 rounded-full border-2 border-current border-t-transparent animate-spin" />
258
+ ) : isRunning ? (
259
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><rect x="4" y="4" width="16" height="16" rx="2" /></svg>
260
+ ) : (
261
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="currentColor"><polygon points="6,3 21,12 6,21" /></svg>
262
+ )}
263
+ </button>
138
264
  )}
139
- </div>
140
- <div className="text-[11px] text-text-3 truncate">
141
- {c.lastError
142
- ? <span className="text-red-400">{c.lastError.slice(0, 60)}{c.lastError.length > 60 ? '...' : ''}</span>
143
- : <>{platformLabel} {agent ? `\u2192 ${agent.name}` : ''}</>
144
- }
145
265
  </div>
146
266
  </div>
147
267
  </button>
148
-
149
- {/* Reconnect button for error-state connectors */}
150
- {c.status === 'error' && hasCredentials && (
151
- <button
152
- onClick={(e) => handleReconnect(e, c)}
153
- disabled={reconnecting === c.id}
154
- title="Reconnect"
155
- aria-label="Reconnect connector"
156
- className={`shrink-0 px-2.5 py-1.5 rounded-[8px] text-[11px] font-600 transition-all cursor-pointer border-none
157
- ${reconnecting === c.id ? 'opacity-50' : 'opacity-0 group-hover:opacity-100 focus:opacity-100'}
158
- bg-amber-500/10 text-amber-400 hover:bg-amber-500/20`}
159
- >
160
- {reconnecting === c.id ? '...' : 'Reconnect'}
161
- </button>
162
- )}
163
-
164
- {/* Toggle button — visible on hover, only if connector has credentials */}
165
- {hasCredentials && <button
166
- onClick={(e) => handleToggle(e, c)}
167
- disabled={isToggling}
168
- title={isRunning ? 'Stop connector' : 'Start connector'}
169
- aria-label={isRunning ? 'Stop connector' : 'Start connector'}
170
- className={`shrink-0 w-8 h-8 rounded-[8px] flex items-center justify-center transition-all cursor-pointer border-none ${
171
- isToggling ? 'opacity-100' : 'opacity-0 group-hover:opacity-100 focus:opacity-100'
172
- } ${
173
- isRunning
174
- ? 'bg-red-500/10 text-red-400 hover:bg-red-500/20'
175
- : 'bg-green-500/10 text-green-400 hover:bg-green-500/20'
176
- } disabled:opacity-50`}
177
- >
178
- {isToggling ? (
179
- <span className="w-3 h-3 rounded-full border-2 border-current border-t-transparent animate-spin" />
180
- ) : isRunning ? (
181
- <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
182
- <rect x="4" y="4" width="16" height="16" rx="2" />
183
- </svg>
184
- ) : (
185
- <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
186
- <polygon points="6,3 21,12 6,21" />
187
- </svg>
188
- )}
189
- </button>}
190
- </div>
191
- )
192
- })}
268
+ )
269
+ })}
270
+ </div>
193
271
  </div>
194
272
  )
195
273
  }