@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.
- package/out/404.html +1 -1
- package/out/_next/static/chunks/{118-4c8241b0218335de.js → 118-ae2b650136a5a5fc.js} +1 -1
- package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +1 -0
- package/out/_next/static/chunks/app/app/[[...slug]]/{page-1e81c047cff17212.js → page-f7eca1b66fb4249b.js} +1 -1
- package/out/_next/static/chunks/app/{page-6892fe2dd07fb48b.js → page-0ee604f7070d14c0.js} +1 -1
- package/out/_next/static/css/8968d98ed4c4d33f.css +1 -0
- package/out/about.html +2 -2
- package/out/about.txt +1 -1
- package/out/app/onboarding.html +1 -1
- package/out/app/onboarding.txt +1 -1
- package/out/app.html +1 -1
- package/out/app.txt +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
- package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
- package/out/blog.html +2 -2
- package/out/blog.txt +1 -1
- package/out/careers.html +2 -2
- package/out/careers.txt +1 -1
- package/out/changelog.html +2 -2
- package/out/changelog.txt +1 -1
- package/out/cloud/link.html +1 -1
- package/out/cloud/link.txt +2 -2
- package/out/complete-profile.html +2 -2
- package/out/complete-profile.txt +1 -1
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +1 -1
- package/out/contact.html +2 -2
- package/out/contact.txt +1 -1
- package/out/docs.html +2 -2
- package/out/docs.txt +1 -1
- package/out/history.html +1 -1
- package/out/history.txt +2 -2
- package/out/index.html +1 -1
- package/out/index.txt +2 -2
- package/out/login.html +2 -2
- package/out/login.txt +1 -1
- package/out/metrics.html +1 -1
- package/out/metrics.txt +2 -2
- package/out/pricing.html +2 -2
- package/out/pricing.txt +1 -1
- package/out/privacy.html +2 -2
- package/out/privacy.txt +1 -1
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +1 -1
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +1 -1
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +1 -1
- package/out/providers.html +1 -1
- package/out/providers.txt +1 -1
- package/out/security.html +2 -2
- package/out/security.txt +1 -1
- package/out/signup.html +2 -2
- package/out/signup.txt +1 -1
- package/out/terms.html +2 -2
- package/out/terms.txt +1 -1
- package/package.json +7 -1
- package/src/app/about/page.tsx +7 -0
- package/src/app/app/[[...slug]]/DashboardPageClient.tsx +853 -0
- package/src/app/app/[[...slug]]/page.tsx +23 -0
- package/src/app/app/onboarding/page.tsx +394 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/blog/go-to-bed-wake-up-to-a-finished-product/page.tsx +88 -0
- package/src/app/blog/let-them-cook-multi-agent-orchestration/page.tsx +93 -0
- package/src/app/blog/page.tsx +15 -0
- package/src/app/careers/page.tsx +7 -0
- package/src/app/changelog/page.tsx +7 -0
- package/src/app/cloud/link/page.tsx +464 -0
- package/src/app/complete-profile/page.tsx +204 -0
- package/src/app/connect-repos/page.tsx +410 -0
- package/src/app/contact/page.tsx +7 -0
- package/src/app/docs/page.tsx +7 -0
- package/src/app/favicon.png +0 -0
- package/src/app/globals.css +200 -0
- package/src/app/history/page.tsx +658 -0
- package/src/app/layout.tsx +25 -0
- package/src/app/login/page.tsx +424 -0
- package/src/app/metrics/page.tsx +781 -0
- package/src/app/page.tsx +59 -0
- package/src/app/pricing/page.tsx +7 -0
- package/src/app/privacy/page.tsx +7 -0
- package/src/app/providers/page.tsx +193 -0
- package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +197 -0
- package/src/app/providers/setup/[provider]/constants.ts +35 -0
- package/src/app/providers/setup/[provider]/page.tsx +42 -0
- package/src/app/security/page.tsx +7 -0
- package/src/app/signup/page.tsx +533 -0
- package/src/app/terms/page.tsx +7 -0
- package/src/components/ActivityFeed.tsx +216 -0
- package/src/components/AddWorkspaceModal.tsx +170 -0
- package/src/components/AgentCard.test.tsx +134 -0
- package/src/components/AgentCard.tsx +585 -0
- package/src/components/AgentList.test.tsx +147 -0
- package/src/components/AgentList.tsx +419 -0
- package/src/components/AgentLogPreview.tsx +173 -0
- package/src/components/AgentProfilePanel.tsx +569 -0
- package/src/components/App.tsx +3424 -0
- package/src/components/BillingPanel.tsx +922 -0
- package/src/components/BillingResult.tsx +447 -0
- package/src/components/BroadcastComposer.tsx +690 -0
- package/src/components/ChannelAdminPanel.tsx +773 -0
- package/src/components/ChannelBrowser.tsx +385 -0
- package/src/components/ChannelChat.tsx +261 -0
- package/src/components/ChannelSidebar.tsx +399 -0
- package/src/components/CloudSessionProvider.tsx +130 -0
- package/src/components/CommandPalette.tsx +815 -0
- package/src/components/ConfirmationDialog.tsx +133 -0
- package/src/components/ConversationHistory.tsx +518 -0
- package/src/components/CoordinatorPanel.tsx +956 -0
- package/src/components/DecisionQueue.tsx +717 -0
- package/src/components/DirectMessageView.tsx +164 -0
- package/src/components/FileAutocomplete.tsx +368 -0
- package/src/components/FleetOverview.tsx +278 -0
- package/src/components/LogViewer.tsx +310 -0
- package/src/components/LogViewerPanel.tsx +482 -0
- package/src/components/Logo.tsx +284 -0
- package/src/components/MentionAutocomplete.tsx +384 -0
- package/src/components/MessageComposer.tsx +473 -0
- package/src/components/MessageList.tsx +725 -0
- package/src/components/MessageSenderName.tsx +91 -0
- package/src/components/MessageStatusIndicator.tsx +142 -0
- package/src/components/NewConversationModal.tsx +400 -0
- package/src/components/NotificationToast.tsx +488 -0
- package/src/components/OnlineUsersIndicator.tsx +164 -0
- package/src/components/Pagination.tsx +124 -0
- package/src/components/PricingPlans.tsx +386 -0
- package/src/components/ProjectList.tsx +711 -0
- package/src/components/ProviderAuthFlow.tsx +343 -0
- package/src/components/ProviderConnectionList.tsx +375 -0
- package/src/components/ProvisioningProgress.tsx +730 -0
- package/src/components/ReactionChips.tsx +70 -0
- package/src/components/ReactionPicker.tsx +121 -0
- package/src/components/RepoAccessPanel.tsx +787 -0
- package/src/components/RepositoriesPanel.tsx +901 -0
- package/src/components/ServerCard.tsx +202 -0
- package/src/components/SessionExpiredModal.tsx +128 -0
- package/src/components/SpawnModal.test.tsx +190 -0
- package/src/components/SpawnModal.tsx +1001 -0
- package/src/components/TaskAssignmentUI.tsx +375 -0
- package/src/components/TerminalProviderSetup.tsx +517 -0
- package/src/components/ThemeProvider.tsx +159 -0
- package/src/components/ThinkingIndicator.tsx +231 -0
- package/src/components/ThreadList.tsx +198 -0
- package/src/components/ThreadPanel.tsx +405 -0
- package/src/components/TrajectoryViewer.tsx +698 -0
- package/src/components/TypingIndicator.tsx +69 -0
- package/src/components/UsageBanner.tsx +231 -0
- package/src/components/UserProfilePanel.tsx +233 -0
- package/src/components/WorkspaceContext.tsx +95 -0
- package/src/components/WorkspaceSelector.tsx +234 -0
- package/src/components/WorkspaceStatusIndicator.tsx +396 -0
- package/src/components/XTermInteractive.tsx +516 -0
- package/src/components/XTermLogViewer.tsx +719 -0
- package/src/components/channels/ChannelDialogs.tsx +1411 -0
- package/src/components/channels/ChannelHeader.tsx +317 -0
- package/src/components/channels/ChannelMessageList.tsx +463 -0
- package/src/components/channels/ChannelViewV1.tsx +146 -0
- package/src/components/channels/MessageInput.tsx +302 -0
- package/src/components/channels/SearchInput.tsx +172 -0
- package/src/components/channels/SearchResults.tsx +336 -0
- package/src/components/channels/api.test.ts +1527 -0
- package/src/components/channels/api.ts +703 -0
- package/src/components/channels/index.ts +76 -0
- package/src/components/channels/mockApi.ts +344 -0
- package/src/components/channels/types.ts +566 -0
- package/src/components/hooks/index.ts +58 -0
- package/src/components/hooks/useAgentLogs.ts +504 -0
- package/src/components/hooks/useAgents.ts +127 -0
- package/src/components/hooks/useBroadcastDedup.test.ts +371 -0
- package/src/components/hooks/useBroadcastDedup.ts +86 -0
- package/src/components/hooks/useChannelAdmin.ts +329 -0
- package/src/components/hooks/useChannelBrowser.ts +239 -0
- package/src/components/hooks/useChannelCommands.ts +138 -0
- package/src/components/hooks/useChannels.ts +367 -0
- package/src/components/hooks/useDebounce.ts +29 -0
- package/src/components/hooks/useDirectMessage.test.ts +952 -0
- package/src/components/hooks/useDirectMessage.ts +141 -0
- package/src/components/hooks/useMessages.ts +310 -0
- package/src/components/hooks/useOrchestrator.test.ts +165 -0
- package/src/components/hooks/useOrchestrator.ts +424 -0
- package/src/components/hooks/usePinnedAgents.test.ts +356 -0
- package/src/components/hooks/usePinnedAgents.ts +140 -0
- package/src/components/hooks/usePresence.test.ts +245 -0
- package/src/components/hooks/usePresence.ts +377 -0
- package/src/components/hooks/useRecentRepos.ts +130 -0
- package/src/components/hooks/useSession.ts +209 -0
- package/src/components/hooks/useThread.ts +138 -0
- package/src/components/hooks/useTrajectory.ts +265 -0
- package/src/components/hooks/useWebSocket.ts +290 -0
- package/src/components/hooks/useWorkspaceMembers.ts +132 -0
- package/src/components/hooks/useWorkspaceRepos.ts +73 -0
- package/src/components/hooks/useWorkspaceStatus.ts +237 -0
- package/src/components/index.ts +81 -0
- package/src/components/layout/Header.tsx +311 -0
- package/src/components/layout/RepoContextHeader.tsx +361 -0
- package/src/components/layout/Sidebar.archive.test.tsx +126 -0
- package/src/components/layout/Sidebar.test.tsx +691 -0
- package/src/components/layout/Sidebar.tsx +900 -0
- package/src/components/layout/index.ts +7 -0
- package/src/components/settings/BillingSettingsPanel.tsx +564 -0
- package/src/components/settings/SettingsPage.tsx +683 -0
- package/src/components/settings/TeamSettingsPanel.tsx +560 -0
- package/src/components/settings/WorkspaceSettingsPanel.tsx +1368 -0
- package/src/components/settings/index.ts +11 -0
- package/src/components/settings/types.ts +79 -0
- package/src/components/utils/messageFormatting.test.tsx +331 -0
- package/src/components/utils/messageFormatting.tsx +597 -0
- package/src/index.ts +63 -0
- package/src/landing/AboutPage.tsx +77 -0
- package/src/landing/BlogContent.tsx +187 -0
- package/src/landing/BlogPage.tsx +47 -0
- package/src/landing/CareersPage.tsx +53 -0
- package/src/landing/ChangelogPage.tsx +33 -0
- package/src/landing/ContactPage.tsx +41 -0
- package/src/landing/DocsPage.tsx +43 -0
- package/src/landing/LandingPage.tsx +702 -0
- package/src/landing/PricingPage.tsx +549 -0
- package/src/landing/PrivacyPage.tsx +117 -0
- package/src/landing/SecurityPage.tsx +42 -0
- package/src/landing/StaticPage.tsx +165 -0
- package/src/landing/TermsPage.tsx +125 -0
- package/src/landing/blogData.ts +312 -0
- package/src/landing/index.ts +18 -0
- package/src/landing/styles.css +3673 -0
- package/src/lib/agent-merge.test.ts +43 -0
- package/src/lib/agent-merge.ts +35 -0
- package/src/lib/api.ts +1294 -0
- package/src/lib/cloudApi.ts +893 -0
- package/src/lib/colors.test.ts +175 -0
- package/src/lib/colors.ts +218 -0
- package/src/lib/config.ts +109 -0
- package/src/lib/hierarchy.ts +242 -0
- package/src/lib/stuckDetection.ts +142 -0
- package/src/lib/useUrlRouting.ts +190 -0
- package/src/types/index.ts +317 -0
- package/src/types/threading.ts +7 -0
- package/out/_next/static/chunks/285-dc644487a8d6500d.js +0 -1
- package/out/_next/static/css/4c58d9cf493aa626.css +0 -1
- /package/out/_next/static/{AqelRhy1vr2nBUcU0Iqcp → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
- /package/out/_next/static/{AqelRhy1vr2nBUcU0Iqcp → IxfA6RZu4trcsEMYlkQra}/_ssgManifest.js +0 -0
- /package/out/_next/static/chunks/{528-d375bc8b46912d2c.js → 528-f5f676996d613c25.js} +0 -0
- /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
|
+
}
|