agent-ecosystem/src/renderer/components/team/members/MemberDetailDialog.tsx
iliya c99a9cfc48 feat: integrate CLI installer service and UI components
- Introduced the CliInstallerService to manage CLI installation, including status checks and installation processes.
- Added IPC handlers for CLI installer operations, enabling communication between main and renderer processes.
- Implemented the CliStatusBanner and CliStatusSection components to display CLI installation status and controls in the dashboard and settings.
- Enhanced the dashboard and settings UI to provide users with real-time feedback on CLI installation progress and errors.
- Updated the TeamDataService to support task clarification features, improving task management capabilities.
2026-02-26 17:58:51 +02:00

206 lines
7 KiB
TypeScript

import { useMemo, useState } from 'react';
import { Button } from '@renderer/components/ui/button';
import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@renderer/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
import { useMemberStats } from '@renderer/hooks/useMemberStats';
import { BarChart3, FileText, ListPlus, MessageSquare, UserMinus } from 'lucide-react';
import { MemberDetailHeader } from './MemberDetailHeader';
import { MemberDetailStats, type MemberDetailTab } from './MemberDetailStats';
import { MemberLogsTab } from './MemberLogsTab';
import { MemberMessagesTab } from './MemberMessagesTab';
import { MemberStatsTab } from './MemberStatsTab';
import { MemberTasksTab } from './MemberTasksTab';
import type {
InboxMessage,
LeadActivityState,
ResolvedTeamMember,
TeamTaskWithKanban,
} from '@shared/types';
interface MemberDetailDialogProps {
open: boolean;
member: ResolvedTeamMember | null;
teamName: string;
tasks: TeamTaskWithKanban[];
messages: InboxMessage[];
isTeamAlive?: boolean;
isTeamProvisioning?: boolean;
leadActivity?: LeadActivityState;
onClose: () => void;
onSendMessage: () => void;
onAssignTask: () => void;
onTaskClick: (task: TeamTaskWithKanban) => void;
onRemoveMember?: () => void;
onUpdateRole?: (memberName: string, role: string | undefined) => Promise<void> | void;
updatingRole?: boolean;
onViewMemberChanges?: (memberName: string, filePath?: string) => void;
}
export const MemberDetailDialog = ({
open,
member,
teamName,
tasks,
messages,
isTeamAlive,
isTeamProvisioning,
leadActivity,
onClose,
onSendMessage,
onAssignTask,
onTaskClick,
onRemoveMember,
onUpdateRole,
updatingRole,
onViewMemberChanges,
}: MemberDetailDialogProps): React.JSX.Element | null => {
const memberTasks = useMemo(
() => (member ? tasks.filter((t) => t.owner === member.name) : []),
[tasks, member]
);
const memberMessages = useMemo(
() => (member ? messages.filter((m) => m.from === member.name || m.to === member.name) : []),
[messages, member]
);
const inProgressTasks = useMemo(
() => memberTasks.filter((t) => t.status === 'in_progress').length,
[memberTasks]
);
const completedTasks = useMemo(
() => memberTasks.filter((t) => t.status === 'completed').length,
[memberTasks]
);
const [activeTab, setActiveTab] = useState<MemberDetailTab>('tasks');
const {
stats: memberStats,
loading: statsLoading,
error: statsError,
} = useMemberStats(teamName, member?.name ?? null);
const totalTokens = memberStats ? memberStats.inputTokens + memberStats.outputTokens : null;
if (!member) return null;
return (
<Dialog open={open} onOpenChange={(nextOpen) => !nextOpen && onClose()}>
<DialogContent className="min-w-0 sm:max-w-4xl">
<div className="flex items-start gap-4">
<DialogHeader className="shrink-0">
<MemberDetailHeader
member={member}
isTeamAlive={isTeamAlive}
isTeamProvisioning={isTeamProvisioning}
leadActivity={member.agentType === 'team-lead' ? leadActivity : undefined}
onUpdateRole={
onUpdateRole ? (newRole) => onUpdateRole(member.name, newRole) : undefined
}
updatingRole={updatingRole}
/>
</DialogHeader>
<MemberDetailStats
totalTasks={memberTasks.length}
inProgressTasks={inProgressTasks}
completedTasks={completedTasks}
messageCount={memberMessages.length}
totalTokens={totalTokens}
statsLoading={statsLoading}
statsComputedAt={memberStats?.computedAt}
onTabChange={setActiveTab}
/>
</div>
<Tabs
value={activeTab}
onValueChange={(v) => setActiveTab(v as MemberDetailTab)}
className="min-w-0 overflow-hidden"
>
<TabsList className="w-full">
<TabsTrigger value="tasks" className="flex-1 gap-1.5">
Tasks
{memberTasks.length > 0 && (
<span className="rounded-full bg-[var(--color-surface)] px-1.5 text-[10px]">
{memberTasks.length}
</span>
)}
</TabsTrigger>
<TabsTrigger value="messages" className="flex-1 gap-1.5">
Messages
{memberMessages.length > 0 && (
<span className="rounded-full bg-[var(--color-surface)] px-1.5 text-[10px]">
{memberMessages.length}
</span>
)}
</TabsTrigger>
<TabsTrigger value="stats" className="flex-1 gap-1.5">
<BarChart3 size={12} />
Stats
</TabsTrigger>
<TabsTrigger value="logs" className="flex-1 gap-1.5">
<FileText size={12} />
Logs
</TabsTrigger>
</TabsList>
<TabsContent value="tasks">
<MemberTasksTab tasks={memberTasks} onTaskClick={onTaskClick} />
</TabsContent>
<TabsContent value="messages">
<MemberMessagesTab messages={memberMessages} teamName={teamName} />
</TabsContent>
<TabsContent value="stats">
<MemberStatsTab
teamName={teamName}
memberName={member.name}
prefetchedStats={memberStats}
prefetchedLoading={statsLoading}
prefetchedError={statsError}
onFileClick={(filePath) => onViewMemberChanges?.(member.name, filePath)}
onShowAllFiles={() => onViewMemberChanges?.(member.name)}
/>
</TabsContent>
<TabsContent value="logs" className="min-w-0 overflow-hidden">
<MemberLogsTab teamName={teamName} memberName={member.name} />
</TabsContent>
</Tabs>
<DialogFooter>
{member.removedAt ? (
<span className="text-xs text-[var(--color-text-muted)]">
Removed {new Date(member.removedAt).toLocaleDateString()}
</span>
) : (
<>
<Button variant="outline" size="sm" className="gap-1.5" onClick={onSendMessage}>
<MessageSquare size={14} />
Send Message
</Button>
<Button variant="outline" size="sm" className="gap-1.5" onClick={onAssignTask}>
<ListPlus size={14} />
Assign Task
</Button>
{onRemoveMember && member.agentType !== 'team-lead' && (
<Button
variant="outline"
size="sm"
className="gap-1.5 text-red-400 hover:bg-red-500/10 hover:text-red-300"
onClick={onRemoveMember}
>
<UserMinus size={14} />
Remove
</Button>
)}
</>
)}
</DialogFooter>
</DialogContent>
</Dialog>
);
};