@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,231 @@
1
+ /**
2
+ * ThinkingIndicator Component
3
+ *
4
+ * Displays an animated indicator when an agent is processing/thinking.
5
+ * Shows a pulsing animation similar to "typing..." indicators in chat apps.
6
+ */
7
+
8
+ import React, { useEffect, useState } from 'react';
9
+
10
+ export interface ThinkingIndicatorProps {
11
+ /** Whether the agent is currently processing */
12
+ isProcessing: boolean;
13
+ /** When processing started (for elapsed time display) */
14
+ processingStartedAt?: number;
15
+ /** Size variant */
16
+ size?: 'small' | 'medium' | 'large';
17
+ /** Show elapsed time */
18
+ showElapsed?: boolean;
19
+ /** Show label text */
20
+ showLabel?: boolean;
21
+ }
22
+
23
+ export function ThinkingIndicator({
24
+ isProcessing,
25
+ processingStartedAt,
26
+ size = 'medium',
27
+ showElapsed = false,
28
+ showLabel = false,
29
+ }: ThinkingIndicatorProps) {
30
+ const [elapsedMs, setElapsedMs] = useState(0);
31
+
32
+ // Update elapsed time every second when processing
33
+ useEffect(() => {
34
+ if (!isProcessing || !processingStartedAt) {
35
+ setElapsedMs(0);
36
+ return;
37
+ }
38
+
39
+ const updateElapsed = () => {
40
+ setElapsedMs(Date.now() - processingStartedAt);
41
+ };
42
+
43
+ updateElapsed();
44
+ const interval = setInterval(updateElapsed, 1000);
45
+ return () => clearInterval(interval);
46
+ }, [isProcessing, processingStartedAt]);
47
+
48
+ if (!isProcessing) {
49
+ return null;
50
+ }
51
+
52
+ const formatElapsed = (ms: number): string => {
53
+ const seconds = Math.floor(ms / 1000);
54
+ if (seconds < 60) return `${seconds}s`;
55
+ const minutes = Math.floor(seconds / 60);
56
+ return `${minutes}m ${seconds % 60}s`;
57
+ };
58
+
59
+ const dotSizeClasses: Record<string, string> = {
60
+ small: 'w-1 h-1',
61
+ medium: 'w-1.5 h-1.5',
62
+ large: 'w-2 h-2',
63
+ };
64
+
65
+ const gapClasses: Record<string, string> = {
66
+ small: 'gap-0.5',
67
+ medium: 'gap-1',
68
+ large: 'gap-1.5',
69
+ };
70
+
71
+ return (
72
+ <span
73
+ className="inline-flex items-center gap-1.5 text-accent-purple"
74
+ title="Processing..."
75
+ >
76
+ <span className={`inline-flex items-center ${gapClasses[size]}`}>
77
+ <span
78
+ className={`${dotSizeClasses[size]} rounded-full bg-accent-purple animate-bounce`}
79
+ style={{ animationDelay: '0ms', animationDuration: '800ms' }}
80
+ />
81
+ <span
82
+ className={`${dotSizeClasses[size]} rounded-full bg-accent-purple animate-bounce`}
83
+ style={{ animationDelay: '150ms', animationDuration: '800ms' }}
84
+ />
85
+ <span
86
+ className={`${dotSizeClasses[size]} rounded-full bg-accent-purple animate-bounce`}
87
+ style={{ animationDelay: '300ms', animationDuration: '800ms' }}
88
+ />
89
+ </span>
90
+ {showLabel && (
91
+ <span className="text-xs font-medium text-accent-purple">thinking</span>
92
+ )}
93
+ {showElapsed && elapsedMs > 0 && (
94
+ <span className="text-xs text-accent-purple/70">{formatElapsed(elapsedMs)}</span>
95
+ )}
96
+ </span>
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Inline thinking indicator for compact views
102
+ */
103
+ export function ThinkingDot({ isProcessing }: { isProcessing: boolean }) {
104
+ if (!isProcessing) return null;
105
+
106
+ return (
107
+ <span className="thinking-dot-inline" title="Processing...">
108
+ <span className="thinking-dot-pulse" />
109
+ </span>
110
+ );
111
+ }
112
+
113
+ /**
114
+ * CSS styles for the thinking indicator
115
+ */
116
+ export const thinkingIndicatorStyles = `
117
+ .thinking-indicator {
118
+ display: inline-flex;
119
+ align-items: center;
120
+ gap: 6px;
121
+ }
122
+
123
+ .thinking-dots {
124
+ display: inline-flex;
125
+ align-items: center;
126
+ gap: 3px;
127
+ }
128
+
129
+ .thinking-dot {
130
+ width: 6px;
131
+ height: 6px;
132
+ border-radius: 50%;
133
+ background: #6366f1;
134
+ animation: thinking-bounce 1.4s ease-in-out infinite;
135
+ }
136
+
137
+ .thinking-dot:nth-child(1) {
138
+ animation-delay: 0s;
139
+ }
140
+
141
+ .thinking-dot:nth-child(2) {
142
+ animation-delay: 0.2s;
143
+ }
144
+
145
+ .thinking-dot:nth-child(3) {
146
+ animation-delay: 0.4s;
147
+ }
148
+
149
+ @keyframes thinking-bounce {
150
+ 0%, 60%, 100% {
151
+ transform: translateY(0);
152
+ opacity: 0.4;
153
+ }
154
+ 30% {
155
+ transform: translateY(-4px);
156
+ opacity: 1;
157
+ }
158
+ }
159
+
160
+ .thinking-elapsed {
161
+ font-size: 10px;
162
+ color: #6366f1;
163
+ margin-left: 4px;
164
+ }
165
+
166
+ /* Size variants */
167
+ .thinking-indicator-small .thinking-dot {
168
+ width: 4px;
169
+ height: 4px;
170
+ }
171
+
172
+ .thinking-indicator-small .thinking-dots {
173
+ gap: 2px;
174
+ }
175
+
176
+ .thinking-indicator-large .thinking-dot {
177
+ width: 8px;
178
+ height: 8px;
179
+ }
180
+
181
+ .thinking-indicator-large .thinking-dots {
182
+ gap: 4px;
183
+ }
184
+
185
+ @keyframes thinking-bounce-large {
186
+ 0%, 60%, 100% {
187
+ transform: translateY(0);
188
+ opacity: 0.4;
189
+ }
190
+ 30% {
191
+ transform: translateY(-6px);
192
+ opacity: 1;
193
+ }
194
+ }
195
+
196
+ .thinking-indicator-large .thinking-dot {
197
+ animation: thinking-bounce-large 1.4s ease-in-out infinite;
198
+ }
199
+
200
+ /* Inline dot for compact views */
201
+ .thinking-dot-inline {
202
+ display: inline-flex;
203
+ align-items: center;
204
+ justify-content: center;
205
+ width: 12px;
206
+ height: 12px;
207
+ }
208
+
209
+ .thinking-dot-pulse {
210
+ width: 8px;
211
+ height: 8px;
212
+ border-radius: 50%;
213
+ background: #6366f1;
214
+ animation: thinking-pulse 1.5s ease-in-out infinite;
215
+ }
216
+
217
+ @keyframes thinking-pulse {
218
+ 0% {
219
+ transform: scale(0.8);
220
+ opacity: 0.5;
221
+ }
222
+ 50% {
223
+ transform: scale(1.1);
224
+ opacity: 1;
225
+ }
226
+ 100% {
227
+ transform: scale(0.8);
228
+ opacity: 0.5;
229
+ }
230
+ }
231
+ `;
@@ -0,0 +1,198 @@
1
+ /**
2
+ * ThreadList Component
3
+ *
4
+ * Displays a list of active threads in the sidebar with unread indicators.
5
+ * Features a refined design with subtle glows and smooth transitions.
6
+ */
7
+
8
+ import React from 'react';
9
+ import type { ThreadInfo } from './hooks/useMessages';
10
+
11
+ export interface ThreadListProps {
12
+ threads: ThreadInfo[];
13
+ currentThread?: string | null;
14
+ onThreadSelect: (threadId: string) => void;
15
+ /** Total unread count for the threads section header badge */
16
+ totalUnreadCount?: number;
17
+ /** Whether the threads section is collapsed */
18
+ isCollapsed?: boolean;
19
+ /** Callback to toggle the collapsed state */
20
+ onToggleCollapse?: () => void;
21
+ }
22
+
23
+ export function ThreadList({
24
+ threads,
25
+ currentThread,
26
+ onThreadSelect,
27
+ totalUnreadCount = 0,
28
+ isCollapsed = false,
29
+ onToggleCollapse,
30
+ }: ThreadListProps) {
31
+ if (threads.length === 0) {
32
+ return null;
33
+ }
34
+
35
+ return (
36
+ <div className="px-2 py-2">
37
+ {/* Section Header */}
38
+ <button
39
+ className="w-full flex items-center justify-between px-2 py-2 mb-1.5 bg-transparent border-none cursor-pointer rounded-lg transition-all duration-200 hover:bg-gradient-to-r hover:from-[rgba(255,255,255,0.03)] hover:to-transparent"
40
+ onClick={onToggleCollapse}
41
+ aria-expanded={!isCollapsed}
42
+ aria-label={isCollapsed ? 'Expand threads' : 'Collapse threads'}
43
+ >
44
+ <div className="flex items-center gap-2">
45
+ <CollapseIcon isCollapsed={isCollapsed} />
46
+ <span className="text-xs font-semibold uppercase tracking-wider text-text-muted">
47
+ Threads
48
+ </span>
49
+ <span className="text-xs text-text-dim font-mono">({threads.length})</span>
50
+ </div>
51
+ {totalUnreadCount > 0 && (
52
+ <span
53
+ className="min-w-[18px] h-[18px] flex items-center justify-center text-[10px] font-bold bg-accent-cyan text-bg-deep rounded-full px-1.5"
54
+ style={{ boxShadow: '0 0 8px rgba(0, 217, 255, 0.5)' }}
55
+ >
56
+ {totalUnreadCount > 99 ? '99+' : totalUnreadCount}
57
+ </span>
58
+ )}
59
+ </button>
60
+
61
+ {/* Thread Items - only show when not collapsed */}
62
+ {!isCollapsed && (
63
+ <div className="space-y-1">
64
+ {threads.map((thread) => (
65
+ <ThreadItem
66
+ key={thread.id}
67
+ thread={thread}
68
+ isSelected={currentThread === thread.id}
69
+ onClick={() => onThreadSelect(thread.id)}
70
+ />
71
+ ))}
72
+ </div>
73
+ )}
74
+ </div>
75
+ );
76
+ }
77
+
78
+ interface ThreadItemProps {
79
+ thread: ThreadInfo;
80
+ isSelected: boolean;
81
+ onClick: () => void;
82
+ }
83
+
84
+ function ThreadItem({ thread, isSelected, onClick }: ThreadItemProps) {
85
+ const hasUnread = thread.unreadCount > 0;
86
+ const timestamp = formatRelativeTime(thread.lastMessage.timestamp);
87
+
88
+ return (
89
+ <button
90
+ className={`
91
+ group w-full flex items-center gap-3 px-2.5 py-2.5 rounded-lg text-left transition-all duration-200 cursor-pointer border-none
92
+ hover:bg-gradient-to-r hover:from-[rgba(255,255,255,0.03)] hover:to-transparent
93
+ ${isSelected
94
+ ? 'bg-gradient-to-r from-[rgba(255,255,255,0.06)] to-transparent'
95
+ : 'bg-transparent'
96
+ }
97
+ `}
98
+ onClick={onClick}
99
+ style={{
100
+ borderLeft: isSelected ? '2px solid #00d9ff' : '2px solid transparent',
101
+ boxShadow: isSelected ? 'inset 4px 0 12px -4px rgba(0, 217, 255, 0.25)' : 'none',
102
+ }}
103
+ >
104
+ {/* Thread Icon */}
105
+ <div
106
+ className={`
107
+ relative shrink-0 w-8 h-8 rounded-lg flex items-center justify-center overflow-hidden transition-all duration-200
108
+ ${hasUnread ? 'text-accent-cyan' : 'text-text-muted'}
109
+ `}
110
+ style={{
111
+ background: hasUnread
112
+ ? 'linear-gradient(135deg, rgba(0, 217, 255, 0.2), rgba(0, 217, 255, 0.1))'
113
+ : 'rgba(255, 255, 255, 0.03)',
114
+ boxShadow: hasUnread ? '0 0 8px rgba(0, 217, 255, 0.2)' : 'none',
115
+ }}
116
+ >
117
+ {/* Subtle shine overlay */}
118
+ <div
119
+ className="absolute inset-0 opacity-20"
120
+ style={{
121
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.2) 0%, transparent 50%)',
122
+ }}
123
+ />
124
+ <ThreadIcon />
125
+ </div>
126
+
127
+ {/* Thread Info */}
128
+ <div className="flex-1 min-w-0">
129
+ <div className="flex items-center gap-2">
130
+ <span className={`
131
+ text-[13px] truncate transition-colors duration-200
132
+ ${hasUnread ? 'font-semibold text-text-primary' : 'text-text-secondary group-hover:text-text-primary'}
133
+ `}>
134
+ {thread.name}
135
+ </span>
136
+ {hasUnread && (
137
+ <span
138
+ className="shrink-0 min-w-[16px] h-[16px] flex items-center justify-center text-[9px] font-bold bg-accent-cyan text-bg-deep rounded-full px-1"
139
+ style={{ boxShadow: '0 0 6px rgba(0, 217, 255, 0.4)' }}
140
+ >
141
+ {thread.unreadCount > 99 ? '99+' : thread.unreadCount}
142
+ </span>
143
+ )}
144
+ </div>
145
+ <div className="flex items-center gap-1.5 text-[11px] text-text-muted font-mono">
146
+ <span className="truncate">{thread.participants.slice(0, 2).join(', ')}</span>
147
+ {thread.participants.length > 2 && (
148
+ <span className="text-text-dim">+{thread.participants.length - 2}</span>
149
+ )}
150
+ <span className="text-text-dim opacity-50">·</span>
151
+ <span className="text-text-dim">{timestamp}</span>
152
+ </div>
153
+ </div>
154
+ </button>
155
+ );
156
+ }
157
+
158
+ function formatRelativeTime(timestamp: string | number): string {
159
+ const date = new Date(timestamp);
160
+ const now = new Date();
161
+ const diffMs = now.getTime() - date.getTime();
162
+ const diffMins = Math.floor(diffMs / (1000 * 60));
163
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
164
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
165
+
166
+ if (diffMins < 1) return 'now';
167
+ if (diffMins < 60) return `${diffMins}m`;
168
+ if (diffHours < 24) return `${diffHours}h`;
169
+ if (diffDays < 7) return `${diffDays}d`;
170
+
171
+ return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
172
+ }
173
+
174
+ function ThreadIcon() {
175
+ return (
176
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
177
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
178
+ </svg>
179
+ );
180
+ }
181
+
182
+ function CollapseIcon({ isCollapsed }: { isCollapsed: boolean }) {
183
+ return (
184
+ <svg
185
+ width="12"
186
+ height="12"
187
+ viewBox="0 0 24 24"
188
+ fill="none"
189
+ stroke="currentColor"
190
+ strokeWidth="2"
191
+ strokeLinecap="round"
192
+ strokeLinejoin="round"
193
+ className={`text-text-muted transition-transform duration-200 ${isCollapsed ? '' : 'rotate-90'}`}
194
+ >
195
+ <polyline points="9 18 15 12 9 6" />
196
+ </svg>
197
+ );
198
+ }