@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.
- 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/{dYlczDQI12PIQ3tqq3N4Y → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
- /package/out/_next/static/{dYlczDQI12PIQ3tqq3N4Y → 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,711 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProjectList Component
|
|
3
|
+
*
|
|
4
|
+
* Displays projects with nested agents in a flat hierarchy.
|
|
5
|
+
* Each project is a collapsible section with its agents listed directly underneath.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useState, useMemo, useEffect } from 'react';
|
|
9
|
+
import type { Agent, Project } from '../types';
|
|
10
|
+
import { AgentCard } from './AgentCard';
|
|
11
|
+
import { STATUS_COLORS, getAgentColor } from '../lib/colors';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Gets the simple display name for an agent within a team group.
|
|
15
|
+
* Since the team header already shows context, just show the agent's role/name.
|
|
16
|
+
*
|
|
17
|
+
* Examples:
|
|
18
|
+
* - "Frontend-Lead" in team "Frontend" → "Lead"
|
|
19
|
+
* - "Lead" in team "Lead" → "Lead"
|
|
20
|
+
* - "backend-api" in team "backend" → "Api"
|
|
21
|
+
*/
|
|
22
|
+
function stripTeamPrefix(agentName: string, teamName: string): string {
|
|
23
|
+
const lowerAgent = agentName.toLowerCase();
|
|
24
|
+
const lowerTeam = teamName.toLowerCase().replace(/-?team$/i, '');
|
|
25
|
+
|
|
26
|
+
// Pattern 1: Team prefix with dash separator (e.g., "frontend-lead" → "lead")
|
|
27
|
+
if (lowerTeam && lowerAgent.startsWith(lowerTeam + '-')) {
|
|
28
|
+
const stripped = agentName.substring(lowerTeam.length + 1);
|
|
29
|
+
return capitalizeWords(stripped);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Pattern 2: Team prefix with underscore separator
|
|
33
|
+
if (lowerTeam && lowerAgent.startsWith(lowerTeam + '_')) {
|
|
34
|
+
const stripped = agentName.substring(lowerTeam.length + 1);
|
|
35
|
+
return capitalizeWords(stripped);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Pattern 3: Last segment of hyphenated name (e.g., "LeadFrontend-Dev" → "Dev")
|
|
39
|
+
const parts = agentName.split('-');
|
|
40
|
+
if (parts.length > 1) {
|
|
41
|
+
return capitalizeWords(parts[parts.length - 1]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// No prefix to strip, return capitalized original
|
|
45
|
+
return capitalizeWords(agentName);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Capitalizes words in a string (handles dash and underscore separators)
|
|
50
|
+
*/
|
|
51
|
+
function capitalizeWords(str: string): string {
|
|
52
|
+
return str
|
|
53
|
+
.split(/[-_]/)
|
|
54
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
55
|
+
.join(' ');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ProjectListProps {
|
|
59
|
+
projects: Project[];
|
|
60
|
+
localAgents?: Agent[];
|
|
61
|
+
/** Bridge-level agents like Architect that span all projects */
|
|
62
|
+
bridgeAgents?: Agent[];
|
|
63
|
+
currentProject?: string;
|
|
64
|
+
selectedAgent?: string;
|
|
65
|
+
searchQuery?: string;
|
|
66
|
+
/** Array of pinned agent names */
|
|
67
|
+
pinnedAgents?: string[];
|
|
68
|
+
/** Whether max pins has been reached */
|
|
69
|
+
isMaxPinned?: boolean;
|
|
70
|
+
onProjectSelect?: (project: Project) => void;
|
|
71
|
+
onAgentSelect?: (agent: Agent, project?: Project) => void;
|
|
72
|
+
onReleaseClick?: (agent: Agent) => void;
|
|
73
|
+
onLogsClick?: (agent: Agent) => void;
|
|
74
|
+
onProfileClick?: (agent: Agent) => void;
|
|
75
|
+
/** Handler for pin/unpin toggle */
|
|
76
|
+
onPinToggle?: (agent: Agent) => void;
|
|
77
|
+
compact?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function ProjectList({
|
|
81
|
+
projects,
|
|
82
|
+
localAgents = [],
|
|
83
|
+
bridgeAgents = [],
|
|
84
|
+
currentProject,
|
|
85
|
+
selectedAgent,
|
|
86
|
+
searchQuery = '',
|
|
87
|
+
pinnedAgents = [],
|
|
88
|
+
isMaxPinned = false,
|
|
89
|
+
onProjectSelect,
|
|
90
|
+
onAgentSelect,
|
|
91
|
+
onReleaseClick,
|
|
92
|
+
onLogsClick,
|
|
93
|
+
onProfileClick,
|
|
94
|
+
onPinToggle,
|
|
95
|
+
compact = false,
|
|
96
|
+
}: ProjectListProps) {
|
|
97
|
+
const [expandedProjects, setExpandedProjects] = useState<Set<string>>(
|
|
98
|
+
() => new Set(projects.map((p) => p.id))
|
|
99
|
+
);
|
|
100
|
+
const [isAllCollapsed, setIsAllCollapsed] = useState(false);
|
|
101
|
+
|
|
102
|
+
// Filter out system agents (setup agents and Dashboard) and human users
|
|
103
|
+
// These should not appear in the sidebar but can still send/receive messages
|
|
104
|
+
// Human users (isHuman or cli === 'dashboard') should appear in Direct Messages, not projects
|
|
105
|
+
const filterSystemAgents = (agents: Agent[]) =>
|
|
106
|
+
agents.filter(a =>
|
|
107
|
+
!a.name.startsWith('__setup__') &&
|
|
108
|
+
a.name !== 'Dashboard' &&
|
|
109
|
+
!a.isHuman &&
|
|
110
|
+
a.cli !== 'dashboard'
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Filter projects and agents based on search query
|
|
114
|
+
const filteredData = useMemo(() => {
|
|
115
|
+
const query = searchQuery.toLowerCase().trim();
|
|
116
|
+
|
|
117
|
+
// Always filter system agents first
|
|
118
|
+
const filteredLocalAgents = filterSystemAgents(localAgents);
|
|
119
|
+
const filteredBridgeAgents = filterSystemAgents(bridgeAgents);
|
|
120
|
+
const projectsWithFilteredAgents = projects.map(p => ({
|
|
121
|
+
...p,
|
|
122
|
+
agents: filterSystemAgents(p.agents),
|
|
123
|
+
}));
|
|
124
|
+
|
|
125
|
+
if (!query) {
|
|
126
|
+
return { projects: projectsWithFilteredAgents, localAgents: filteredLocalAgents, bridgeAgents: filteredBridgeAgents };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Filter local agents by search query (using pre-filtered agents)
|
|
130
|
+
const searchFilteredLocal = filteredLocalAgents.filter(
|
|
131
|
+
(a) =>
|
|
132
|
+
a.name.toLowerCase().includes(query) ||
|
|
133
|
+
a.currentTask?.toLowerCase().includes(query)
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Filter bridge agents by search query (using pre-filtered agents)
|
|
137
|
+
const searchFilteredBridge = filteredBridgeAgents.filter(
|
|
138
|
+
(a) =>
|
|
139
|
+
a.name.toLowerCase().includes(query) ||
|
|
140
|
+
a.currentTask?.toLowerCase().includes(query)
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Filter projects (show project if name matches OR any agent matches)
|
|
144
|
+
// Uses pre-filtered projects with system agents already removed
|
|
145
|
+
const searchFilteredProjects = projectsWithFilteredAgents
|
|
146
|
+
.map((project) => {
|
|
147
|
+
const projectNameMatches =
|
|
148
|
+
project.name?.toLowerCase().includes(query) ||
|
|
149
|
+
project.path.toLowerCase().includes(query);
|
|
150
|
+
|
|
151
|
+
const searchFilteredAgents = project.agents.filter(
|
|
152
|
+
(a) =>
|
|
153
|
+
a.name.toLowerCase().includes(query) ||
|
|
154
|
+
a.currentTask?.toLowerCase().includes(query)
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Include project if name matches or has matching agents
|
|
158
|
+
if (projectNameMatches || searchFilteredAgents.length > 0) {
|
|
159
|
+
return {
|
|
160
|
+
...project,
|
|
161
|
+
agents: projectNameMatches ? project.agents : searchFilteredAgents,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
})
|
|
166
|
+
.filter(Boolean) as Project[];
|
|
167
|
+
|
|
168
|
+
return { projects: searchFilteredProjects, localAgents: searchFilteredLocal, bridgeAgents: searchFilteredBridge };
|
|
169
|
+
}, [projects, localAgents, bridgeAgents, searchQuery]);
|
|
170
|
+
|
|
171
|
+
const toggleProject = (projectId: string) => {
|
|
172
|
+
setExpandedProjects((prev) => {
|
|
173
|
+
const next = new Set(prev);
|
|
174
|
+
if (next.has(projectId)) {
|
|
175
|
+
next.delete(projectId);
|
|
176
|
+
} else {
|
|
177
|
+
next.add(projectId);
|
|
178
|
+
}
|
|
179
|
+
return next;
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const toggleAll = () => {
|
|
184
|
+
if (isAllCollapsed) {
|
|
185
|
+
// Expand: restore all projects
|
|
186
|
+
setIsAllCollapsed(false);
|
|
187
|
+
const allProjectIds = new Set(filteredData.projects.map((p) => p.id));
|
|
188
|
+
if (filteredData.localAgents.length > 0) {
|
|
189
|
+
allProjectIds.add('__local__');
|
|
190
|
+
}
|
|
191
|
+
setExpandedProjects(allProjectIds);
|
|
192
|
+
} else {
|
|
193
|
+
// Collapse: hide everything
|
|
194
|
+
setIsAllCollapsed(true);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const totalAgents =
|
|
199
|
+
filteredData.localAgents.length +
|
|
200
|
+
filteredData.bridgeAgents.length +
|
|
201
|
+
filteredData.projects.reduce((sum, p) => sum + p.agents.length, 0);
|
|
202
|
+
|
|
203
|
+
if (totalAgents === 0 && projects.length === 0 && localAgents.length === 0) {
|
|
204
|
+
return (
|
|
205
|
+
<div className="flex flex-col items-center justify-center py-10 px-5 text-text-muted text-center">
|
|
206
|
+
<EmptyIcon />
|
|
207
|
+
<p>No projects or agents</p>
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (totalAgents === 0 && searchQuery) {
|
|
213
|
+
return (
|
|
214
|
+
<div className="flex flex-col items-center justify-center py-10 px-5 text-text-muted text-center">
|
|
215
|
+
<SearchIcon />
|
|
216
|
+
<p>No results for "{searchQuery}"</p>
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Only show bridge section when in bridge mode (multiple projects)
|
|
222
|
+
const isInBridgeMode = filteredData.projects.length > 1;
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div className="flex flex-col gap-1">
|
|
226
|
+
<div className="flex justify-between items-center py-2 px-3 text-xs text-text-muted">
|
|
227
|
+
<span>{totalAgents} {totalAgents === 1 ? 'agent' : 'agents'}</span>
|
|
228
|
+
<button
|
|
229
|
+
className="bg-transparent border-none text-accent cursor-pointer text-xs hover:underline"
|
|
230
|
+
onClick={toggleAll}
|
|
231
|
+
>
|
|
232
|
+
{isAllCollapsed ? 'Expand all' : 'Collapse all'}
|
|
233
|
+
</button>
|
|
234
|
+
</div>
|
|
235
|
+
|
|
236
|
+
{!isAllCollapsed && (
|
|
237
|
+
<>
|
|
238
|
+
{/* Bridge-level agents section (Architect, etc.) - only in bridge mode */}
|
|
239
|
+
{isInBridgeMode && filteredData.bridgeAgents.length > 0 && (
|
|
240
|
+
<BridgeSection
|
|
241
|
+
agents={filteredData.bridgeAgents}
|
|
242
|
+
selectedAgent={selectedAgent}
|
|
243
|
+
compact={compact}
|
|
244
|
+
pinnedAgents={pinnedAgents}
|
|
245
|
+
isMaxPinned={isMaxPinned}
|
|
246
|
+
onAgentSelect={(agent) => onAgentSelect?.(agent)}
|
|
247
|
+
onReleaseClick={onReleaseClick}
|
|
248
|
+
onLogsClick={onLogsClick}
|
|
249
|
+
onProfileClick={onProfileClick}
|
|
250
|
+
onPinToggle={onPinToggle}
|
|
251
|
+
/>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
{/* Local agents section (current project) - only when not in bridge mode */}
|
|
255
|
+
{!isInBridgeMode && filteredData.localAgents.length > 0 && (
|
|
256
|
+
<ProjectSection
|
|
257
|
+
project={{
|
|
258
|
+
id: '__local__',
|
|
259
|
+
path: '',
|
|
260
|
+
name: 'Local',
|
|
261
|
+
agents: filteredData.localAgents,
|
|
262
|
+
}}
|
|
263
|
+
isExpanded={expandedProjects.has('__local__')}
|
|
264
|
+
isCurrentProject={true}
|
|
265
|
+
selectedAgent={selectedAgent}
|
|
266
|
+
compact={compact}
|
|
267
|
+
pinnedAgents={pinnedAgents}
|
|
268
|
+
isMaxPinned={isMaxPinned}
|
|
269
|
+
onToggle={() => toggleProject('__local__')}
|
|
270
|
+
onAgentSelect={(agent) => onAgentSelect?.(agent)}
|
|
271
|
+
onReleaseClick={onReleaseClick}
|
|
272
|
+
onLogsClick={onLogsClick}
|
|
273
|
+
onProfileClick={onProfileClick}
|
|
274
|
+
onPinToggle={onPinToggle}
|
|
275
|
+
/>
|
|
276
|
+
)}
|
|
277
|
+
|
|
278
|
+
{/* Bridged projects */}
|
|
279
|
+
{filteredData.projects.map((project) => (
|
|
280
|
+
<ProjectSection
|
|
281
|
+
key={project.id}
|
|
282
|
+
project={project}
|
|
283
|
+
isExpanded={expandedProjects.has(project.id)}
|
|
284
|
+
isCurrentProject={project.id === currentProject}
|
|
285
|
+
selectedAgent={selectedAgent}
|
|
286
|
+
compact={compact}
|
|
287
|
+
isBridgeMode={isInBridgeMode}
|
|
288
|
+
pinnedAgents={pinnedAgents}
|
|
289
|
+
isMaxPinned={isMaxPinned}
|
|
290
|
+
onToggle={() => toggleProject(project.id)}
|
|
291
|
+
onProjectSelect={() => onProjectSelect?.(project)}
|
|
292
|
+
onAgentSelect={(agent) => onAgentSelect?.(agent, project)}
|
|
293
|
+
onReleaseClick={onReleaseClick}
|
|
294
|
+
onLogsClick={onLogsClick}
|
|
295
|
+
onProfileClick={onProfileClick}
|
|
296
|
+
onPinToggle={onPinToggle}
|
|
297
|
+
/>
|
|
298
|
+
))}
|
|
299
|
+
</>
|
|
300
|
+
)}
|
|
301
|
+
</div>
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
interface ProjectSectionProps {
|
|
306
|
+
project: Project;
|
|
307
|
+
isExpanded: boolean;
|
|
308
|
+
isCurrentProject: boolean;
|
|
309
|
+
selectedAgent?: string;
|
|
310
|
+
compact?: boolean;
|
|
311
|
+
/** Is this project part of a multi-project bridge setup */
|
|
312
|
+
isBridgeMode?: boolean;
|
|
313
|
+
pinnedAgents?: string[];
|
|
314
|
+
isMaxPinned?: boolean;
|
|
315
|
+
onToggle: () => void;
|
|
316
|
+
onProjectSelect?: () => void;
|
|
317
|
+
onAgentSelect?: (agent: Agent) => void;
|
|
318
|
+
onReleaseClick?: (agent: Agent) => void;
|
|
319
|
+
onLogsClick?: (agent: Agent) => void;
|
|
320
|
+
onProfileClick?: (agent: Agent) => void;
|
|
321
|
+
onPinToggle?: (agent: Agent) => void;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
interface TeamGroup {
|
|
325
|
+
name: string;
|
|
326
|
+
agents: Agent[];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function ProjectSection({
|
|
330
|
+
project,
|
|
331
|
+
isExpanded,
|
|
332
|
+
isCurrentProject,
|
|
333
|
+
selectedAgent,
|
|
334
|
+
compact,
|
|
335
|
+
isBridgeMode = false,
|
|
336
|
+
pinnedAgents = [],
|
|
337
|
+
isMaxPinned = false,
|
|
338
|
+
onToggle,
|
|
339
|
+
onProjectSelect,
|
|
340
|
+
onAgentSelect,
|
|
341
|
+
onReleaseClick,
|
|
342
|
+
onLogsClick,
|
|
343
|
+
onProfileClick,
|
|
344
|
+
onPinToggle,
|
|
345
|
+
}: ProjectSectionProps) {
|
|
346
|
+
const [expandedTeams, setExpandedTeams] = useState<Set<string>>(new Set());
|
|
347
|
+
|
|
348
|
+
const stats = useMemo(() => {
|
|
349
|
+
let online = 0;
|
|
350
|
+
let needsAttention = 0;
|
|
351
|
+
for (const agent of project.agents) {
|
|
352
|
+
if (agent.status === 'online') online++;
|
|
353
|
+
if (agent.needsAttention) needsAttention++;
|
|
354
|
+
}
|
|
355
|
+
return { online, needsAttention, total: project.agents.length };
|
|
356
|
+
}, [project.agents]);
|
|
357
|
+
|
|
358
|
+
// Group agents by team (optional user-defined grouping)
|
|
359
|
+
const { teams, ungroupedAgents } = useMemo(() => {
|
|
360
|
+
const teamMap = new Map<string, Agent[]>();
|
|
361
|
+
const ungrouped: Agent[] = [];
|
|
362
|
+
|
|
363
|
+
for (const agent of project.agents) {
|
|
364
|
+
if (agent.team) {
|
|
365
|
+
const existing = teamMap.get(agent.team) || [];
|
|
366
|
+
existing.push(agent);
|
|
367
|
+
teamMap.set(agent.team, existing);
|
|
368
|
+
} else {
|
|
369
|
+
ungrouped.push(agent);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const teams: TeamGroup[] = Array.from(teamMap.entries())
|
|
374
|
+
.map(([name, agents]) => ({ name, agents }))
|
|
375
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
376
|
+
|
|
377
|
+
return { teams, ungroupedAgents: ungrouped };
|
|
378
|
+
}, [project.agents]);
|
|
379
|
+
|
|
380
|
+
const toggleTeam = (teamName: string) => {
|
|
381
|
+
setExpandedTeams((prev) => {
|
|
382
|
+
const next = new Set(prev);
|
|
383
|
+
if (next.has(teamName)) {
|
|
384
|
+
next.delete(teamName);
|
|
385
|
+
} else {
|
|
386
|
+
next.add(teamName);
|
|
387
|
+
}
|
|
388
|
+
return next;
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// Auto-expand teams when project expands
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
if (isExpanded && expandedTeams.size === 0 && teams.length > 0) {
|
|
395
|
+
setExpandedTeams(new Set(teams.map((t) => t.name)));
|
|
396
|
+
}
|
|
397
|
+
}, [isExpanded, teams, expandedTeams]);
|
|
398
|
+
|
|
399
|
+
const projectColor = getAgentColor(project.name || project.id);
|
|
400
|
+
const displayName = project.name || project.path.split('/').pop() || project.id;
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
<div className="mb-1">
|
|
404
|
+
<button
|
|
405
|
+
className={`group flex items-center gap-2 w-full py-2.5 px-3 bg-none border-none cursor-pointer text-[13px] text-left rounded-md transition-colors duration-200 relative text-text-primary hover:bg-bg-hover ${
|
|
406
|
+
isCurrentProject ? 'bg-success-light' : ''
|
|
407
|
+
}`}
|
|
408
|
+
onClick={onToggle}
|
|
409
|
+
onDoubleClick={onProjectSelect}
|
|
410
|
+
style={{
|
|
411
|
+
'--project-color': projectColor.primary,
|
|
412
|
+
'--project-light': projectColor.light,
|
|
413
|
+
} as React.CSSProperties}
|
|
414
|
+
>
|
|
415
|
+
<div
|
|
416
|
+
className="absolute left-0 top-1 bottom-1 w-[3px] rounded-sm"
|
|
417
|
+
style={{ background: projectColor.primary }}
|
|
418
|
+
/>
|
|
419
|
+
<ChevronIcon expanded={isExpanded} />
|
|
420
|
+
<FolderIcon color={projectColor.primary} />
|
|
421
|
+
<span className="font-semibold text-text-primary whitespace-nowrap overflow-hidden text-ellipsis">{displayName}</span>
|
|
422
|
+
<span className="text-text-muted font-normal flex-shrink-0">({stats.total})</span>
|
|
423
|
+
|
|
424
|
+
<div className="ml-auto flex gap-2 flex-shrink-0">
|
|
425
|
+
{stats.online > 0 && (
|
|
426
|
+
<span className="flex items-center gap-1 text-[11px] text-text-dim">
|
|
427
|
+
<span
|
|
428
|
+
className="w-1.5 h-1.5 rounded-full"
|
|
429
|
+
style={{ backgroundColor: STATUS_COLORS.online }}
|
|
430
|
+
/>
|
|
431
|
+
{stats.online}
|
|
432
|
+
</span>
|
|
433
|
+
)}
|
|
434
|
+
{stats.needsAttention > 0 && (
|
|
435
|
+
<span className="flex items-center gap-1 text-[11px] text-text-dim">
|
|
436
|
+
<span
|
|
437
|
+
className="w-1.5 h-1.5 rounded-full"
|
|
438
|
+
style={{ backgroundColor: STATUS_COLORS.attention }}
|
|
439
|
+
/>
|
|
440
|
+
{stats.needsAttention}
|
|
441
|
+
</span>
|
|
442
|
+
)}
|
|
443
|
+
</div>
|
|
444
|
+
|
|
445
|
+
{project.lead?.connected && (
|
|
446
|
+
<span className="text-[#ffd700] text-xs ml-1" title={`Lead: ${project.lead.name}`}>
|
|
447
|
+
★
|
|
448
|
+
</span>
|
|
449
|
+
)}
|
|
450
|
+
|
|
451
|
+
{/* Switch button - shown in bridge mode for non-current projects */}
|
|
452
|
+
{isBridgeMode && !isCurrentProject && onProjectSelect && (
|
|
453
|
+
<span
|
|
454
|
+
role="button"
|
|
455
|
+
tabIndex={0}
|
|
456
|
+
className="ml-2 py-1 px-2 text-[10px] font-medium bg-accent-cyan/20 text-accent-cyan rounded cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent-cyan/30"
|
|
457
|
+
onClick={(e) => {
|
|
458
|
+
e.stopPropagation();
|
|
459
|
+
onProjectSelect();
|
|
460
|
+
}}
|
|
461
|
+
onKeyDown={(e) => {
|
|
462
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
463
|
+
e.preventDefault();
|
|
464
|
+
e.stopPropagation();
|
|
465
|
+
onProjectSelect();
|
|
466
|
+
}
|
|
467
|
+
}}
|
|
468
|
+
title="Switch to this project"
|
|
469
|
+
>
|
|
470
|
+
Switch
|
|
471
|
+
</span>
|
|
472
|
+
)}
|
|
473
|
+
</button>
|
|
474
|
+
|
|
475
|
+
{isExpanded && (
|
|
476
|
+
<div className="py-1 pl-5 flex flex-col gap-1">
|
|
477
|
+
{/* Team groups (optional user-defined) */}
|
|
478
|
+
{teams.map((team) => (
|
|
479
|
+
<div key={team.name} className="mb-0.5">
|
|
480
|
+
<button
|
|
481
|
+
className="flex items-center gap-1.5 w-full py-1.5 px-2 bg-none border-none cursor-pointer text-xs text-left rounded transition-colors duration-200 text-text-secondary hover:bg-bg-hover"
|
|
482
|
+
onClick={() => toggleTeam(team.name)}
|
|
483
|
+
>
|
|
484
|
+
<ChevronIcon expanded={expandedTeams.has(team.name)} />
|
|
485
|
+
<TeamIcon />
|
|
486
|
+
<span className="font-medium text-text-secondary">{team.name}</span>
|
|
487
|
+
<span className="text-text-muted font-normal">({team.agents.length})</span>
|
|
488
|
+
</button>
|
|
489
|
+
{expandedTeams.has(team.name) && (
|
|
490
|
+
<div className="py-0.5 pl-4 flex flex-col gap-1">
|
|
491
|
+
{team.agents.map((agent) => (
|
|
492
|
+
<AgentCard
|
|
493
|
+
key={agent.name}
|
|
494
|
+
agent={agent}
|
|
495
|
+
isSelected={agent.name === selectedAgent}
|
|
496
|
+
compact={compact}
|
|
497
|
+
displayNameOverride={stripTeamPrefix(agent.name, team.name)}
|
|
498
|
+
isPinned={pinnedAgents.includes(agent.name)}
|
|
499
|
+
isMaxPinned={isMaxPinned}
|
|
500
|
+
onClick={onAgentSelect}
|
|
501
|
+
onReleaseClick={onReleaseClick}
|
|
502
|
+
onLogsClick={onLogsClick}
|
|
503
|
+
onProfileClick={onProfileClick}
|
|
504
|
+
onPinToggle={onPinToggle}
|
|
505
|
+
/>
|
|
506
|
+
))}
|
|
507
|
+
</div>
|
|
508
|
+
)}
|
|
509
|
+
</div>
|
|
510
|
+
))}
|
|
511
|
+
|
|
512
|
+
{/* Ungrouped agents (no team assigned) */}
|
|
513
|
+
{ungroupedAgents.map((agent) => (
|
|
514
|
+
<AgentCard
|
|
515
|
+
key={agent.name}
|
|
516
|
+
agent={agent}
|
|
517
|
+
isSelected={agent.name === selectedAgent}
|
|
518
|
+
compact={compact}
|
|
519
|
+
isPinned={pinnedAgents.includes(agent.name)}
|
|
520
|
+
isMaxPinned={isMaxPinned}
|
|
521
|
+
onClick={onAgentSelect}
|
|
522
|
+
onReleaseClick={onReleaseClick}
|
|
523
|
+
onLogsClick={onLogsClick}
|
|
524
|
+
onProfileClick={onProfileClick}
|
|
525
|
+
onPinToggle={onPinToggle}
|
|
526
|
+
/>
|
|
527
|
+
))}
|
|
528
|
+
</div>
|
|
529
|
+
)}
|
|
530
|
+
</div>
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Bridge Section - displays bridge-level agents (Architect, etc.)
|
|
536
|
+
*/
|
|
537
|
+
interface BridgeSectionProps {
|
|
538
|
+
agents: Agent[];
|
|
539
|
+
selectedAgent?: string;
|
|
540
|
+
compact?: boolean;
|
|
541
|
+
pinnedAgents?: string[];
|
|
542
|
+
isMaxPinned?: boolean;
|
|
543
|
+
onAgentSelect?: (agent: Agent) => void;
|
|
544
|
+
onReleaseClick?: (agent: Agent) => void;
|
|
545
|
+
onLogsClick?: (agent: Agent) => void;
|
|
546
|
+
onProfileClick?: (agent: Agent) => void;
|
|
547
|
+
onPinToggle?: (agent: Agent) => void;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function BridgeSection({
|
|
551
|
+
agents,
|
|
552
|
+
selectedAgent,
|
|
553
|
+
compact,
|
|
554
|
+
pinnedAgents = [],
|
|
555
|
+
isMaxPinned = false,
|
|
556
|
+
onAgentSelect,
|
|
557
|
+
onReleaseClick,
|
|
558
|
+
onLogsClick,
|
|
559
|
+
onProfileClick,
|
|
560
|
+
onPinToggle,
|
|
561
|
+
}: BridgeSectionProps) {
|
|
562
|
+
const [isExpanded, setIsExpanded] = useState(true);
|
|
563
|
+
|
|
564
|
+
return (
|
|
565
|
+
<div className="mb-2">
|
|
566
|
+
<button
|
|
567
|
+
className="flex items-center gap-2 w-full py-2.5 px-3 bg-none border-none cursor-pointer text-[13px] text-left rounded-md transition-colors duration-200 relative text-text-primary hover:bg-accent-purple/10"
|
|
568
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
569
|
+
>
|
|
570
|
+
<div
|
|
571
|
+
className="absolute left-0 top-1 bottom-1 w-[3px] rounded-sm bg-accent-purple"
|
|
572
|
+
/>
|
|
573
|
+
<ChevronIcon expanded={isExpanded} />
|
|
574
|
+
<BridgeIcon />
|
|
575
|
+
<span className="font-semibold text-text-primary">Bridge</span>
|
|
576
|
+
<span className="text-text-muted font-normal flex-shrink-0">({agents.length})</span>
|
|
577
|
+
</button>
|
|
578
|
+
|
|
579
|
+
{isExpanded && (
|
|
580
|
+
<div className="py-1 pl-5 flex flex-col gap-1">
|
|
581
|
+
{agents.map((agent) => (
|
|
582
|
+
<AgentCard
|
|
583
|
+
key={agent.name}
|
|
584
|
+
agent={agent}
|
|
585
|
+
isSelected={agent.name === selectedAgent}
|
|
586
|
+
compact={compact}
|
|
587
|
+
isPinned={pinnedAgents.includes(agent.name)}
|
|
588
|
+
isMaxPinned={isMaxPinned}
|
|
589
|
+
onClick={onAgentSelect}
|
|
590
|
+
onReleaseClick={onReleaseClick}
|
|
591
|
+
onLogsClick={onLogsClick}
|
|
592
|
+
onProfileClick={onProfileClick}
|
|
593
|
+
onPinToggle={onPinToggle}
|
|
594
|
+
/>
|
|
595
|
+
))}
|
|
596
|
+
</div>
|
|
597
|
+
)}
|
|
598
|
+
</div>
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function BridgeIcon() {
|
|
603
|
+
return (
|
|
604
|
+
<svg
|
|
605
|
+
className="flex-shrink-0 text-accent-purple"
|
|
606
|
+
width="16"
|
|
607
|
+
height="16"
|
|
608
|
+
viewBox="0 0 24 24"
|
|
609
|
+
fill="none"
|
|
610
|
+
stroke="currentColor"
|
|
611
|
+
strokeWidth="2"
|
|
612
|
+
strokeLinecap="round"
|
|
613
|
+
strokeLinejoin="round"
|
|
614
|
+
>
|
|
615
|
+
<circle cx="12" cy="12" r="3" />
|
|
616
|
+
<circle cx="5" cy="5" r="2" />
|
|
617
|
+
<circle cx="19" cy="5" r="2" />
|
|
618
|
+
<circle cx="5" cy="19" r="2" />
|
|
619
|
+
<circle cx="19" cy="19" r="2" />
|
|
620
|
+
<line x1="9.5" y1="9.5" x2="6.5" y2="6.5" />
|
|
621
|
+
<line x1="14.5" y1="9.5" x2="17.5" y2="6.5" />
|
|
622
|
+
<line x1="9.5" y1="14.5" x2="6.5" y2="17.5" />
|
|
623
|
+
<line x1="14.5" y1="14.5" x2="17.5" y2="17.5" />
|
|
624
|
+
</svg>
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function ChevronIcon({ expanded }: { expanded: boolean }) {
|
|
629
|
+
return (
|
|
630
|
+
<svg
|
|
631
|
+
className={`transition-transform duration-200 text-text-muted flex-shrink-0 ${expanded ? 'rotate-90' : ''}`}
|
|
632
|
+
width="16"
|
|
633
|
+
height="16"
|
|
634
|
+
viewBox="0 0 24 24"
|
|
635
|
+
fill="none"
|
|
636
|
+
stroke="currentColor"
|
|
637
|
+
strokeWidth="2"
|
|
638
|
+
>
|
|
639
|
+
<polyline points="9 18 15 12 9 6" />
|
|
640
|
+
</svg>
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function FolderIcon({ color }: { color: string }) {
|
|
645
|
+
return (
|
|
646
|
+
<svg
|
|
647
|
+
className="flex-shrink-0"
|
|
648
|
+
style={{ color }}
|
|
649
|
+
width="16"
|
|
650
|
+
height="16"
|
|
651
|
+
viewBox="0 0 24 24"
|
|
652
|
+
fill="none"
|
|
653
|
+
stroke="currentColor"
|
|
654
|
+
strokeWidth="2"
|
|
655
|
+
>
|
|
656
|
+
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
|
657
|
+
</svg>
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function TeamIcon() {
|
|
662
|
+
return (
|
|
663
|
+
<svg
|
|
664
|
+
className="text-text-muted flex-shrink-0"
|
|
665
|
+
width="14"
|
|
666
|
+
height="14"
|
|
667
|
+
viewBox="0 0 24 24"
|
|
668
|
+
fill="none"
|
|
669
|
+
stroke="currentColor"
|
|
670
|
+
strokeWidth="2"
|
|
671
|
+
>
|
|
672
|
+
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
|
673
|
+
<circle cx="9" cy="7" r="4" />
|
|
674
|
+
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
|
675
|
+
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
676
|
+
</svg>
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function EmptyIcon() {
|
|
681
|
+
return (
|
|
682
|
+
<svg
|
|
683
|
+
className="mb-3 opacity-50"
|
|
684
|
+
width="48"
|
|
685
|
+
height="48"
|
|
686
|
+
viewBox="0 0 24 24"
|
|
687
|
+
fill="none"
|
|
688
|
+
stroke="currentColor"
|
|
689
|
+
strokeWidth="1"
|
|
690
|
+
>
|
|
691
|
+
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
|
692
|
+
</svg>
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function SearchIcon() {
|
|
697
|
+
return (
|
|
698
|
+
<svg
|
|
699
|
+
className="mb-3 opacity-50"
|
|
700
|
+
width="48"
|
|
701
|
+
height="48"
|
|
702
|
+
viewBox="0 0 24 24"
|
|
703
|
+
fill="none"
|
|
704
|
+
stroke="currentColor"
|
|
705
|
+
strokeWidth="1"
|
|
706
|
+
>
|
|
707
|
+
<circle cx="11" cy="11" r="8" />
|
|
708
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
709
|
+
</svg>
|
|
710
|
+
);
|
|
711
|
+
}
|