@agent-relay/dashboard 2.0.81 → 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/{dYlczDQI12PIQ3tqq3N4Y → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
  242. /package/out/_next/static/{dYlczDQI12PIQ3tqq3N4Y → 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,597 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter';
3
+ import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
4
+ // Only import languages we actually need (saves ~300KB)
5
+ import javascript from 'react-syntax-highlighter/dist/esm/languages/prism/javascript';
6
+ import typescript from 'react-syntax-highlighter/dist/esm/languages/prism/typescript';
7
+ import python from 'react-syntax-highlighter/dist/esm/languages/prism/python';
8
+ import bash from 'react-syntax-highlighter/dist/esm/languages/prism/bash';
9
+ import json from 'react-syntax-highlighter/dist/esm/languages/prism/json';
10
+ import markdown from 'react-syntax-highlighter/dist/esm/languages/prism/markdown';
11
+ import yaml from 'react-syntax-highlighter/dist/esm/languages/prism/yaml';
12
+ import css from 'react-syntax-highlighter/dist/esm/languages/prism/css';
13
+ import go from 'react-syntax-highlighter/dist/esm/languages/prism/go';
14
+ import rust from 'react-syntax-highlighter/dist/esm/languages/prism/rust';
15
+ import sql from 'react-syntax-highlighter/dist/esm/languages/prism/sql';
16
+ import ruby from 'react-syntax-highlighter/dist/esm/languages/prism/ruby';
17
+ import java from 'react-syntax-highlighter/dist/esm/languages/prism/java';
18
+ import docker from 'react-syntax-highlighter/dist/esm/languages/prism/docker';
19
+
20
+ SyntaxHighlighter.registerLanguage('javascript', javascript);
21
+ SyntaxHighlighter.registerLanguage('typescript', typescript);
22
+ SyntaxHighlighter.registerLanguage('python', python);
23
+ SyntaxHighlighter.registerLanguage('bash', bash);
24
+ SyntaxHighlighter.registerLanguage('shell', bash);
25
+ SyntaxHighlighter.registerLanguage('json', json);
26
+ SyntaxHighlighter.registerLanguage('markdown', markdown);
27
+ SyntaxHighlighter.registerLanguage('yaml', yaml);
28
+ SyntaxHighlighter.registerLanguage('css', css);
29
+ SyntaxHighlighter.registerLanguage('go', go);
30
+ SyntaxHighlighter.registerLanguage('rust', rust);
31
+ SyntaxHighlighter.registerLanguage('sql', sql);
32
+ SyntaxHighlighter.registerLanguage('ruby', ruby);
33
+ SyntaxHighlighter.registerLanguage('java', java);
34
+ SyntaxHighlighter.registerLanguage('docker', docker);
35
+ SyntaxHighlighter.registerLanguage('dockerfile', docker);
36
+
37
+ /**
38
+ * Custom theme extending oneDark to match dashboard styling
39
+ */
40
+ const customCodeTheme = {
41
+ ...oneDark,
42
+ 'pre[class*="language-"]': {
43
+ ...oneDark['pre[class*="language-"]'],
44
+ background: 'rgba(15, 23, 42, 0.8)',
45
+ margin: '0.5rem 0',
46
+ padding: '1rem',
47
+ borderRadius: '0.5rem',
48
+ border: '1px solid rgba(148, 163, 184, 0.1)',
49
+ fontSize: '0.75rem',
50
+ lineHeight: '1.5',
51
+ },
52
+ 'code[class*="language-"]': {
53
+ ...oneDark['code[class*="language-"]'],
54
+ background: 'transparent',
55
+ fontSize: '0.75rem',
56
+ },
57
+ };
58
+
59
+ /**
60
+ * CodeBlock Component - Renders syntax highlighted code
61
+ */
62
+ interface CodeBlockProps {
63
+ code: string;
64
+ language: string;
65
+ }
66
+
67
+ function CodeBlock({ code, language }: CodeBlockProps) {
68
+ const [copied, setCopied] = useState(false);
69
+
70
+ const handleCopy = useCallback(async () => {
71
+ try {
72
+ await navigator.clipboard.writeText(code);
73
+ setCopied(true);
74
+ setTimeout(() => setCopied(false), 2000);
75
+ } catch (err) {
76
+ console.error('Failed to copy:', err);
77
+ }
78
+ }, [code]);
79
+
80
+ // Normalize language names for syntax highlighter
81
+ const normalizedLanguage = language.toLowerCase().replace(/^(js|jsx)$/, 'javascript')
82
+ .replace(/^(ts|tsx)$/, 'typescript')
83
+ .replace(/^(py)$/, 'python')
84
+ .replace(/^(rb)$/, 'ruby')
85
+ .replace(/^(sh|shell|zsh)$/, 'bash');
86
+
87
+ return (
88
+ <div className="relative group my-2">
89
+ {/* Language badge and copy button */}
90
+ <div className="absolute top-2 right-2 flex items-center gap-2 z-10">
91
+ {language && language !== 'text' && (
92
+ <span className="text-xs px-2 py-0.5 rounded bg-accent-cyan/20 text-accent-cyan font-mono">
93
+ {language}
94
+ </span>
95
+ )}
96
+ <button
97
+ onClick={handleCopy}
98
+ className="opacity-0 group-hover:opacity-100 transition-opacity text-xs px-2 py-1 rounded bg-bg-tertiary hover:bg-bg-card text-text-muted hover:text-text-primary border border-border-subtle"
99
+ title="Copy code"
100
+ >
101
+ {copied ? '✓ Copied' : 'Copy'}
102
+ </button>
103
+ </div>
104
+ <SyntaxHighlighter
105
+ language={normalizedLanguage}
106
+ style={customCodeTheme}
107
+ customStyle={{
108
+ margin: 0,
109
+ background: 'rgba(15, 23, 42, 0.8)',
110
+ }}
111
+ showLineNumbers={code.split('\n').length > 3}
112
+ lineNumberStyle={{
113
+ minWidth: '2.5em',
114
+ paddingRight: '1em',
115
+ color: 'rgba(148, 163, 184, 0.4)',
116
+ userSelect: 'none',
117
+ }}
118
+ >
119
+ {code.trim()}
120
+ </SyntaxHighlighter>
121
+ </div>
122
+ );
123
+ }
124
+
125
+ /**
126
+ * Check if a line looks like part of a table (has pipe characters)
127
+ */
128
+ function isTableLine(line: string): boolean {
129
+ const pipeCount = (line.match(/\|/g) || []).length;
130
+ return pipeCount >= 2 || (line.trim().startsWith('|') && line.trim().endsWith('|'));
131
+ }
132
+
133
+ /**
134
+ * Check if a line is a table separator (dashes and pipes)
135
+ */
136
+ function isTableSeparator(line: string): boolean {
137
+ return /^[\s|:-]+$/.test(line) && line.includes('-') && line.includes('|');
138
+ }
139
+
140
+ /**
141
+ * Check if a line is a blockquote (starts with >)
142
+ */
143
+ function isQuoteLine(line: string): boolean {
144
+ return line.trimStart().startsWith('>');
145
+ }
146
+
147
+ /**
148
+ * Extract the content from a quote line (remove the leading > and space)
149
+ */
150
+ function getQuoteContent(line: string): string {
151
+ const trimmed = line.trimStart();
152
+ if (trimmed.startsWith('> ')) {
153
+ return trimmed.slice(2);
154
+ }
155
+ if (trimmed.startsWith('>')) {
156
+ return trimmed.slice(1);
157
+ }
158
+ return line;
159
+ }
160
+
161
+ interface ContentSection {
162
+ type: 'text' | 'table' | 'code' | 'quote';
163
+ content: string;
164
+ language?: string;
165
+ }
166
+
167
+ /**
168
+ * Split content into text, table, and code sections
169
+ * Code blocks are detected by fenced code block syntax (```language ... ```)
170
+ */
171
+ function splitContentSections(content: string): ContentSection[] {
172
+ const sections: ContentSection[] = [];
173
+
174
+ // First, extract code blocks using regex
175
+ // Matches ```language\ncode\n``` or ```\ncode\n```
176
+ const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
177
+ let lastIndex = 0;
178
+ let match;
179
+
180
+ while ((match = codeBlockRegex.exec(content)) !== null) {
181
+ // Add any content before this code block
182
+ if (match.index > lastIndex) {
183
+ const beforeContent = content.slice(lastIndex, match.index);
184
+ const beforeSections = splitTextAndTableSections(beforeContent);
185
+ sections.push(...beforeSections);
186
+ }
187
+
188
+ // Add the code block
189
+ sections.push({
190
+ type: 'code',
191
+ language: match[1] || 'text',
192
+ content: match[2],
193
+ });
194
+
195
+ lastIndex = match.index + match[0].length;
196
+ }
197
+
198
+ // Add any remaining content after the last code block
199
+ if (lastIndex < content.length) {
200
+ const afterContent = content.slice(lastIndex);
201
+ const afterSections = splitTextAndTableSections(afterContent);
202
+ sections.push(...afterSections);
203
+ }
204
+
205
+ // If no code blocks were found, just split text/tables
206
+ if (sections.length === 0) {
207
+ return splitTextAndTableSections(content);
208
+ }
209
+
210
+ return sections;
211
+ }
212
+
213
+ /**
214
+ * Split content into text, table, and quote sections (helper for non-code content)
215
+ */
216
+ function splitTextAndTableSections(content: string): ContentSection[] {
217
+ const lines = content.split('\n');
218
+ const sections: ContentSection[] = [];
219
+ let currentSection: ContentSection | null = null;
220
+
221
+ for (const line of lines) {
222
+ let sectionType: 'text' | 'table' | 'quote';
223
+ let lineContent = line;
224
+
225
+ if (isQuoteLine(line)) {
226
+ sectionType = 'quote';
227
+ lineContent = getQuoteContent(line);
228
+ } else if (isTableLine(line) || isTableSeparator(line)) {
229
+ sectionType = 'table';
230
+ } else {
231
+ sectionType = 'text';
232
+ }
233
+
234
+ if (!currentSection || currentSection.type !== sectionType) {
235
+ if (currentSection) {
236
+ sections.push(currentSection);
237
+ }
238
+ currentSection = { type: sectionType, content: lineContent };
239
+ } else {
240
+ currentSection.content += '\n' + lineContent;
241
+ }
242
+ }
243
+
244
+ if (currentSection) {
245
+ sections.push(currentSection);
246
+ }
247
+
248
+ return sections;
249
+ }
250
+
251
+ export interface FormatMessageOptions {
252
+ mentions?: string[];
253
+ }
254
+
255
+ /**
256
+ * Format message body with newline preservation, link detection, table, and code support
257
+ */
258
+ export function formatMessageBody(content: string, options: FormatMessageOptions = {}): React.ReactNode {
259
+ const normalizedContent = content
260
+ .replace(/\\n/g, '\n')
261
+ .replace(/\r\n/g, '\n')
262
+ .replace(/\r/g, '\n');
263
+
264
+ const sections = splitContentSections(normalizedContent);
265
+
266
+ // If only one section and not a table, use simple rendering
267
+ if (sections.length === 1 && sections[0].type === 'text') {
268
+ const lines = normalizedContent.split('\n');
269
+ return lines.map((line, i) => (
270
+ <React.Fragment key={i}>
271
+ {i > 0 && <br />}
272
+ {formatLine(line, options.mentions)}
273
+ </React.Fragment>
274
+ ));
275
+ }
276
+
277
+ // Render mixed content with tables, code blocks, and quotes
278
+ return sections.map((section, sectionIndex) => {
279
+ if (section.type === 'code') {
280
+ return (
281
+ <CodeBlock
282
+ key={sectionIndex}
283
+ code={section.content}
284
+ language={section.language || 'text'}
285
+ />
286
+ );
287
+ }
288
+
289
+ if (section.type === 'table') {
290
+ return (
291
+ <pre
292
+ key={sectionIndex}
293
+ className="font-mono text-xs leading-relaxed whitespace-pre overflow-x-auto my-2 p-3 bg-bg-tertiary/50 rounded-lg border border-border-subtle"
294
+ >
295
+ {section.content}
296
+ </pre>
297
+ );
298
+ }
299
+
300
+ if (section.type === 'quote') {
301
+ const lines = section.content.split('\n');
302
+ return (
303
+ <blockquote
304
+ key={sectionIndex}
305
+ className="my-2 pl-3 py-1 border-l-2 border-accent-cyan/50 bg-bg-tertiary/30 rounded-r text-text-secondary italic"
306
+ >
307
+ {lines.map((line, i) => (
308
+ <React.Fragment key={i}>
309
+ {i > 0 && <br />}
310
+ {formatLine(line, options.mentions)}
311
+ </React.Fragment>
312
+ ))}
313
+ </blockquote>
314
+ );
315
+ }
316
+
317
+ // Regular text section
318
+ const lines = section.content.split('\n');
319
+ return (
320
+ <span key={sectionIndex}>
321
+ {lines.map((line, i) => (
322
+ <React.Fragment key={i}>
323
+ {i > 0 && <br />}
324
+ {formatLine(line, options.mentions)}
325
+ </React.Fragment>
326
+ ))}
327
+ </span>
328
+ );
329
+ });
330
+ }
331
+
332
+ /**
333
+ * Check if a line is a heading and return the level (1-6) or 0 if not
334
+ */
335
+ function getHeadingLevel(line: string): number {
336
+ const match = line.match(/^(#{1,6})\s+/);
337
+ return match ? match[1].length : 0;
338
+ }
339
+
340
+ /**
341
+ * Check if a line is a list item (bullet or numbered)
342
+ */
343
+ function getListInfo(line: string): { type: 'bullet' | 'numbered' | null; content: string; indent: number } {
344
+ const bulletMatch = line.match(/^(\s*)([-*])\s+(.*)$/);
345
+ if (bulletMatch) {
346
+ return { type: 'bullet', content: bulletMatch[3], indent: bulletMatch[1].length };
347
+ }
348
+ const numberedMatch = line.match(/^(\s*)(\d+\.)\s+(.*)$/);
349
+ if (numberedMatch) {
350
+ return { type: 'numbered', content: numberedMatch[3], indent: numberedMatch[1].length };
351
+ }
352
+ return { type: null, content: line, indent: 0 };
353
+ }
354
+
355
+ /**
356
+ * Format inline markdown elements (bold, italic, strikethrough, links, code, URLs)
357
+ * Processes patterns sequentially to avoid regex complexity issues
358
+ */
359
+ function formatInlineMarkdown(text: string, mentions?: string[], keyPrefix: string = ''): React.ReactNode {
360
+ const parts: React.ReactNode[] = [];
361
+ let remaining = text;
362
+ let partIndex = 0;
363
+
364
+ // Define patterns in order of precedence (most specific first)
365
+ const patterns: Array<{
366
+ regex: RegExp;
367
+ render: (match: RegExpMatchArray, key: string) => React.ReactNode;
368
+ }> = [
369
+ // Inline code `code` - process first to protect content
370
+ {
371
+ regex: /`([^`]+)`/,
372
+ render: (match, key) => (
373
+ <code
374
+ key={key}
375
+ className="px-1.5 py-0.5 mx-0.5 rounded bg-bg-elevated/80 text-accent-cyan font-mono text-[0.85em] border border-border-subtle/50"
376
+ >
377
+ {match[1]}
378
+ </code>
379
+ ),
380
+ },
381
+ // Markdown link [text](url)
382
+ {
383
+ regex: /\[([^\]]+)\]\(([^)]+)\)/,
384
+ render: (match, key) => (
385
+ <a
386
+ key={key}
387
+ href={match[2]}
388
+ target="_blank"
389
+ rel="noopener noreferrer"
390
+ className="text-accent-cyan no-underline hover:underline"
391
+ >
392
+ {match[1]}
393
+ </a>
394
+ ),
395
+ },
396
+ // Bold **text** (must come before italic *)
397
+ {
398
+ regex: /\*\*([^*]+)\*\*/,
399
+ render: (match, key) => (
400
+ <strong key={key} className="font-semibold text-text-primary">
401
+ {formatInlineMarkdown(match[1], mentions, `${key}-inner`)}
402
+ </strong>
403
+ ),
404
+ },
405
+ // Bold __text__
406
+ {
407
+ regex: /__([^_]+)__/,
408
+ render: (match, key) => (
409
+ <strong key={key} className="font-semibold text-text-primary">
410
+ {formatInlineMarkdown(match[1], mentions, `${key}-inner`)}
411
+ </strong>
412
+ ),
413
+ },
414
+ // Italic _text_ (using underscore to avoid conflict with bold **)
415
+ {
416
+ regex: /_([^_]+)_/,
417
+ render: (match, key) => (
418
+ <em key={key} className="italic">
419
+ {formatInlineMarkdown(match[1], mentions, `${key}-inner`)}
420
+ </em>
421
+ ),
422
+ },
423
+ // Italic *text* (single asterisk - bold ** is matched first due to ordering)
424
+ {
425
+ regex: /\*([^*]+)\*/,
426
+ render: (match, key) => (
427
+ <em key={key} className="italic">
428
+ {formatInlineMarkdown(match[1], mentions, `${key}-inner`)}
429
+ </em>
430
+ ),
431
+ },
432
+ // Strikethrough ~~text~~
433
+ {
434
+ regex: /~~([^~]+)~~/,
435
+ render: (match, key) => (
436
+ <del key={key} className="line-through text-text-muted">
437
+ {match[1]}
438
+ </del>
439
+ ),
440
+ },
441
+ // URL
442
+ {
443
+ regex: /https?:\/\/[^\s]+/,
444
+ render: (match, key) => (
445
+ <a
446
+ key={key}
447
+ href={match[0]}
448
+ target="_blank"
449
+ rel="noopener noreferrer"
450
+ className="text-accent-cyan no-underline hover:underline"
451
+ >
452
+ {match[0]}
453
+ </a>
454
+ ),
455
+ },
456
+ ];
457
+
458
+ while (remaining.length > 0) {
459
+ // Find the earliest match among all patterns
460
+ let earliestMatch: { pattern: typeof patterns[0]; match: RegExpMatchArray; index: number } | null = null;
461
+
462
+ for (const pattern of patterns) {
463
+ const match = remaining.match(pattern.regex);
464
+ if (match && match.index !== undefined) {
465
+ if (!earliestMatch || match.index < earliestMatch.index) {
466
+ earliestMatch = { pattern, match, index: match.index };
467
+ }
468
+ }
469
+ }
470
+
471
+ if (!earliestMatch) {
472
+ // No more matches, add remaining text
473
+ parts.push(highlightMentions(remaining, mentions, `${keyPrefix}-text-${partIndex}`));
474
+ break;
475
+ }
476
+
477
+ // Add text before the match
478
+ if (earliestMatch.index > 0) {
479
+ const textBefore = remaining.slice(0, earliestMatch.index);
480
+ parts.push(highlightMentions(textBefore, mentions, `${keyPrefix}-pre-${partIndex}`));
481
+ partIndex++;
482
+ }
483
+
484
+ // Render the matched element
485
+ parts.push(earliestMatch.pattern.render(earliestMatch.match, `${keyPrefix}-el-${partIndex}`));
486
+ partIndex++;
487
+
488
+ // Move past the match
489
+ remaining = remaining.slice(earliestMatch.index + earliestMatch.match[0].length);
490
+ }
491
+
492
+ return parts.length === 1 ? parts[0] : parts;
493
+ }
494
+
495
+ /**
496
+ * Format a single line, detecting headings, lists, and inline markdown
497
+ */
498
+ function formatLine(line: string, mentions?: string[]): React.ReactNode {
499
+ // Check for headings
500
+ const headingLevel = getHeadingLevel(line);
501
+ if (headingLevel > 0) {
502
+ const content = line.replace(/^#{1,6}\s+/, '');
503
+ // Use inline styles for font-size to override parent's text-sm
504
+ const headingStyles: Record<number, { className: string; style: React.CSSProperties }> = {
505
+ 1: {
506
+ className: 'font-bold text-text-primary mt-4 mb-2 pb-1 border-b border-border-subtle',
507
+ style: { fontSize: '1.5rem', lineHeight: '2rem' }, // 24px
508
+ },
509
+ 2: {
510
+ className: 'font-bold text-text-primary mt-3 mb-2',
511
+ style: { fontSize: '1.25rem', lineHeight: '1.75rem' }, // 20px
512
+ },
513
+ 3: {
514
+ className: 'font-semibold text-text-primary mt-2.5 mb-1.5',
515
+ style: { fontSize: '1.125rem', lineHeight: '1.5rem' }, // 18px
516
+ },
517
+ 4: {
518
+ className: 'font-semibold text-text-primary mt-2 mb-1',
519
+ style: { fontSize: '1rem', lineHeight: '1.5rem' }, // 16px
520
+ },
521
+ 5: {
522
+ className: 'font-medium text-text-secondary mt-1.5 mb-1',
523
+ style: { fontSize: '0.875rem', lineHeight: '1.25rem' }, // 14px
524
+ },
525
+ 6: {
526
+ className: 'font-medium text-text-muted mt-1 mb-0.5 uppercase tracking-wide',
527
+ style: { fontSize: '0.75rem', lineHeight: '1rem' }, // 12px
528
+ },
529
+ };
530
+ const { className, style } = headingStyles[headingLevel] || headingStyles[3];
531
+ return (
532
+ <div className={className} style={style}>
533
+ {formatInlineMarkdown(content, mentions, 'heading')}
534
+ </div>
535
+ );
536
+ }
537
+
538
+ // Check for list items
539
+ const listInfo = getListInfo(line);
540
+ if (listInfo.type) {
541
+ const indentStyle = { marginLeft: `${listInfo.indent * 0.5 + 0.5}rem` };
542
+ return (
543
+ <div className="flex items-start gap-2 my-0.5" style={indentStyle}>
544
+ <span className="text-accent-cyan flex-shrink-0 w-4 text-center">
545
+ {listInfo.type === 'bullet' ? '•' : line.match(/^\s*(\d+\.)/)?.[1]}
546
+ </span>
547
+ <span>{formatInlineMarkdown(listInfo.content, mentions, 'list')}</span>
548
+ </div>
549
+ );
550
+ }
551
+
552
+ // Regular line with inline formatting
553
+ return formatInlineMarkdown(line, mentions, 'line');
554
+ }
555
+
556
+ function escapeRegExp(value: string): string {
557
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
558
+ }
559
+
560
+ function highlightMentions(text: string, mentions: string[] | undefined, keyPrefix: string): React.ReactNode {
561
+ if (!mentions || mentions.length === 0) {
562
+ return text;
563
+ }
564
+
565
+ const escapedMentions = mentions.map(escapeRegExp).filter(Boolean);
566
+ if (escapedMentions.length === 0) {
567
+ return text;
568
+ }
569
+
570
+ const pattern = new RegExp(`@(${escapedMentions.join('|')})\\b`, 'g');
571
+ const nodes: React.ReactNode[] = [];
572
+ let lastIndex = 0;
573
+ let match;
574
+
575
+ while ((match = pattern.exec(text)) !== null) {
576
+ if (match.index > lastIndex) {
577
+ nodes.push(text.slice(lastIndex, match.index));
578
+ }
579
+
580
+ nodes.push(
581
+ <span
582
+ key={`${keyPrefix}-mention-${match.index}`}
583
+ className="px-1 py-0.5 bg-accent-cyan/20 text-accent-cyan rounded"
584
+ >
585
+ @{match[1]}
586
+ </span>
587
+ );
588
+
589
+ lastIndex = match.index + match[0].length;
590
+ }
591
+
592
+ if (lastIndex < text.length) {
593
+ nodes.push(text.slice(lastIndex));
594
+ }
595
+
596
+ return nodes.length > 0 ? nodes : text;
597
+ }
package/src/index.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Dashboard V2 - Main Entry Point
3
+ *
4
+ * Exports all utilities, components, and types for the v2 dashboard.
5
+ */
6
+
7
+ // Types
8
+ export * from './types/index.js';
9
+
10
+ // Color coding utilities
11
+ export {
12
+ getAgentColor,
13
+ getAgentPrefix,
14
+ getAgentInitials,
15
+ parseAgentHierarchy,
16
+ groupAgentsByPrefix,
17
+ sortAgentsByHierarchy,
18
+ getAgentColorVars,
19
+ STATUS_COLORS,
20
+ type ColorScheme,
21
+ type AgentStatus,
22
+ } from './lib/colors.js';
23
+
24
+ // Hierarchy utilities
25
+ export {
26
+ buildAgentTree,
27
+ flattenTree,
28
+ groupAgents,
29
+ getAgentDisplayName,
30
+ getAgentBreadcrumb,
31
+ matchesSearch,
32
+ filterAgents,
33
+ getGroupStats,
34
+ type HierarchyNode,
35
+ type AgentGroup,
36
+ } from './lib/hierarchy.js';
37
+
38
+ // API utilities
39
+ export {
40
+ api,
41
+ DashboardWebSocket,
42
+ getWebSocket,
43
+ type DashboardData,
44
+ } from './lib/api.js';
45
+
46
+ // Reaction components
47
+ export { ReactionChips } from './components/ReactionChips.js';
48
+ export { ReactionPicker } from './components/ReactionPicker.js';
49
+
50
+ // Thread hook
51
+ export { useThread } from './components/hooks/useThread.js';
52
+
53
+ // React Components
54
+ // These require React to be installed. For Next.js consumers, use
55
+ // transpilePackages: ['@agent-relay/dashboard'] and import from
56
+ // '@agent-relay/dashboard/components/App' etc.
57
+ export { App } from './components/App.js';
58
+ export type { AppProps } from './components/App.js';
59
+ export { MessageList } from './components/MessageList.js';
60
+ export { ThreadPanel } from './components/ThreadPanel.js';
61
+
62
+ // Config
63
+ export { config, getWebSocketUrl, getApiBaseUrl } from './lib/config.js';