import React from 'react'; import { toast } from '@/components/ui'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu'; import { Icon } from "@/components/icon/Icon"; import { cn } from '@/lib/utils'; import { useAgentGroupsStore, type AgentGroup } from '@/stores/useAgentGroupsStore'; import { useAllSessionStatuses } from '@/sync/sync-context'; import { useI18n } from '@/lib/i18n'; const formatRelativeTime = (timestamp: number): { unit: 'now' | 'minutes' | 'hours' | 'days'; count?: number } => { const now = Date.now(); const diff = now - timestamp; const minutes = Math.floor(diff / (60 * 1000)); const hours = Math.floor(diff / (60 * 60 * 1000)); const days = Math.floor(diff / (24 * 60 * 60 * 1000)); if (minutes < 1) return { unit: 'now' }; if (minutes < 60) return { unit: 'minutes', count: minutes }; if (hours < 24) return { unit: 'hours', count: hours }; return { unit: 'days', count: days }; }; interface AgentGroupItemProps { group: AgentGroup; isSelected: boolean; isBusy: boolean; onSelect: () => void; } const AgentGroupItem: React.FC = ({ group, isSelected, isBusy, onSelect }) => { const { t } = useI18n(); const [menuOpen, setMenuOpen] = React.useState(false); const [contextMenuOpen, setContextMenuOpen] = React.useState(false); const [confirmOpen, setConfirmOpen] = React.useState(false); const [isDeleting, setIsDeleting] = React.useState(false); const deleteGroupSessions = useAgentGroupsStore((s) => s.deleteGroupSessions); const handleDeleteGroup = React.useCallback(async () => { if (isDeleting) return; setIsDeleting(true); toast.info(t('agentManager.sidebar.toast.deletingGroup', { group: group.name })); const { failedIds, failedWorktreePaths } = await deleteGroupSessions(group.sessions, { removeWorktrees: true }); if (failedIds.length === 0 && failedWorktreePaths.length === 0) { toast.success(t('agentManager.sidebar.toast.deletedGroup', { group: group.name })); } else { toast.error(t('agentManager.sidebar.toast.failedToDeleteGroup', { group: group.name })); } setIsDeleting(false); setConfirmOpen(false); }, [deleteGroupSessions, group.name, group.sessions, isDeleting, t]); const relativeTime = formatRelativeTime(group.lastActive); const renderGroupMenuItems = (Item: React.ElementType) => ( { e.stopPropagation(); setMenuOpen(false); setContextMenuOpen(false); setConfirmOpen(true); }} > {t('agentManager.sidebar.item.delete')} ); return ( <> { event.preventDefault(); setContextMenuOpen(true); }} /> } >
{ if (open) setContextMenuOpen(false); setMenuOpen(open); }}> {renderGroupMenuItems(DropdownMenuItem)}
{renderGroupMenuItems(ContextMenuItem)}
{t('agentManager.sidebar.dialog.deleteGroupTitle')} {t('agentManager.sidebar.dialog.deleteGroupDescription', { group: group.name })} ); }; interface AgentManagerSidebarProps { className?: string; groups: AgentGroup[]; selectedGroupName?: string | null; onGroupSelect?: (groupName: string) => void; onNewAgent?: () => void; } export const AgentManagerSidebar: React.FC = ({ className, groups, selectedGroupName, onGroupSelect, onNewAgent, }) => { const { t } = useI18n(); const [searchQuery, setSearchQuery] = React.useState(''); const [showAll, setShowAll] = React.useState(false); const isLoading = useAgentGroupsStore((s) => s.isLoading); // Session statuses for busy indicators const allStatuses = useAllSessionStatuses(); const busyGroups = React.useMemo(() => { const set = new Set(); for (const group of groups) { if (group.sessions.some((s) => allStatuses[s.id]?.type === 'busy')) { set.add(group.name); } } return set; }, [groups, allStatuses]); const MAX_VISIBLE = 5; const filteredGroups = React.useMemo(() => { if (!searchQuery.trim()) return groups; const query = searchQuery.toLowerCase(); return groups.filter(group => group.name.toLowerCase().includes(query) ); }, [searchQuery, groups]); const visibleGroups = showAll ? filteredGroups : filteredGroups.slice(0, MAX_VISIBLE); const remainingCount = filteredGroups.length - MAX_VISIBLE; return (
{/* Search Input */}
setSearchQuery(e.target.value)} placeholder={t('agentManager.sidebar.search.placeholder')} className="pl-8 h-8 rounded-lg border-border/40 bg-background/50 typography-meta" />
{/* New Agent Button */}
{/* Agent Groups Section Header */}
{t('agentManager.sidebar.section.agentGroups')} {isLoading && ( {t('agentManager.sidebar.state.loading')} )}
{/* Group List */} {visibleGroups.map((group) => ( onGroupSelect?.(group.name)} /> ))} {!showAll && remainingCount > 0 && ( )} {showAll && filteredGroups.length > MAX_VISIBLE && ( )} {!isLoading && filteredGroups.length === 0 && (

{searchQuery.trim() ? t('agentManager.sidebar.state.noGroupsFound') : t('agentManager.sidebar.state.noGroupsYet')}

{!searchQuery.trim() && (

{t('agentManager.sidebar.state.createToGetStarted')}

)}
)}
); };