@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,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logo Component - Monogram AR
|
|
3
|
+
*
|
|
4
|
+
* The Agent Relay logo featuring interwoven A and R letters.
|
|
5
|
+
* Uses the signature cyan (#00d9ff) and teal (#00ffc8) colors
|
|
6
|
+
* to symbolize structure and flow.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React from 'react';
|
|
10
|
+
|
|
11
|
+
export interface LogoProps {
|
|
12
|
+
/** Size of the logo in pixels (width and height) */
|
|
13
|
+
size?: number;
|
|
14
|
+
/** Whether to include the wordmark alongside the icon */
|
|
15
|
+
showWordmark?: boolean;
|
|
16
|
+
/** Additional CSS classes */
|
|
17
|
+
className?: string;
|
|
18
|
+
/** Whether to apply a glow effect */
|
|
19
|
+
withGlow?: boolean;
|
|
20
|
+
/** Whether to animate on hover */
|
|
21
|
+
animated?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Agent Relay Monogram Logo
|
|
26
|
+
*
|
|
27
|
+
* An SVG logo with interwoven A and R letters representing
|
|
28
|
+
* the connection between agents in the relay system.
|
|
29
|
+
*/
|
|
30
|
+
export function Logo({
|
|
31
|
+
size = 40,
|
|
32
|
+
showWordmark = false,
|
|
33
|
+
className = '',
|
|
34
|
+
withGlow = true,
|
|
35
|
+
animated = true,
|
|
36
|
+
}: LogoProps) {
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
className={`
|
|
40
|
+
inline-flex items-center gap-3
|
|
41
|
+
${animated ? 'group' : ''}
|
|
42
|
+
${className}
|
|
43
|
+
`}
|
|
44
|
+
>
|
|
45
|
+
<svg
|
|
46
|
+
width={size}
|
|
47
|
+
height={size}
|
|
48
|
+
viewBox="0 0 100 100"
|
|
49
|
+
fill="none"
|
|
50
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
51
|
+
className={`
|
|
52
|
+
transition-all duration-300
|
|
53
|
+
${withGlow ? 'drop-shadow-[0_0_8px_rgba(0,217,255,0.3)]' : ''}
|
|
54
|
+
${animated ? 'group-hover:drop-shadow-[0_0_16px_rgba(0,217,255,0.5)] group-hover:scale-105' : ''}
|
|
55
|
+
`}
|
|
56
|
+
aria-label="Agent Relay Logo"
|
|
57
|
+
role="img"
|
|
58
|
+
>
|
|
59
|
+
{/* A shape - Primary structure */}
|
|
60
|
+
<path
|
|
61
|
+
d="M30 80 L 50 20 L 70 80"
|
|
62
|
+
stroke="#00d9ff"
|
|
63
|
+
strokeWidth="4"
|
|
64
|
+
strokeLinejoin="round"
|
|
65
|
+
strokeLinecap="round"
|
|
66
|
+
fill="none"
|
|
67
|
+
className={`
|
|
68
|
+
${animated ? 'transition-all duration-300 group-hover:[stroke-width:5]' : ''}
|
|
69
|
+
`}
|
|
70
|
+
/>
|
|
71
|
+
{/* A crossbar */}
|
|
72
|
+
<line
|
|
73
|
+
x1="40"
|
|
74
|
+
y1="50"
|
|
75
|
+
x2="60"
|
|
76
|
+
y2="50"
|
|
77
|
+
stroke="#00d9ff"
|
|
78
|
+
strokeWidth="4"
|
|
79
|
+
strokeLinecap="round"
|
|
80
|
+
className={`
|
|
81
|
+
${animated ? 'transition-all duration-300 group-hover:[stroke-width:5]' : ''}
|
|
82
|
+
`}
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
{/* R overlay - Representing flow/relay */}
|
|
86
|
+
<path
|
|
87
|
+
d="M50 20 L 50 80"
|
|
88
|
+
stroke="#00ffc8"
|
|
89
|
+
strokeWidth="2"
|
|
90
|
+
strokeLinecap="round"
|
|
91
|
+
opacity="0.7"
|
|
92
|
+
className={`
|
|
93
|
+
${animated ? 'transition-opacity duration-300 group-hover:opacity-100' : ''}
|
|
94
|
+
`}
|
|
95
|
+
/>
|
|
96
|
+
<path
|
|
97
|
+
d="M50 20 C 80 20 80 50 50 50 L 80 80"
|
|
98
|
+
stroke="#00ffc8"
|
|
99
|
+
strokeWidth="2"
|
|
100
|
+
strokeLinecap="round"
|
|
101
|
+
strokeLinejoin="round"
|
|
102
|
+
fill="none"
|
|
103
|
+
opacity="0.7"
|
|
104
|
+
className={`
|
|
105
|
+
${animated ? 'transition-opacity duration-300 group-hover:opacity-100' : ''}
|
|
106
|
+
`}
|
|
107
|
+
/>
|
|
108
|
+
</svg>
|
|
109
|
+
|
|
110
|
+
{showWordmark && (
|
|
111
|
+
<span
|
|
112
|
+
className={`
|
|
113
|
+
font-display font-semibold text-text-primary tracking-tight
|
|
114
|
+
transition-all duration-300
|
|
115
|
+
${animated ? 'group-hover:text-accent-cyan' : ''}
|
|
116
|
+
`}
|
|
117
|
+
style={{ fontSize: size * 0.45 }}
|
|
118
|
+
>
|
|
119
|
+
Agent Relay
|
|
120
|
+
</span>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Compact logo icon for tight spaces like headers
|
|
128
|
+
*/
|
|
129
|
+
export function LogoIcon({
|
|
130
|
+
size = 24,
|
|
131
|
+
className = '',
|
|
132
|
+
withGlow = false,
|
|
133
|
+
}: Pick<LogoProps, 'size' | 'className' | 'withGlow'>) {
|
|
134
|
+
return (
|
|
135
|
+
<svg
|
|
136
|
+
width={size}
|
|
137
|
+
height={size}
|
|
138
|
+
viewBox="0 0 100 100"
|
|
139
|
+
fill="none"
|
|
140
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
141
|
+
className={`
|
|
142
|
+
transition-all duration-300
|
|
143
|
+
${withGlow ? 'drop-shadow-[0_0_8px_rgba(0,217,255,0.3)]' : ''}
|
|
144
|
+
${className}
|
|
145
|
+
`}
|
|
146
|
+
aria-label="Agent Relay Logo"
|
|
147
|
+
role="img"
|
|
148
|
+
>
|
|
149
|
+
{/* A shape */}
|
|
150
|
+
<path
|
|
151
|
+
d="M30 80 L 50 20 L 70 80"
|
|
152
|
+
stroke="#00d9ff"
|
|
153
|
+
strokeWidth="5"
|
|
154
|
+
strokeLinejoin="round"
|
|
155
|
+
strokeLinecap="round"
|
|
156
|
+
fill="none"
|
|
157
|
+
/>
|
|
158
|
+
<line
|
|
159
|
+
x1="40"
|
|
160
|
+
y1="50"
|
|
161
|
+
x2="60"
|
|
162
|
+
y2="50"
|
|
163
|
+
stroke="#00d9ff"
|
|
164
|
+
strokeWidth="5"
|
|
165
|
+
strokeLinecap="round"
|
|
166
|
+
/>
|
|
167
|
+
|
|
168
|
+
{/* R overlay */}
|
|
169
|
+
<path
|
|
170
|
+
d="M50 20 L 50 80"
|
|
171
|
+
stroke="#00ffc8"
|
|
172
|
+
strokeWidth="2.5"
|
|
173
|
+
strokeLinecap="round"
|
|
174
|
+
opacity="0.7"
|
|
175
|
+
/>
|
|
176
|
+
<path
|
|
177
|
+
d="M50 20 C 80 20 80 50 50 50 L 80 80"
|
|
178
|
+
stroke="#00ffc8"
|
|
179
|
+
strokeWidth="2.5"
|
|
180
|
+
strokeLinecap="round"
|
|
181
|
+
strokeLinejoin="round"
|
|
182
|
+
fill="none"
|
|
183
|
+
opacity="0.7"
|
|
184
|
+
/>
|
|
185
|
+
</svg>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Large animated logo for landing pages and hero sections
|
|
191
|
+
*/
|
|
192
|
+
export function LogoHero({
|
|
193
|
+
className = '',
|
|
194
|
+
}: {
|
|
195
|
+
className?: string;
|
|
196
|
+
}) {
|
|
197
|
+
return (
|
|
198
|
+
<div className={`relative inline-block ${className}`}>
|
|
199
|
+
{/* Outer glow ring animation */}
|
|
200
|
+
<div
|
|
201
|
+
className="
|
|
202
|
+
absolute inset-[-20%]
|
|
203
|
+
rounded-full
|
|
204
|
+
bg-accent-cyan/10
|
|
205
|
+
animate-pulse
|
|
206
|
+
blur-2xl
|
|
207
|
+
"
|
|
208
|
+
aria-hidden="true"
|
|
209
|
+
/>
|
|
210
|
+
|
|
211
|
+
<svg
|
|
212
|
+
width={120}
|
|
213
|
+
height={120}
|
|
214
|
+
viewBox="0 0 100 100"
|
|
215
|
+
fill="none"
|
|
216
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
217
|
+
className="
|
|
218
|
+
relative z-10
|
|
219
|
+
drop-shadow-[0_0_20px_rgba(0,217,255,0.4)]
|
|
220
|
+
animate-[float_6s_ease-in-out_infinite]
|
|
221
|
+
"
|
|
222
|
+
aria-label="Agent Relay Logo"
|
|
223
|
+
role="img"
|
|
224
|
+
>
|
|
225
|
+
<defs>
|
|
226
|
+
<linearGradient id="logoGradientA" x1="30" y1="80" x2="70" y2="20" gradientUnits="userSpaceOnUse">
|
|
227
|
+
<stop offset="0%" stopColor="#00d9ff" />
|
|
228
|
+
<stop offset="100%" stopColor="#00b8d9" />
|
|
229
|
+
</linearGradient>
|
|
230
|
+
<linearGradient id="logoGradientR" x1="50" y1="20" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
|
231
|
+
<stop offset="0%" stopColor="#00ffc8" />
|
|
232
|
+
<stop offset="100%" stopColor="#00d9b8" />
|
|
233
|
+
</linearGradient>
|
|
234
|
+
</defs>
|
|
235
|
+
|
|
236
|
+
{/* A shape with gradient */}
|
|
237
|
+
<path
|
|
238
|
+
d="M30 80 L 50 20 L 70 80"
|
|
239
|
+
stroke="url(#logoGradientA)"
|
|
240
|
+
strokeWidth="5"
|
|
241
|
+
strokeLinejoin="round"
|
|
242
|
+
strokeLinecap="round"
|
|
243
|
+
fill="none"
|
|
244
|
+
/>
|
|
245
|
+
<line
|
|
246
|
+
x1="40"
|
|
247
|
+
y1="50"
|
|
248
|
+
x2="60"
|
|
249
|
+
y2="50"
|
|
250
|
+
stroke="url(#logoGradientA)"
|
|
251
|
+
strokeWidth="5"
|
|
252
|
+
strokeLinecap="round"
|
|
253
|
+
/>
|
|
254
|
+
|
|
255
|
+
{/* R overlay with gradient */}
|
|
256
|
+
<path
|
|
257
|
+
d="M50 20 L 50 80"
|
|
258
|
+
stroke="url(#logoGradientR)"
|
|
259
|
+
strokeWidth="3"
|
|
260
|
+
strokeLinecap="round"
|
|
261
|
+
opacity="0.8"
|
|
262
|
+
/>
|
|
263
|
+
<path
|
|
264
|
+
d="M50 20 C 80 20 80 50 50 50 L 80 80"
|
|
265
|
+
stroke="url(#logoGradientR)"
|
|
266
|
+
strokeWidth="3"
|
|
267
|
+
strokeLinecap="round"
|
|
268
|
+
strokeLinejoin="round"
|
|
269
|
+
fill="none"
|
|
270
|
+
opacity="0.8"
|
|
271
|
+
/>
|
|
272
|
+
</svg>
|
|
273
|
+
|
|
274
|
+
<style>{`
|
|
275
|
+
@keyframes float {
|
|
276
|
+
0%, 100% { transform: translateY(0px); }
|
|
277
|
+
50% { transform: translateY(-8px); }
|
|
278
|
+
}
|
|
279
|
+
`}</style>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export default Logo;
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MentionAutocomplete Component
|
|
3
|
+
*
|
|
4
|
+
* Provides @-mention autocomplete for the message composer.
|
|
5
|
+
* Shows a dropdown list of agents and teams when typing @ at the start of a message.
|
|
6
|
+
* Supports:
|
|
7
|
+
* - @AgentName - mention a specific agent
|
|
8
|
+
* - @everyone / @* - broadcast to all agents
|
|
9
|
+
* - @team:name - mention all agents in a team
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React, { useState, useCallback, useEffect, useRef, useMemo } from 'react';
|
|
13
|
+
import type { Agent } from '../types';
|
|
14
|
+
import { getAgentColor, getAgentInitials } from '../lib/colors';
|
|
15
|
+
|
|
16
|
+
/** Human user info for autocomplete */
|
|
17
|
+
export interface HumanUser {
|
|
18
|
+
/** Username (GitHub username) */
|
|
19
|
+
username: string;
|
|
20
|
+
/** Optional avatar URL */
|
|
21
|
+
avatarUrl?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface MentionAutocompleteProps {
|
|
25
|
+
/** List of available agents */
|
|
26
|
+
agents: Agent[];
|
|
27
|
+
/** List of human users (extracted from recent messages) */
|
|
28
|
+
humanUsers?: HumanUser[];
|
|
29
|
+
/** Current input value */
|
|
30
|
+
inputValue: string;
|
|
31
|
+
/** Cursor position in input */
|
|
32
|
+
cursorPosition: number;
|
|
33
|
+
/** Called when a mention is selected */
|
|
34
|
+
onSelect: (mention: string, newValue: string) => void;
|
|
35
|
+
/** Called when autocomplete should be hidden */
|
|
36
|
+
onClose: () => void;
|
|
37
|
+
/** Whether the autocomplete is visible */
|
|
38
|
+
isVisible: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface MentionOption {
|
|
42
|
+
name: string;
|
|
43
|
+
displayName: string;
|
|
44
|
+
description: string;
|
|
45
|
+
isBroadcast?: boolean;
|
|
46
|
+
isTeam?: boolean;
|
|
47
|
+
isHuman?: boolean;
|
|
48
|
+
avatarUrl?: string;
|
|
49
|
+
memberCount?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if the input has an @-mention being typed at the cursor position.
|
|
54
|
+
* Works for @ at any position in the text, not just the start.
|
|
55
|
+
*/
|
|
56
|
+
export function getMentionQuery(value: string, cursorPos: number): string | null {
|
|
57
|
+
// Search backwards from cursor to find @
|
|
58
|
+
const textBeforeCursor = value.substring(0, cursorPos);
|
|
59
|
+
|
|
60
|
+
// Find the last @ before cursor that starts a mention
|
|
61
|
+
// A mention starts after whitespace, at start of string, or after certain punctuation
|
|
62
|
+
const mentionMatch = textBeforeCursor.match(/(?:^|[\s(])@(\S*)$/);
|
|
63
|
+
if (mentionMatch) {
|
|
64
|
+
return mentionMatch[1]; // Return the text after @
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Result of completing a mention.
|
|
71
|
+
*/
|
|
72
|
+
export interface CompletionResult {
|
|
73
|
+
/** The new input value with the completed mention */
|
|
74
|
+
value: string;
|
|
75
|
+
/** The cursor position after the completion (after the trailing space) */
|
|
76
|
+
cursorPosition: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Complete a mention in the input value at the cursor position.
|
|
81
|
+
*/
|
|
82
|
+
export function completeMentionInValue(
|
|
83
|
+
value: string,
|
|
84
|
+
mention: string,
|
|
85
|
+
cursorPos: number
|
|
86
|
+
): CompletionResult {
|
|
87
|
+
const textBeforeCursor = value.substring(0, cursorPos);
|
|
88
|
+
const textAfterCursor = value.substring(cursorPos);
|
|
89
|
+
|
|
90
|
+
// Find the @ and partial text before cursor
|
|
91
|
+
const mentionMatch = textBeforeCursor.match(/(?:^|[\s(])@(\S*)$/);
|
|
92
|
+
if (mentionMatch) {
|
|
93
|
+
// Calculate where the @ starts (accounting for whitespace/punctuation before it)
|
|
94
|
+
const matchStart = mentionMatch.index || 0;
|
|
95
|
+
const prefixChar = mentionMatch[0].charAt(0);
|
|
96
|
+
const atStart = prefixChar === '@' ? matchStart : matchStart + 1;
|
|
97
|
+
|
|
98
|
+
// Build the new value
|
|
99
|
+
const beforeMention = value.substring(0, atStart);
|
|
100
|
+
const completedMention = `@${mention} `;
|
|
101
|
+
const newValue = beforeMention + completedMention + textAfterCursor;
|
|
102
|
+
const newCursorPos = beforeMention.length + completedMention.length;
|
|
103
|
+
return { value: newValue, cursorPosition: newCursorPos };
|
|
104
|
+
}
|
|
105
|
+
return { value, cursorPosition: cursorPos };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function MentionAutocomplete({
|
|
109
|
+
agents,
|
|
110
|
+
humanUsers = [],
|
|
111
|
+
inputValue,
|
|
112
|
+
cursorPosition,
|
|
113
|
+
onSelect,
|
|
114
|
+
onClose,
|
|
115
|
+
isVisible,
|
|
116
|
+
}: MentionAutocompleteProps) {
|
|
117
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
118
|
+
const listRef = useRef<HTMLDivElement>(null);
|
|
119
|
+
|
|
120
|
+
// Get the current mention query
|
|
121
|
+
const query = useMemo(
|
|
122
|
+
() => getMentionQuery(inputValue, cursorPosition),
|
|
123
|
+
[inputValue, cursorPosition]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Extract unique teams from agents
|
|
127
|
+
const teams = useMemo(() => {
|
|
128
|
+
const teamMap = new Map<string, Agent[]>();
|
|
129
|
+
agents.forEach((agent) => {
|
|
130
|
+
if (agent.team) {
|
|
131
|
+
const existing = teamMap.get(agent.team) || [];
|
|
132
|
+
existing.push(agent);
|
|
133
|
+
teamMap.set(agent.team, existing);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return teamMap;
|
|
137
|
+
}, [agents]);
|
|
138
|
+
|
|
139
|
+
// Filter options based on query
|
|
140
|
+
const options = useMemo((): MentionOption[] => {
|
|
141
|
+
if (query === null) return [];
|
|
142
|
+
|
|
143
|
+
const queryLower = query.toLowerCase();
|
|
144
|
+
const result: MentionOption[] = [];
|
|
145
|
+
|
|
146
|
+
// Add broadcast option if it matches
|
|
147
|
+
const broadcastMatches =
|
|
148
|
+
'*'.includes(queryLower) ||
|
|
149
|
+
'everyone'.includes(queryLower) ||
|
|
150
|
+
'all'.includes(queryLower) ||
|
|
151
|
+
'broadcast'.includes(queryLower) ||
|
|
152
|
+
queryLower === '';
|
|
153
|
+
|
|
154
|
+
if (broadcastMatches) {
|
|
155
|
+
result.push({
|
|
156
|
+
name: '*',
|
|
157
|
+
displayName: '@everyone',
|
|
158
|
+
description: 'Broadcast to all agents',
|
|
159
|
+
isBroadcast: true,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add team options if query matches "team" or a team name
|
|
164
|
+
const isTeamQuery = queryLower.startsWith('team:') || queryLower.startsWith('team');
|
|
165
|
+
const teamSearchQuery = queryLower.startsWith('team:')
|
|
166
|
+
? queryLower.substring(5)
|
|
167
|
+
: queryLower.replace(/^team/, '');
|
|
168
|
+
|
|
169
|
+
if (isTeamQuery || queryLower === '') {
|
|
170
|
+
teams.forEach((members, teamName) => {
|
|
171
|
+
const teamNameLower = teamName.toLowerCase();
|
|
172
|
+
if (
|
|
173
|
+
teamSearchQuery === '' ||
|
|
174
|
+
teamNameLower.includes(teamSearchQuery) ||
|
|
175
|
+
`team:${teamNameLower}`.includes(queryLower)
|
|
176
|
+
) {
|
|
177
|
+
result.push({
|
|
178
|
+
name: `team:${teamName}`,
|
|
179
|
+
displayName: `@team:${teamName}`,
|
|
180
|
+
description: `${members.length} agent${members.length !== 1 ? 's' : ''}: ${members.map(m => m.name).join(', ')}`,
|
|
181
|
+
isTeam: true,
|
|
182
|
+
memberCount: members.length,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Filter human users by username
|
|
189
|
+
const agentNames = new Set(agents.map(a => a.name.toLowerCase()));
|
|
190
|
+
const matchingHumans = humanUsers.filter((user) => {
|
|
191
|
+
const usernameLower = user.username.toLowerCase();
|
|
192
|
+
return usernameLower.includes(queryLower) &&
|
|
193
|
+
!agentNames.has(usernameLower); // Don't show if they're also an agent name
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
matchingHumans.forEach((user) => {
|
|
197
|
+
result.push({
|
|
198
|
+
name: user.username,
|
|
199
|
+
displayName: `@${user.username}`,
|
|
200
|
+
description: 'Human user',
|
|
201
|
+
isHuman: true,
|
|
202
|
+
avatarUrl: user.avatarUrl,
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Filter agents by name
|
|
207
|
+
const matchingAgents = agents.filter((agent) =>
|
|
208
|
+
agent.name.toLowerCase().includes(queryLower)
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
matchingAgents.forEach((agent) => {
|
|
212
|
+
result.push({
|
|
213
|
+
name: agent.name,
|
|
214
|
+
displayName: `@${agent.name}`,
|
|
215
|
+
description: agent.team ? `${agent.status || 'Agent'} · ${agent.team}` : (agent.status || 'Agent'),
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return result;
|
|
220
|
+
}, [query, agents, humanUsers, teams]);
|
|
221
|
+
|
|
222
|
+
// Reset selection when options change
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
setSelectedIndex(0);
|
|
225
|
+
}, [options.length]);
|
|
226
|
+
|
|
227
|
+
// Scroll selected item into view
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (!listRef.current) return;
|
|
230
|
+
const selected = listRef.current.querySelector('[data-selected="true"]');
|
|
231
|
+
if (selected) {
|
|
232
|
+
selected.scrollIntoView({ block: 'nearest' });
|
|
233
|
+
}
|
|
234
|
+
}, [selectedIndex]);
|
|
235
|
+
|
|
236
|
+
// Handle keyboard navigation
|
|
237
|
+
const handleKeyDown = useCallback(
|
|
238
|
+
(e: KeyboardEvent) => {
|
|
239
|
+
if (!isVisible || options.length === 0) return;
|
|
240
|
+
|
|
241
|
+
switch (e.key) {
|
|
242
|
+
case 'ArrowDown':
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
setSelectedIndex((prev) => (prev + 1) % options.length);
|
|
245
|
+
break;
|
|
246
|
+
case 'ArrowUp':
|
|
247
|
+
e.preventDefault();
|
|
248
|
+
setSelectedIndex((prev) => (prev - 1 + options.length) % options.length);
|
|
249
|
+
break;
|
|
250
|
+
case 'Enter':
|
|
251
|
+
case 'Tab':
|
|
252
|
+
e.preventDefault();
|
|
253
|
+
const selected = options[selectedIndex];
|
|
254
|
+
if (selected) {
|
|
255
|
+
const result = completeMentionInValue(inputValue, selected.name, cursorPosition);
|
|
256
|
+
onSelect(selected.name, result.value);
|
|
257
|
+
}
|
|
258
|
+
break;
|
|
259
|
+
case 'Escape':
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
onClose();
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
[isVisible, options, selectedIndex, inputValue, cursorPosition, onSelect, onClose]
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Register keyboard listener
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
if (isVisible) {
|
|
271
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
272
|
+
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
273
|
+
}
|
|
274
|
+
}, [isVisible, handleKeyDown]);
|
|
275
|
+
|
|
276
|
+
// Handle click on option
|
|
277
|
+
const handleClick = useCallback(
|
|
278
|
+
(option: MentionOption) => {
|
|
279
|
+
const result = completeMentionInValue(inputValue, option.name, cursorPosition);
|
|
280
|
+
onSelect(option.name, result.value);
|
|
281
|
+
},
|
|
282
|
+
[inputValue, cursorPosition, onSelect]
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
if (!isVisible || options.length === 0) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<div
|
|
291
|
+
className="absolute bottom-full left-0 right-0 max-h-[200px] overflow-y-auto bg-bg-card border border-border rounded-lg shadow-modal z-[100] mb-1"
|
|
292
|
+
ref={listRef}
|
|
293
|
+
>
|
|
294
|
+
{options.map((option, index) => (
|
|
295
|
+
<div
|
|
296
|
+
key={option.name}
|
|
297
|
+
data-selected={index === selectedIndex}
|
|
298
|
+
className={`flex items-center gap-2.5 py-2 px-3 cursor-pointer transition-colors duration-150 ${
|
|
299
|
+
index === selectedIndex ? 'bg-bg-hover' : 'hover:bg-bg-hover'
|
|
300
|
+
}`}
|
|
301
|
+
onClick={() => handleClick(option)}
|
|
302
|
+
onMouseEnter={() => setSelectedIndex(index)}
|
|
303
|
+
>
|
|
304
|
+
{/* Avatar/Icon */}
|
|
305
|
+
{option.isHuman && option.avatarUrl ? (
|
|
306
|
+
<img
|
|
307
|
+
src={option.avatarUrl}
|
|
308
|
+
alt={option.name}
|
|
309
|
+
className="w-7 h-7 rounded-md object-cover"
|
|
310
|
+
/>
|
|
311
|
+
) : (
|
|
312
|
+
<div
|
|
313
|
+
className="w-7 h-7 rounded-md flex items-center justify-center text-white text-[11px] font-semibold"
|
|
314
|
+
style={{
|
|
315
|
+
background: option.isBroadcast
|
|
316
|
+
? 'var(--color-warning, #f59e0b)'
|
|
317
|
+
: option.isTeam
|
|
318
|
+
? 'var(--color-accent-purple, #a855f7)'
|
|
319
|
+
: option.isHuman
|
|
320
|
+
? '#a855f7' // Purple for human users
|
|
321
|
+
: getAgentColor(option.name).primary,
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
324
|
+
{option.isBroadcast ? '*' : option.isTeam ? (
|
|
325
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
326
|
+
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
|
327
|
+
<circle cx="9" cy="7" r="4" />
|
|
328
|
+
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
|
329
|
+
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
330
|
+
</svg>
|
|
331
|
+
) : option.isHuman ? (
|
|
332
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
333
|
+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
|
334
|
+
<circle cx="12" cy="7" r="4" />
|
|
335
|
+
</svg>
|
|
336
|
+
) : getAgentInitials(option.name)}
|
|
337
|
+
</div>
|
|
338
|
+
)}
|
|
339
|
+
<div className="flex flex-col gap-0.5 min-w-0 flex-1">
|
|
340
|
+
<span className="text-sm font-medium text-text-primary">{option.displayName}</span>
|
|
341
|
+
<span className="text-xs text-text-muted truncate">{option.description}</span>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
))}
|
|
345
|
+
</div>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Hook to manage mention autocomplete state
|
|
351
|
+
*/
|
|
352
|
+
export function useMentionAutocomplete(agents: Agent[]) {
|
|
353
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
354
|
+
const [inputValue, setInputValue] = useState('');
|
|
355
|
+
const [cursorPosition, setCursorPosition] = useState(0);
|
|
356
|
+
|
|
357
|
+
const handleInputChange = useCallback((value: string, cursorPos: number) => {
|
|
358
|
+
setInputValue(value);
|
|
359
|
+
setCursorPosition(cursorPos);
|
|
360
|
+
|
|
361
|
+
// Show autocomplete if typing @mention at start
|
|
362
|
+
const query = getMentionQuery(value, cursorPos);
|
|
363
|
+
setIsVisible(query !== null);
|
|
364
|
+
}, []);
|
|
365
|
+
|
|
366
|
+
const handleSelect = useCallback((mention: string, newValue: string) => {
|
|
367
|
+
setInputValue(newValue);
|
|
368
|
+
setCursorPosition(newValue.indexOf(' ') + 1);
|
|
369
|
+
setIsVisible(false);
|
|
370
|
+
}, []);
|
|
371
|
+
|
|
372
|
+
const handleClose = useCallback(() => {
|
|
373
|
+
setIsVisible(false);
|
|
374
|
+
}, []);
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
isVisible,
|
|
378
|
+
inputValue,
|
|
379
|
+
cursorPosition,
|
|
380
|
+
setInputValue: handleInputChange,
|
|
381
|
+
handleSelect,
|
|
382
|
+
handleClose,
|
|
383
|
+
};
|
|
384
|
+
}
|