@swarmclawai/swarmclaw 0.5.2 → 0.6.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 (173) hide show
  1. package/README.md +42 -7
  2. package/bin/swarmclaw.js +76 -16
  3. package/next.config.ts +11 -1
  4. package/package.json +4 -2
  5. package/public/screenshots/agents.png +0 -0
  6. package/public/screenshots/dashboard.png +0 -0
  7. package/public/screenshots/providers.png +0 -0
  8. package/public/screenshots/tasks.png +0 -0
  9. package/scripts/postinstall.mjs +18 -0
  10. package/src/app/api/chatrooms/[id]/chat/route.ts +410 -0
  11. package/src/app/api/chatrooms/[id]/members/route.ts +82 -0
  12. package/src/app/api/chatrooms/[id]/pins/route.ts +39 -0
  13. package/src/app/api/chatrooms/[id]/reactions/route.ts +42 -0
  14. package/src/app/api/chatrooms/[id]/route.ts +84 -0
  15. package/src/app/api/chatrooms/route.ts +50 -0
  16. package/src/app/api/credentials/route.ts +2 -3
  17. package/src/app/api/knowledge/[id]/route.ts +13 -2
  18. package/src/app/api/knowledge/route.ts +8 -1
  19. package/src/app/api/memory/route.ts +8 -0
  20. package/src/app/api/notifications/[id]/route.ts +27 -0
  21. package/src/app/api/notifications/route.ts +68 -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 +155 -0
  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/route.ts +3 -3
  29. package/src/app/api/settings/route.ts +9 -0
  30. package/src/app/api/setup/check-provider/route.ts +3 -16
  31. package/src/app/api/skills/[id]/route.ts +6 -0
  32. package/src/app/api/skills/route.ts +6 -0
  33. package/src/app/api/tasks/[id]/route.ts +20 -0
  34. package/src/app/api/tasks/bulk/route.ts +100 -0
  35. package/src/app/api/tasks/route.ts +1 -0
  36. package/src/app/api/usage/route.ts +45 -0
  37. package/src/app/api/webhooks/[id]/route.ts +15 -1
  38. package/src/app/globals.css +58 -15
  39. package/src/app/page.tsx +142 -13
  40. package/src/cli/index.js +42 -0
  41. package/src/cli/index.test.js +30 -0
  42. package/src/cli/spec.js +32 -0
  43. package/src/components/agents/agent-avatar.tsx +57 -10
  44. package/src/components/agents/agent-card.tsx +48 -15
  45. package/src/components/agents/agent-chat-list.tsx +123 -10
  46. package/src/components/agents/agent-list.tsx +50 -19
  47. package/src/components/agents/agent-sheet.tsx +56 -63
  48. package/src/components/auth/access-key-gate.tsx +10 -3
  49. package/src/components/auth/setup-wizard.tsx +2 -2
  50. package/src/components/auth/user-picker.tsx +31 -3
  51. package/src/components/chat/activity-moment.tsx +169 -0
  52. package/src/components/chat/chat-header.tsx +2 -0
  53. package/src/components/chat/chat-tool-toggles.tsx +1 -1
  54. package/src/components/chat/file-path-chip.tsx +125 -0
  55. package/src/components/chat/markdown-utils.ts +9 -0
  56. package/src/components/chat/message-bubble.tsx +46 -295
  57. package/src/components/chat/message-list.tsx +50 -1
  58. package/src/components/chat/streaming-bubble.tsx +36 -46
  59. package/src/components/chat/suggestions-bar.tsx +1 -1
  60. package/src/components/chat/thinking-indicator.tsx +72 -10
  61. package/src/components/chat/tool-call-bubble.tsx +66 -70
  62. package/src/components/chat/tool-request-banner.tsx +31 -7
  63. package/src/components/chat/transfer-agent-picker.tsx +63 -0
  64. package/src/components/chatrooms/agent-hover-card.tsx +124 -0
  65. package/src/components/chatrooms/chatroom-input.tsx +320 -0
  66. package/src/components/chatrooms/chatroom-list.tsx +123 -0
  67. package/src/components/chatrooms/chatroom-message.tsx +427 -0
  68. package/src/components/chatrooms/chatroom-sheet.tsx +215 -0
  69. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +134 -0
  70. package/src/components/chatrooms/chatroom-typing-bar.tsx +88 -0
  71. package/src/components/chatrooms/chatroom-view.tsx +344 -0
  72. package/src/components/chatrooms/reaction-picker.tsx +273 -0
  73. package/src/components/connectors/connector-sheet.tsx +34 -47
  74. package/src/components/home/home-view.tsx +501 -0
  75. package/src/components/input/chat-input.tsx +79 -41
  76. package/src/components/knowledge/knowledge-list.tsx +31 -1
  77. package/src/components/knowledge/knowledge-sheet.tsx +83 -2
  78. package/src/components/layout/app-layout.tsx +209 -83
  79. package/src/components/layout/mobile-header.tsx +2 -0
  80. package/src/components/layout/update-banner.tsx +2 -2
  81. package/src/components/logs/log-list.tsx +2 -2
  82. package/src/components/mcp-servers/mcp-server-sheet.tsx +1 -1
  83. package/src/components/memory/memory-agent-list.tsx +143 -0
  84. package/src/components/memory/memory-browser.tsx +205 -0
  85. package/src/components/memory/memory-card.tsx +34 -7
  86. package/src/components/memory/memory-detail.tsx +359 -120
  87. package/src/components/memory/memory-sheet.tsx +157 -23
  88. package/src/components/plugins/plugin-list.tsx +1 -1
  89. package/src/components/plugins/plugin-sheet.tsx +1 -1
  90. package/src/components/projects/project-detail.tsx +509 -0
  91. package/src/components/projects/project-list.tsx +195 -59
  92. package/src/components/providers/provider-list.tsx +2 -2
  93. package/src/components/providers/provider-sheet.tsx +3 -3
  94. package/src/components/schedules/schedule-card.tsx +3 -2
  95. package/src/components/schedules/schedule-list.tsx +1 -1
  96. package/src/components/schedules/schedule-sheet.tsx +25 -25
  97. package/src/components/secrets/secret-sheet.tsx +47 -24
  98. package/src/components/secrets/secrets-list.tsx +18 -8
  99. package/src/components/sessions/new-session-sheet.tsx +33 -65
  100. package/src/components/sessions/session-card.tsx +45 -14
  101. package/src/components/sessions/session-list.tsx +35 -18
  102. package/src/components/shared/agent-picker-list.tsx +90 -0
  103. package/src/components/shared/agent-switch-dialog.tsx +156 -0
  104. package/src/components/shared/attachment-chip.tsx +165 -0
  105. package/src/components/shared/avatar.tsx +10 -1
  106. package/src/components/shared/check-icon.tsx +12 -0
  107. package/src/components/shared/confirm-dialog.tsx +1 -1
  108. package/src/components/shared/empty-state.tsx +32 -0
  109. package/src/components/shared/file-preview.tsx +34 -0
  110. package/src/components/shared/form-styles.ts +2 -0
  111. package/src/components/shared/keyboard-shortcuts-dialog.tsx +116 -0
  112. package/src/components/shared/notification-center.tsx +223 -0
  113. package/src/components/shared/profile-sheet.tsx +115 -0
  114. package/src/components/shared/reply-quote.tsx +26 -0
  115. package/src/components/shared/search-dialog.tsx +296 -0
  116. package/src/components/shared/section-label.tsx +12 -0
  117. package/src/components/shared/settings/plugin-manager.tsx +1 -1
  118. package/src/components/shared/settings/section-providers.tsx +1 -1
  119. package/src/components/shared/settings/section-secrets.tsx +1 -1
  120. package/src/components/shared/settings/section-theme.tsx +95 -0
  121. package/src/components/shared/settings/section-user-preferences.tsx +39 -0
  122. package/src/components/shared/settings/settings-page.tsx +180 -27
  123. package/src/components/shared/settings/settings-sheet.tsx +9 -73
  124. package/src/components/shared/sheet-footer.tsx +33 -0
  125. package/src/components/skills/skill-list.tsx +61 -30
  126. package/src/components/skills/skill-sheet.tsx +81 -2
  127. package/src/components/tasks/task-board.tsx +448 -26
  128. package/src/components/tasks/task-card.tsx +46 -9
  129. package/src/components/tasks/task-column.tsx +62 -3
  130. package/src/components/tasks/task-list.tsx +12 -4
  131. package/src/components/tasks/task-sheet.tsx +89 -72
  132. package/src/components/ui/hover-card.tsx +52 -0
  133. package/src/components/usage/metrics-dashboard.tsx +78 -0
  134. package/src/components/usage/usage-list.tsx +1 -1
  135. package/src/components/webhooks/webhook-sheet.tsx +1 -1
  136. package/src/hooks/use-view-router.ts +69 -19
  137. package/src/instrumentation.ts +15 -1
  138. package/src/lib/chat.ts +2 -0
  139. package/src/lib/cron-human.ts +114 -0
  140. package/src/lib/memory.ts +3 -0
  141. package/src/lib/server/chat-execution.ts +24 -4
  142. package/src/lib/server/connectors/manager.ts +11 -0
  143. package/src/lib/server/context-manager.ts +225 -13
  144. package/src/lib/server/create-notification.ts +42 -0
  145. package/src/lib/server/daemon-state.ts +165 -10
  146. package/src/lib/server/execution-log.ts +1 -0
  147. package/src/lib/server/heartbeat-service.ts +40 -5
  148. package/src/lib/server/heartbeat-wake.ts +110 -0
  149. package/src/lib/server/langgraph-checkpoint.ts +1 -0
  150. package/src/lib/server/memory-consolidation.ts +92 -0
  151. package/src/lib/server/memory-db.ts +51 -6
  152. package/src/lib/server/openclaw-gateway.ts +9 -1
  153. package/src/lib/server/provider-health.ts +125 -0
  154. package/src/lib/server/queue.ts +5 -4
  155. package/src/lib/server/scheduler.ts +8 -0
  156. package/src/lib/server/session-run-manager.ts +4 -0
  157. package/src/lib/server/session-tools/chatroom.ts +136 -0
  158. package/src/lib/server/session-tools/context-mgmt.ts +36 -18
  159. package/src/lib/server/session-tools/index.ts +2 -0
  160. package/src/lib/server/session-tools/memory.ts +6 -1
  161. package/src/lib/server/storage.ts +80 -29
  162. package/src/lib/server/stream-agent-chat.ts +153 -47
  163. package/src/lib/server/system-events.ts +49 -0
  164. package/src/lib/server/ws-hub.ts +11 -0
  165. package/src/lib/soul-suggestions.ts +109 -0
  166. package/src/lib/tasks.ts +4 -1
  167. package/src/lib/view-routes.ts +36 -1
  168. package/src/lib/ws-client.ts +14 -4
  169. package/src/proxy.ts +79 -2
  170. package/src/stores/use-app-store.ts +94 -3
  171. package/src/stores/use-chat-store.ts +48 -3
  172. package/src/stores/use-chatroom-store.ts +276 -0
  173. package/src/types/index.ts +69 -2
@@ -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
+ }
@@ -8,6 +8,9 @@ import { useWs } from '@/hooks/use-ws'
8
8
  import { toast } from 'sonner'
9
9
  import type { Connector, ConnectorPlatform } from '@/types'
10
10
  import { ConnectorPlatformBadge } from '@/components/shared/connector-platform-icon'
11
+ import { AgentPickerList } from '@/components/shared/agent-picker-list'
12
+ import { SheetFooter } from '@/components/shared/sheet-footer'
13
+ import { SectionLabel } from '@/components/shared/section-label'
11
14
 
12
15
  /** Auto-detect URLs in text and make them clickable links that open in a new tab */
13
16
  function linkify(text: string) {
@@ -283,10 +286,10 @@ export function ConnectorSheet() {
283
286
  const pollWaStatus = useCallback(async () => {
284
287
  if (!editing) return
285
288
  try {
286
- const data = await api<any>('GET', `/connectors/${editing.id}`)
287
- setQrDataUrl(data.qrDataUrl || null)
288
- setWaAuthenticated(data.authenticated ?? false)
289
- setWaHasCreds(data.hasCredentials ?? false)
289
+ const data = await api<Record<string, unknown>>('GET', `/connectors/${editing.id}`)
290
+ setQrDataUrl((data.qrDataUrl as string | null) || null)
291
+ setWaAuthenticated((data.authenticated as boolean) ?? false)
292
+ setWaHasCreds((data.hasCredentials as boolean) ?? false)
290
293
  if (data.status === 'running' && editing.status !== 'running') {
291
294
  const store = useAppStore.getState()
292
295
  const updated = { ...store.connectors }
@@ -316,8 +319,8 @@ export function ConnectorSheet() {
316
319
  await loadConnectors()
317
320
  setOpen(false)
318
321
  setEditingId(null)
319
- } catch (err: any) {
320
- toast.error(err.message)
322
+ } catch (err: unknown) {
323
+ toast.error(err instanceof Error ? err.message : String(err))
321
324
  } finally {
322
325
  setSaving(false)
323
326
  }
@@ -340,9 +343,9 @@ export function ConnectorSheet() {
340
343
  setQrDataUrl(null)
341
344
  }
342
345
  await loadConnectors()
343
- } catch (err: any) {
346
+ } catch (err: unknown) {
344
347
  setWaConnecting(false)
345
- toast.error(`Failed to ${action}: ${err.message}`)
348
+ toast.error(`Failed to ${action}: ${err instanceof Error ? err.message : String(err)}`)
346
349
  } finally {
347
350
  setActionLoading(false)
348
351
  }
@@ -357,7 +360,7 @@ export function ConnectorSheet() {
357
360
  }
358
361
 
359
362
  const platformConfig = PLATFORMS.find((p) => p.id === platform)!
360
- const agentList = Object.values(agents)
363
+ const agentList = Object.values(agents).sort((a, b) => a.name.localeCompare(b.name))
361
364
  const credList = Object.values(credentials)
362
365
 
363
366
  const inputClass = "w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-surface text-text text-[14px] outline-none transition-all placeholder:text-text-3/50 focus:border-white/[0.15]"
@@ -374,7 +377,7 @@ export function ConnectorSheet() {
374
377
  {/* Platform selector (only for new) */}
375
378
  {!editing && (
376
379
  <div className="mb-8">
377
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Platform</label>
380
+ <SectionLabel>Platform</SectionLabel>
378
381
  <div className="grid grid-cols-2 gap-3">
379
382
  {PLATFORMS.map((p) => (
380
383
  <button
@@ -460,17 +463,12 @@ export function ConnectorSheet() {
460
463
  <div className="mb-6">
461
464
  <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">Route to Agent</label>
462
465
  <p className="text-[12px] text-text-3/60 mb-2">Incoming messages will be handled by this agent</p>
463
- <select
464
- value={agentId || ''}
465
- onChange={(e) => setAgentId(e.target.value)}
466
- className={`${inputClass} appearance-none cursor-pointer`}
467
- style={{ fontFamily: 'inherit' }}
468
- >
469
- <option value="">Select a agent...</option>
470
- {agentList.map((p: any) => (
471
- <option key={p.id} value={p.id}>{p.name}{p.isOrchestrator ? ' (Orchestrator)' : ''}</option>
472
- ))}
473
- </select>
466
+ <AgentPickerList
467
+ agents={agentList}
468
+ selected={agentId}
469
+ onSelect={(id) => setAgentId(id)}
470
+ showOrchBadge={true}
471
+ />
474
472
  </div>
475
473
 
476
474
  {/* Bot token credential */}
@@ -495,7 +493,7 @@ export function ConnectorSheet() {
495
493
  style={{ fontFamily: 'inherit' }}
496
494
  >
497
495
  <option value="">Select credential...</option>
498
- {credList.map((c: any) => (
496
+ {credList.map((c) => (
499
497
  <option key={c.id} value={c.id}>{c.name} ({c.provider})</option>
500
498
  ))}
501
499
  <option value="__new__">+ Add new key...</option>
@@ -547,7 +545,7 @@ export function ConnectorSheet() {
547
545
  onClick={async () => {
548
546
  setSavingCred(true)
549
547
  try {
550
- const cred = await api<any>('POST', '/credentials', {
548
+ const cred = await api<{ id: string }>('POST', '/credentials', {
551
549
  provider: platform,
552
550
  name: newCredName.trim() || `${platformConfig.label} Bot Token`,
553
551
  apiKey: newCredValue.trim(),
@@ -557,8 +555,8 @@ export function ConnectorSheet() {
557
555
  setShowNewCred(false)
558
556
  setNewCredName('')
559
557
  setNewCredValue('')
560
- } catch (err: any) {
561
- toast.error(`Failed to save: ${err.message}`)
558
+ } catch (err: unknown) {
559
+ toast.error(`Failed to save: ${err instanceof Error ? err.message : String(err)}`)
562
560
  } finally {
563
561
  setSavingCred(false)
564
562
  }
@@ -730,8 +728,8 @@ export function ConnectorSheet() {
730
728
  setQrDataUrl(null)
731
729
  setWaConnecting(true)
732
730
  await loadConnectors()
733
- } catch (err: any) {
734
- toast.error(`Failed to unlink: ${err.message}`)
731
+ } catch (err: unknown) {
732
+ toast.error(`Failed to unlink: ${err instanceof Error ? err.message : String(err)}`)
735
733
  } finally {
736
734
  setActionLoading(false)
737
735
  }
@@ -771,8 +769,8 @@ export function ConnectorSheet() {
771
769
  setQrDataUrl(null)
772
770
  setWaConnecting(true)
773
771
  await loadConnectors()
774
- } catch (err: any) {
775
- toast.error(`Failed to re-pair: ${err.message}`)
772
+ } catch (err: unknown) {
773
+ toast.error(`Failed to re-pair: ${err instanceof Error ? err.message : String(err)}`)
776
774
  } finally {
777
775
  setActionLoading(false)
778
776
  }
@@ -796,28 +794,17 @@ export function ConnectorSheet() {
796
794
  )}
797
795
 
798
796
  {/* Actions */}
799
- <div className="flex gap-3 pt-4 border-t border-white/[0.04]">
800
- {editing && (
797
+ <SheetFooter
798
+ onCancel={() => { setOpen(false); setEditingId(null) }}
799
+ onSave={handleSave}
800
+ saveLabel={saving ? 'Saving...' : editing ? 'Save' : 'Create Connector'}
801
+ saveDisabled={saving || !agentId}
802
+ left={editing && (
801
803
  <button onClick={handleDelete} className="py-3.5 px-6 rounded-[14px] border border-red-500/20 bg-transparent text-red-400 text-[15px] font-600 cursor-pointer hover:bg-red-500/10 transition-all" style={{ fontFamily: 'inherit' }}>
802
804
  Delete
803
805
  </button>
804
806
  )}
805
- <button
806
- onClick={() => { setOpen(false); setEditingId(null) }}
807
- className="flex-1 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer hover:bg-surface-2 transition-all"
808
- style={{ fontFamily: 'inherit' }}
809
- >
810
- Cancel
811
- </button>
812
- <button
813
- onClick={handleSave}
814
- disabled={saving || !agentId}
815
- className="flex-1 py-3.5 rounded-[14px] border-none bg-[#6366F1] text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110"
816
- style={{ fontFamily: 'inherit' }}
817
- >
818
- {saving ? 'Saving...' : editing ? 'Save' : 'Create Connector'}
819
- </button>
820
- </div>
807
+ />
821
808
  </BottomSheet>
822
809
  )
823
810
  }