@agent-relay/dashboard 2.0.80 → 2.0.82

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 (244) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/chunks/{118-4c8241b0218335de.js → 118-ae2b650136a5a5fc.js} +1 -1
  3. package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +1 -0
  4. package/out/_next/static/chunks/app/app/[[...slug]]/{page-1e81c047cff17212.js → page-f7eca1b66fb4249b.js} +1 -1
  5. package/out/_next/static/chunks/app/{page-6892fe2dd07fb48b.js → page-0ee604f7070d14c0.js} +1 -1
  6. package/out/_next/static/css/8968d98ed4c4d33f.css +1 -0
  7. package/out/about.html +2 -2
  8. package/out/about.txt +1 -1
  9. package/out/app/onboarding.html +1 -1
  10. package/out/app/onboarding.txt +1 -1
  11. package/out/app.html +1 -1
  12. package/out/app.txt +2 -2
  13. package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +2 -2
  14. package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
  15. package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
  16. package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
  17. package/out/blog.html +2 -2
  18. package/out/blog.txt +1 -1
  19. package/out/careers.html +2 -2
  20. package/out/careers.txt +1 -1
  21. package/out/changelog.html +2 -2
  22. package/out/changelog.txt +1 -1
  23. package/out/cloud/link.html +1 -1
  24. package/out/cloud/link.txt +2 -2
  25. package/out/complete-profile.html +2 -2
  26. package/out/complete-profile.txt +1 -1
  27. package/out/connect-repos.html +1 -1
  28. package/out/connect-repos.txt +1 -1
  29. package/out/contact.html +2 -2
  30. package/out/contact.txt +1 -1
  31. package/out/docs.html +2 -2
  32. package/out/docs.txt +1 -1
  33. package/out/history.html +1 -1
  34. package/out/history.txt +2 -2
  35. package/out/index.html +1 -1
  36. package/out/index.txt +2 -2
  37. package/out/login.html +2 -2
  38. package/out/login.txt +1 -1
  39. package/out/metrics.html +1 -1
  40. package/out/metrics.txt +2 -2
  41. package/out/pricing.html +2 -2
  42. package/out/pricing.txt +1 -1
  43. package/out/privacy.html +2 -2
  44. package/out/privacy.txt +1 -1
  45. package/out/providers/setup/claude.html +1 -1
  46. package/out/providers/setup/claude.txt +1 -1
  47. package/out/providers/setup/codex.html +1 -1
  48. package/out/providers/setup/codex.txt +1 -1
  49. package/out/providers/setup/cursor.html +1 -1
  50. package/out/providers/setup/cursor.txt +1 -1
  51. package/out/providers.html +1 -1
  52. package/out/providers.txt +1 -1
  53. package/out/security.html +2 -2
  54. package/out/security.txt +1 -1
  55. package/out/signup.html +2 -2
  56. package/out/signup.txt +1 -1
  57. package/out/terms.html +2 -2
  58. package/out/terms.txt +1 -1
  59. package/package.json +7 -1
  60. package/src/app/about/page.tsx +7 -0
  61. package/src/app/app/[[...slug]]/DashboardPageClient.tsx +853 -0
  62. package/src/app/app/[[...slug]]/page.tsx +23 -0
  63. package/src/app/app/onboarding/page.tsx +394 -0
  64. package/src/app/apple-icon.png +0 -0
  65. package/src/app/blog/go-to-bed-wake-up-to-a-finished-product/page.tsx +88 -0
  66. package/src/app/blog/let-them-cook-multi-agent-orchestration/page.tsx +93 -0
  67. package/src/app/blog/page.tsx +15 -0
  68. package/src/app/careers/page.tsx +7 -0
  69. package/src/app/changelog/page.tsx +7 -0
  70. package/src/app/cloud/link/page.tsx +464 -0
  71. package/src/app/complete-profile/page.tsx +204 -0
  72. package/src/app/connect-repos/page.tsx +410 -0
  73. package/src/app/contact/page.tsx +7 -0
  74. package/src/app/docs/page.tsx +7 -0
  75. package/src/app/favicon.png +0 -0
  76. package/src/app/globals.css +200 -0
  77. package/src/app/history/page.tsx +658 -0
  78. package/src/app/layout.tsx +25 -0
  79. package/src/app/login/page.tsx +424 -0
  80. package/src/app/metrics/page.tsx +781 -0
  81. package/src/app/page.tsx +59 -0
  82. package/src/app/pricing/page.tsx +7 -0
  83. package/src/app/privacy/page.tsx +7 -0
  84. package/src/app/providers/page.tsx +193 -0
  85. package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +197 -0
  86. package/src/app/providers/setup/[provider]/constants.ts +35 -0
  87. package/src/app/providers/setup/[provider]/page.tsx +42 -0
  88. package/src/app/security/page.tsx +7 -0
  89. package/src/app/signup/page.tsx +533 -0
  90. package/src/app/terms/page.tsx +7 -0
  91. package/src/components/ActivityFeed.tsx +216 -0
  92. package/src/components/AddWorkspaceModal.tsx +170 -0
  93. package/src/components/AgentCard.test.tsx +134 -0
  94. package/src/components/AgentCard.tsx +585 -0
  95. package/src/components/AgentList.test.tsx +147 -0
  96. package/src/components/AgentList.tsx +419 -0
  97. package/src/components/AgentLogPreview.tsx +173 -0
  98. package/src/components/AgentProfilePanel.tsx +569 -0
  99. package/src/components/App.tsx +3424 -0
  100. package/src/components/BillingPanel.tsx +922 -0
  101. package/src/components/BillingResult.tsx +447 -0
  102. package/src/components/BroadcastComposer.tsx +690 -0
  103. package/src/components/ChannelAdminPanel.tsx +773 -0
  104. package/src/components/ChannelBrowser.tsx +385 -0
  105. package/src/components/ChannelChat.tsx +261 -0
  106. package/src/components/ChannelSidebar.tsx +399 -0
  107. package/src/components/CloudSessionProvider.tsx +130 -0
  108. package/src/components/CommandPalette.tsx +815 -0
  109. package/src/components/ConfirmationDialog.tsx +133 -0
  110. package/src/components/ConversationHistory.tsx +518 -0
  111. package/src/components/CoordinatorPanel.tsx +956 -0
  112. package/src/components/DecisionQueue.tsx +717 -0
  113. package/src/components/DirectMessageView.tsx +164 -0
  114. package/src/components/FileAutocomplete.tsx +368 -0
  115. package/src/components/FleetOverview.tsx +278 -0
  116. package/src/components/LogViewer.tsx +310 -0
  117. package/src/components/LogViewerPanel.tsx +482 -0
  118. package/src/components/Logo.tsx +284 -0
  119. package/src/components/MentionAutocomplete.tsx +384 -0
  120. package/src/components/MessageComposer.tsx +473 -0
  121. package/src/components/MessageList.tsx +725 -0
  122. package/src/components/MessageSenderName.tsx +91 -0
  123. package/src/components/MessageStatusIndicator.tsx +142 -0
  124. package/src/components/NewConversationModal.tsx +400 -0
  125. package/src/components/NotificationToast.tsx +488 -0
  126. package/src/components/OnlineUsersIndicator.tsx +164 -0
  127. package/src/components/Pagination.tsx +124 -0
  128. package/src/components/PricingPlans.tsx +386 -0
  129. package/src/components/ProjectList.tsx +711 -0
  130. package/src/components/ProviderAuthFlow.tsx +343 -0
  131. package/src/components/ProviderConnectionList.tsx +375 -0
  132. package/src/components/ProvisioningProgress.tsx +730 -0
  133. package/src/components/ReactionChips.tsx +70 -0
  134. package/src/components/ReactionPicker.tsx +121 -0
  135. package/src/components/RepoAccessPanel.tsx +787 -0
  136. package/src/components/RepositoriesPanel.tsx +901 -0
  137. package/src/components/ServerCard.tsx +202 -0
  138. package/src/components/SessionExpiredModal.tsx +128 -0
  139. package/src/components/SpawnModal.test.tsx +190 -0
  140. package/src/components/SpawnModal.tsx +1001 -0
  141. package/src/components/TaskAssignmentUI.tsx +375 -0
  142. package/src/components/TerminalProviderSetup.tsx +517 -0
  143. package/src/components/ThemeProvider.tsx +159 -0
  144. package/src/components/ThinkingIndicator.tsx +231 -0
  145. package/src/components/ThreadList.tsx +198 -0
  146. package/src/components/ThreadPanel.tsx +405 -0
  147. package/src/components/TrajectoryViewer.tsx +698 -0
  148. package/src/components/TypingIndicator.tsx +69 -0
  149. package/src/components/UsageBanner.tsx +231 -0
  150. package/src/components/UserProfilePanel.tsx +233 -0
  151. package/src/components/WorkspaceContext.tsx +95 -0
  152. package/src/components/WorkspaceSelector.tsx +234 -0
  153. package/src/components/WorkspaceStatusIndicator.tsx +396 -0
  154. package/src/components/XTermInteractive.tsx +516 -0
  155. package/src/components/XTermLogViewer.tsx +719 -0
  156. package/src/components/channels/ChannelDialogs.tsx +1411 -0
  157. package/src/components/channels/ChannelHeader.tsx +317 -0
  158. package/src/components/channels/ChannelMessageList.tsx +463 -0
  159. package/src/components/channels/ChannelViewV1.tsx +146 -0
  160. package/src/components/channels/MessageInput.tsx +302 -0
  161. package/src/components/channels/SearchInput.tsx +172 -0
  162. package/src/components/channels/SearchResults.tsx +336 -0
  163. package/src/components/channels/api.test.ts +1527 -0
  164. package/src/components/channels/api.ts +703 -0
  165. package/src/components/channels/index.ts +76 -0
  166. package/src/components/channels/mockApi.ts +344 -0
  167. package/src/components/channels/types.ts +566 -0
  168. package/src/components/hooks/index.ts +58 -0
  169. package/src/components/hooks/useAgentLogs.ts +504 -0
  170. package/src/components/hooks/useAgents.ts +127 -0
  171. package/src/components/hooks/useBroadcastDedup.test.ts +371 -0
  172. package/src/components/hooks/useBroadcastDedup.ts +86 -0
  173. package/src/components/hooks/useChannelAdmin.ts +329 -0
  174. package/src/components/hooks/useChannelBrowser.ts +239 -0
  175. package/src/components/hooks/useChannelCommands.ts +138 -0
  176. package/src/components/hooks/useChannels.ts +367 -0
  177. package/src/components/hooks/useDebounce.ts +29 -0
  178. package/src/components/hooks/useDirectMessage.test.ts +952 -0
  179. package/src/components/hooks/useDirectMessage.ts +141 -0
  180. package/src/components/hooks/useMessages.ts +310 -0
  181. package/src/components/hooks/useOrchestrator.test.ts +165 -0
  182. package/src/components/hooks/useOrchestrator.ts +424 -0
  183. package/src/components/hooks/usePinnedAgents.test.ts +356 -0
  184. package/src/components/hooks/usePinnedAgents.ts +140 -0
  185. package/src/components/hooks/usePresence.test.ts +245 -0
  186. package/src/components/hooks/usePresence.ts +377 -0
  187. package/src/components/hooks/useRecentRepos.ts +130 -0
  188. package/src/components/hooks/useSession.ts +209 -0
  189. package/src/components/hooks/useThread.ts +138 -0
  190. package/src/components/hooks/useTrajectory.ts +265 -0
  191. package/src/components/hooks/useWebSocket.ts +290 -0
  192. package/src/components/hooks/useWorkspaceMembers.ts +132 -0
  193. package/src/components/hooks/useWorkspaceRepos.ts +73 -0
  194. package/src/components/hooks/useWorkspaceStatus.ts +237 -0
  195. package/src/components/index.ts +81 -0
  196. package/src/components/layout/Header.tsx +311 -0
  197. package/src/components/layout/RepoContextHeader.tsx +361 -0
  198. package/src/components/layout/Sidebar.archive.test.tsx +126 -0
  199. package/src/components/layout/Sidebar.test.tsx +691 -0
  200. package/src/components/layout/Sidebar.tsx +900 -0
  201. package/src/components/layout/index.ts +7 -0
  202. package/src/components/settings/BillingSettingsPanel.tsx +564 -0
  203. package/src/components/settings/SettingsPage.tsx +683 -0
  204. package/src/components/settings/TeamSettingsPanel.tsx +560 -0
  205. package/src/components/settings/WorkspaceSettingsPanel.tsx +1368 -0
  206. package/src/components/settings/index.ts +11 -0
  207. package/src/components/settings/types.ts +79 -0
  208. package/src/components/utils/messageFormatting.test.tsx +331 -0
  209. package/src/components/utils/messageFormatting.tsx +597 -0
  210. package/src/index.ts +63 -0
  211. package/src/landing/AboutPage.tsx +77 -0
  212. package/src/landing/BlogContent.tsx +187 -0
  213. package/src/landing/BlogPage.tsx +47 -0
  214. package/src/landing/CareersPage.tsx +53 -0
  215. package/src/landing/ChangelogPage.tsx +33 -0
  216. package/src/landing/ContactPage.tsx +41 -0
  217. package/src/landing/DocsPage.tsx +43 -0
  218. package/src/landing/LandingPage.tsx +702 -0
  219. package/src/landing/PricingPage.tsx +549 -0
  220. package/src/landing/PrivacyPage.tsx +117 -0
  221. package/src/landing/SecurityPage.tsx +42 -0
  222. package/src/landing/StaticPage.tsx +165 -0
  223. package/src/landing/TermsPage.tsx +125 -0
  224. package/src/landing/blogData.ts +312 -0
  225. package/src/landing/index.ts +18 -0
  226. package/src/landing/styles.css +3673 -0
  227. package/src/lib/agent-merge.test.ts +43 -0
  228. package/src/lib/agent-merge.ts +35 -0
  229. package/src/lib/api.ts +1294 -0
  230. package/src/lib/cloudApi.ts +893 -0
  231. package/src/lib/colors.test.ts +175 -0
  232. package/src/lib/colors.ts +218 -0
  233. package/src/lib/config.ts +109 -0
  234. package/src/lib/hierarchy.ts +242 -0
  235. package/src/lib/stuckDetection.ts +142 -0
  236. package/src/lib/useUrlRouting.ts +190 -0
  237. package/src/types/index.ts +317 -0
  238. package/src/types/threading.ts +7 -0
  239. package/out/_next/static/chunks/285-dc644487a8d6500d.js +0 -1
  240. package/out/_next/static/css/4c58d9cf493aa626.css +0 -1
  241. /package/out/_next/static/{AqelRhy1vr2nBUcU0Iqcp → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
  242. /package/out/_next/static/{AqelRhy1vr2nBUcU0Iqcp → IxfA6RZu4trcsEMYlkQra}/_ssgManifest.js +0 -0
  243. /package/out/_next/static/chunks/{528-d375bc8b46912d2c.js → 528-f5f676996d613c25.js} +0 -0
  244. /package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/{page-a58308f43557b908.js → page-b194f207fbd91862.js} +0 -0
@@ -0,0 +1,690 @@
1
+ /**
2
+ * BroadcastComposer Component
3
+ *
4
+ * Enhanced message composer for fleet-wide broadcasts,
5
+ * with server/agent targeting and message templates.
6
+ */
7
+
8
+ import React, { useState, useCallback, useMemo } from 'react';
9
+ import type { Agent } from '../types';
10
+ import type { ServerInfo } from './ServerCard';
11
+ import { getAgentColor, getAgentInitials } from '../lib/colors';
12
+
13
+ export interface BroadcastTarget {
14
+ type: 'all' | 'server' | 'agents';
15
+ serverIds?: string[];
16
+ agentNames?: string[];
17
+ }
18
+
19
+ export interface BroadcastComposerProps {
20
+ servers: ServerInfo[];
21
+ agents: Agent[];
22
+ onSend: (message: string, target: BroadcastTarget) => Promise<boolean>;
23
+ isSending?: boolean;
24
+ error?: string | null;
25
+ }
26
+
27
+ const MESSAGE_TEMPLATES = [
28
+ { id: 'status', label: 'Status Request', message: 'STATUS: Please report your current status and progress.' },
29
+ { id: 'sync', label: 'Sync Check', message: 'SYNC: Checking in - please acknowledge receipt.' },
30
+ { id: 'halt', label: 'Halt Work', message: 'HALT: Please pause current work and await further instructions.' },
31
+ { id: 'resume', label: 'Resume Work', message: 'RESUME: You may continue with your assigned tasks.' },
32
+ ];
33
+
34
+ export function BroadcastComposer({
35
+ servers,
36
+ agents,
37
+ onSend,
38
+ isSending = false,
39
+ error,
40
+ }: BroadcastComposerProps) {
41
+ const [message, setMessage] = useState('');
42
+ const [targetType, setTargetType] = useState<'all' | 'server' | 'agents'>('all');
43
+ const [selectedServers, setSelectedServers] = useState<Set<string>>(new Set());
44
+ const [selectedAgents, setSelectedAgents] = useState<Set<string>>(new Set());
45
+ const [showTemplates, setShowTemplates] = useState(false);
46
+
47
+ // Online servers only
48
+ const onlineServers = useMemo(
49
+ () => servers.filter((s) => s.status === 'online'),
50
+ [servers]
51
+ );
52
+
53
+ // Build target description
54
+ const targetDescription = useMemo(() => {
55
+ if (targetType === 'all') {
56
+ return `All ${agents.length} agents across ${onlineServers.length} servers`;
57
+ }
58
+ if (targetType === 'server') {
59
+ const count = selectedServers.size;
60
+ return count === 0
61
+ ? 'Select servers'
62
+ : `${count} server${count > 1 ? 's' : ''} selected`;
63
+ }
64
+ if (targetType === 'agents') {
65
+ const count = selectedAgents.size;
66
+ return count === 0
67
+ ? 'Select agents'
68
+ : `${count} agent${count > 1 ? 's' : ''} selected`;
69
+ }
70
+ return '';
71
+ }, [targetType, selectedServers, selectedAgents, agents.length, onlineServers.length]);
72
+
73
+ // Handle send
74
+ const handleSend = useCallback(async () => {
75
+ if (!message.trim() || isSending) return;
76
+
77
+ const target: BroadcastTarget = {
78
+ type: targetType,
79
+ serverIds: targetType === 'server' ? Array.from(selectedServers) : undefined,
80
+ agentNames: targetType === 'agents' ? Array.from(selectedAgents) : undefined,
81
+ };
82
+
83
+ const success = await onSend(message, target);
84
+ if (success) {
85
+ setMessage('');
86
+ }
87
+ }, [message, targetType, selectedServers, selectedAgents, onSend, isSending]);
88
+
89
+ // Toggle server selection
90
+ const toggleServer = (serverId: string) => {
91
+ setSelectedServers((prev) => {
92
+ const next = new Set(prev);
93
+ if (next.has(serverId)) {
94
+ next.delete(serverId);
95
+ } else {
96
+ next.add(serverId);
97
+ }
98
+ return next;
99
+ });
100
+ };
101
+
102
+ // Toggle agent selection
103
+ const toggleAgent = (agentName: string) => {
104
+ setSelectedAgents((prev) => {
105
+ const next = new Set(prev);
106
+ if (next.has(agentName)) {
107
+ next.delete(agentName);
108
+ } else {
109
+ next.add(agentName);
110
+ }
111
+ return next;
112
+ });
113
+ };
114
+
115
+ // Apply template
116
+ const applyTemplate = (template: (typeof MESSAGE_TEMPLATES)[0]) => {
117
+ setMessage(template.message);
118
+ setShowTemplates(false);
119
+ };
120
+
121
+ return (
122
+ <div className="broadcast-composer">
123
+ <div className="broadcast-header">
124
+ <BroadcastIcon />
125
+ <span className="broadcast-title">Fleet Broadcast</span>
126
+ <span className="broadcast-target">{targetDescription}</span>
127
+ </div>
128
+
129
+ {/* Target Type Selection */}
130
+ <div className="broadcast-target-types">
131
+ <button
132
+ className={`broadcast-target-btn ${targetType === 'all' ? 'active' : ''}`}
133
+ onClick={() => setTargetType('all')}
134
+ >
135
+ <GlobeIcon />
136
+ All
137
+ </button>
138
+ <button
139
+ className={`broadcast-target-btn ${targetType === 'server' ? 'active' : ''}`}
140
+ onClick={() => setTargetType('server')}
141
+ >
142
+ <ServerIcon />
143
+ By Server
144
+ </button>
145
+ <button
146
+ className={`broadcast-target-btn ${targetType === 'agents' ? 'active' : ''}`}
147
+ onClick={() => setTargetType('agents')}
148
+ >
149
+ <UsersIcon />
150
+ By Agent
151
+ </button>
152
+ </div>
153
+
154
+ {/* Server Selection */}
155
+ {targetType === 'server' && (
156
+ <div className="broadcast-selection">
157
+ <div className="broadcast-selection-label">Select servers:</div>
158
+ <div className="broadcast-selection-items">
159
+ {onlineServers.map((server) => (
160
+ <button
161
+ key={server.id}
162
+ className={`broadcast-selection-item ${
163
+ selectedServers.has(server.id) ? 'selected' : ''
164
+ }`}
165
+ onClick={() => toggleServer(server.id)}
166
+ >
167
+ <span className="broadcast-item-dot" />
168
+ <span>{server.name}</span>
169
+ <span className="broadcast-item-count">{server.agentCount}</span>
170
+ </button>
171
+ ))}
172
+ </div>
173
+ </div>
174
+ )}
175
+
176
+ {/* Agent Selection */}
177
+ {targetType === 'agents' && (
178
+ <div className="broadcast-selection">
179
+ <div className="broadcast-selection-label">Select agents:</div>
180
+ <div className="broadcast-selection-items broadcast-selection-agents">
181
+ {agents.map((agent) => {
182
+ const colors = getAgentColor(agent.name);
183
+ return (
184
+ <button
185
+ key={agent.name}
186
+ className={`broadcast-selection-item ${
187
+ selectedAgents.has(agent.name) ? 'selected' : ''
188
+ }`}
189
+ onClick={() => toggleAgent(agent.name)}
190
+ >
191
+ <div
192
+ className="broadcast-agent-avatar"
193
+ style={{ backgroundColor: colors.primary, color: colors.text }}
194
+ >
195
+ {getAgentInitials(agent.name)}
196
+ </div>
197
+ <span>{agent.name}</span>
198
+ </button>
199
+ );
200
+ })}
201
+ </div>
202
+ </div>
203
+ )}
204
+
205
+ {/* Message Input */}
206
+ <div className="broadcast-input-wrapper">
207
+ <textarea
208
+ className="broadcast-input"
209
+ placeholder="Type your broadcast message..."
210
+ value={message}
211
+ onChange={(e) => setMessage(e.target.value)}
212
+ disabled={isSending}
213
+ rows={3}
214
+ />
215
+
216
+ <div className="broadcast-input-actions">
217
+ <div className="broadcast-templates-wrapper">
218
+ <button
219
+ className="broadcast-template-btn"
220
+ onClick={() => setShowTemplates(!showTemplates)}
221
+ >
222
+ <TemplateIcon />
223
+ <span className="broadcast-template-text">Templates</span>
224
+ </button>
225
+ {showTemplates && (
226
+ <div className="broadcast-templates-menu">
227
+ {MESSAGE_TEMPLATES.map((template) => (
228
+ <button
229
+ key={template.id}
230
+ className="broadcast-template-item"
231
+ onClick={() => applyTemplate(template)}
232
+ >
233
+ <span className="broadcast-template-label">{template.label}</span>
234
+ <span className="broadcast-template-preview">{template.message}</span>
235
+ </button>
236
+ ))}
237
+ </div>
238
+ )}
239
+ </div>
240
+
241
+ <button
242
+ className="broadcast-send-btn"
243
+ onClick={handleSend}
244
+ disabled={!message.trim() || isSending || (targetType !== 'all' &&
245
+ (targetType === 'server' ? selectedServers.size === 0 : selectedAgents.size === 0))}
246
+ >
247
+ {isSending ? <Spinner /> : <SendIcon />}
248
+ <span className="broadcast-send-text">{isSending ? 'Sending...' : 'Broadcast'}</span>
249
+ </button>
250
+ </div>
251
+ </div>
252
+
253
+ {error && (
254
+ <div className="broadcast-error">
255
+ <ErrorIcon />
256
+ <span>{error}</span>
257
+ </div>
258
+ )}
259
+ </div>
260
+ );
261
+ }
262
+
263
+ // Icon components
264
+ function BroadcastIcon() {
265
+ return (
266
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
267
+ <circle cx="12" cy="12" r="2" />
268
+ <path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14" />
269
+ </svg>
270
+ );
271
+ }
272
+
273
+ function GlobeIcon() {
274
+ return (
275
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
276
+ <circle cx="12" cy="12" r="10" />
277
+ <line x1="2" y1="12" x2="22" y2="12" />
278
+ <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
279
+ </svg>
280
+ );
281
+ }
282
+
283
+ function ServerIcon() {
284
+ return (
285
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
286
+ <rect x="2" y="2" width="20" height="8" rx="2" ry="2" />
287
+ <rect x="2" y="14" width="20" height="8" rx="2" ry="2" />
288
+ <line x1="6" y1="6" x2="6.01" y2="6" />
289
+ <line x1="6" y1="18" x2="6.01" y2="18" />
290
+ </svg>
291
+ );
292
+ }
293
+
294
+ function UsersIcon() {
295
+ return (
296
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
297
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
298
+ <circle cx="9" cy="7" r="4" />
299
+ <path d="M23 21v-2a4 4 0 0 0-3-3.87" />
300
+ <path d="M16 3.13a4 4 0 0 1 0 7.75" />
301
+ </svg>
302
+ );
303
+ }
304
+
305
+ function TemplateIcon() {
306
+ return (
307
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
308
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
309
+ <line x1="3" y1="9" x2="21" y2="9" />
310
+ <line x1="9" y1="21" x2="9" y2="9" />
311
+ </svg>
312
+ );
313
+ }
314
+
315
+ function SendIcon() {
316
+ return (
317
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
318
+ <line x1="22" y1="2" x2="11" y2="13" />
319
+ <polygon points="22 2 15 22 11 13 2 9 22 2" />
320
+ </svg>
321
+ );
322
+ }
323
+
324
+ function ErrorIcon() {
325
+ return (
326
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
327
+ <circle cx="12" cy="12" r="10" />
328
+ <line x1="12" y1="8" x2="12" y2="12" />
329
+ <line x1="12" y1="16" x2="12.01" y2="16" />
330
+ </svg>
331
+ );
332
+ }
333
+
334
+ function Spinner() {
335
+ return (
336
+ <svg className="broadcast-spinner" width="14" height="14" viewBox="0 0 24 24">
337
+ <circle
338
+ cx="12"
339
+ cy="12"
340
+ r="10"
341
+ stroke="currentColor"
342
+ strokeWidth="2"
343
+ fill="none"
344
+ strokeDasharray="32"
345
+ strokeLinecap="round"
346
+ />
347
+ </svg>
348
+ );
349
+ }
350
+
351
+ /**
352
+ * CSS styles for the broadcast composer
353
+ */
354
+ export const broadcastComposerStyles = `
355
+ .broadcast-composer {
356
+ background: #ffffff;
357
+ border-radius: 8px;
358
+ border: 1px solid #e8e8e8;
359
+ overflow: hidden;
360
+ }
361
+
362
+ .broadcast-header {
363
+ display: flex;
364
+ align-items: center;
365
+ gap: 8px;
366
+ padding: 12px 16px;
367
+ border-bottom: 1px solid #e8e8e8;
368
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
369
+ color: #ffffff;
370
+ }
371
+
372
+ .broadcast-header svg {
373
+ opacity: 0.9;
374
+ }
375
+
376
+ .broadcast-title {
377
+ font-weight: 600;
378
+ font-size: 14px;
379
+ }
380
+
381
+ .broadcast-target {
382
+ margin-left: auto;
383
+ font-size: 12px;
384
+ opacity: 0.9;
385
+ }
386
+
387
+ .broadcast-target-types {
388
+ display: flex;
389
+ gap: 8px;
390
+ padding: 12px 16px;
391
+ border-bottom: 1px solid #e8e8e8;
392
+ background: #fafafa;
393
+ }
394
+
395
+ .broadcast-target-btn {
396
+ display: flex;
397
+ align-items: center;
398
+ gap: 6px;
399
+ padding: 8px 14px;
400
+ background: #ffffff;
401
+ border: 1px solid #e8e8e8;
402
+ border-radius: 6px;
403
+ font-size: 13px;
404
+ color: #666;
405
+ cursor: pointer;
406
+ font-family: inherit;
407
+ transition: all 0.15s;
408
+ }
409
+
410
+ .broadcast-target-btn:hover {
411
+ border-color: #d0d0d0;
412
+ color: #333;
413
+ }
414
+
415
+ .broadcast-target-btn.active {
416
+ background: #1264a3;
417
+ border-color: #1264a3;
418
+ color: #ffffff;
419
+ }
420
+
421
+ .broadcast-selection {
422
+ padding: 12px 16px;
423
+ border-bottom: 1px solid #e8e8e8;
424
+ }
425
+
426
+ .broadcast-selection-label {
427
+ font-size: 12px;
428
+ font-weight: 500;
429
+ color: #666;
430
+ margin-bottom: 8px;
431
+ }
432
+
433
+ .broadcast-selection-items {
434
+ display: flex;
435
+ flex-wrap: wrap;
436
+ gap: 8px;
437
+ }
438
+
439
+ .broadcast-selection-agents {
440
+ max-height: 120px;
441
+ overflow-y: auto;
442
+ }
443
+
444
+ .broadcast-selection-item {
445
+ display: flex;
446
+ align-items: center;
447
+ gap: 8px;
448
+ padding: 6px 12px;
449
+ background: #fafafa;
450
+ border: 1px solid #e8e8e8;
451
+ border-radius: 6px;
452
+ font-size: 12px;
453
+ color: #333;
454
+ cursor: pointer;
455
+ font-family: inherit;
456
+ transition: all 0.15s;
457
+ }
458
+
459
+ .broadcast-selection-item:hover {
460
+ background: #f0f0f0;
461
+ }
462
+
463
+ .broadcast-selection-item.selected {
464
+ background: #e8f4fd;
465
+ border-color: #1264a3;
466
+ color: #1264a3;
467
+ }
468
+
469
+ .broadcast-item-dot {
470
+ width: 6px;
471
+ height: 6px;
472
+ border-radius: 50%;
473
+ background: #10b981;
474
+ }
475
+
476
+ .broadcast-item-count {
477
+ font-size: 11px;
478
+ color: #888;
479
+ background: #f0f0f0;
480
+ padding: 1px 5px;
481
+ border-radius: 8px;
482
+ }
483
+
484
+ .broadcast-agent-avatar {
485
+ width: 20px;
486
+ height: 20px;
487
+ border-radius: 4px;
488
+ display: flex;
489
+ align-items: center;
490
+ justify-content: center;
491
+ font-size: 8px;
492
+ font-weight: 600;
493
+ }
494
+
495
+ .broadcast-input-wrapper {
496
+ padding: 16px;
497
+ }
498
+
499
+ .broadcast-input {
500
+ width: 100%;
501
+ padding: 12px;
502
+ border: 1px solid #e8e8e8;
503
+ border-radius: 6px;
504
+ font-size: 14px;
505
+ font-family: inherit;
506
+ resize: vertical;
507
+ min-height: 80px;
508
+ outline: none;
509
+ transition: border-color 0.15s;
510
+ }
511
+
512
+ .broadcast-input:focus {
513
+ border-color: #1264a3;
514
+ }
515
+
516
+ .broadcast-input:disabled {
517
+ background: #fafafa;
518
+ color: #888;
519
+ }
520
+
521
+ .broadcast-input-actions {
522
+ display: flex;
523
+ align-items: center;
524
+ justify-content: space-between;
525
+ margin-top: 12px;
526
+ }
527
+
528
+ .broadcast-templates-wrapper {
529
+ position: relative;
530
+ }
531
+
532
+ .broadcast-template-btn {
533
+ display: flex;
534
+ align-items: center;
535
+ gap: 6px;
536
+ padding: 8px 12px;
537
+ background: #fafafa;
538
+ border: 1px solid #e8e8e8;
539
+ border-radius: 6px;
540
+ font-size: 12px;
541
+ color: #666;
542
+ cursor: pointer;
543
+ font-family: inherit;
544
+ transition: all 0.15s;
545
+ }
546
+
547
+ .broadcast-template-btn:hover {
548
+ background: #f0f0f0;
549
+ color: #333;
550
+ }
551
+
552
+ .broadcast-templates-menu {
553
+ position: absolute;
554
+ bottom: 100%;
555
+ left: 0;
556
+ width: 300px;
557
+ margin-bottom: 4px;
558
+ background: #ffffff;
559
+ border: 1px solid #e8e8e8;
560
+ border-radius: 8px;
561
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
562
+ overflow: hidden;
563
+ z-index: 10;
564
+ }
565
+
566
+ .broadcast-template-item {
567
+ display: flex;
568
+ flex-direction: column;
569
+ align-items: flex-start;
570
+ width: 100%;
571
+ padding: 10px 12px;
572
+ background: transparent;
573
+ border: none;
574
+ border-bottom: 1px solid #f0f0f0;
575
+ cursor: pointer;
576
+ font-family: inherit;
577
+ text-align: left;
578
+ transition: background 0.15s;
579
+ }
580
+
581
+ .broadcast-template-item:last-child {
582
+ border-bottom: none;
583
+ }
584
+
585
+ .broadcast-template-item:hover {
586
+ background: #f9f9f9;
587
+ }
588
+
589
+ .broadcast-template-label {
590
+ font-size: 13px;
591
+ font-weight: 500;
592
+ color: #333;
593
+ }
594
+
595
+ .broadcast-template-preview {
596
+ font-size: 11px;
597
+ color: #888;
598
+ margin-top: 2px;
599
+ white-space: nowrap;
600
+ overflow: hidden;
601
+ text-overflow: ellipsis;
602
+ max-width: 100%;
603
+ }
604
+
605
+ .broadcast-send-btn {
606
+ display: flex;
607
+ align-items: center;
608
+ gap: 6px;
609
+ padding: 10px 20px;
610
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
611
+ border: none;
612
+ border-radius: 6px;
613
+ font-size: 13px;
614
+ font-weight: 500;
615
+ color: #ffffff;
616
+ cursor: pointer;
617
+ font-family: inherit;
618
+ transition: all 0.15s;
619
+ }
620
+
621
+ .broadcast-send-btn:hover:not(:disabled) {
622
+ opacity: 0.9;
623
+ transform: translateY(-1px);
624
+ }
625
+
626
+ .broadcast-send-btn:disabled {
627
+ opacity: 0.5;
628
+ cursor: not-allowed;
629
+ transform: none;
630
+ }
631
+
632
+ .broadcast-spinner {
633
+ animation: spin 1s linear infinite;
634
+ }
635
+
636
+ @keyframes spin {
637
+ from { transform: rotate(0deg); }
638
+ to { transform: rotate(360deg); }
639
+ }
640
+
641
+ .broadcast-error {
642
+ display: flex;
643
+ align-items: center;
644
+ gap: 8px;
645
+ padding: 12px 16px;
646
+ background: #fef2f2;
647
+ border-top: 1px solid #fecaca;
648
+ color: #dc2626;
649
+ font-size: 13px;
650
+ }
651
+
652
+ /* Responsive styles */
653
+ @media (max-width: 768px) {
654
+ .broadcast-input-actions {
655
+ gap: 8px;
656
+ }
657
+
658
+ .broadcast-send-btn {
659
+ padding: 8px 12px;
660
+ font-size: 12px;
661
+ }
662
+
663
+ .broadcast-template-btn {
664
+ padding: 6px 10px;
665
+ }
666
+ }
667
+
668
+ @media (max-width: 480px) {
669
+ .broadcast-input-wrapper {
670
+ padding: 12px;
671
+ }
672
+
673
+ .broadcast-send-btn {
674
+ padding: 8px;
675
+ min-width: auto;
676
+ }
677
+
678
+ .broadcast-send-text {
679
+ display: none;
680
+ }
681
+
682
+ .broadcast-template-text {
683
+ display: none;
684
+ }
685
+
686
+ .broadcast-template-btn {
687
+ padding: 8px;
688
+ }
689
+ }
690
+ `;