import * as React from "react"; import { useTranslation } from "react-i18next"; import { Activity, Bot, CheckCircle2, Clock, Pencil, RefreshCw, Search, SlidersHorizontal, Users, } from "lucide-react"; import { listAgents } from "@/api/agents"; import { listTeamConfigs, listTeamRuns, listTeams } from "@/api"; import { ApiErrorState } from "@/components/shared/ApiErrorState"; import { EmptyState } from "@/components/shared/EmptyState"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { MetricCard } from "@/components/shared/MetricCard"; import { PageHeader } from "@/components/shared/PageHeader"; import { SearchInput } from "@/components/shared/SearchInput"; import { StatusBadge } from "@/components/shared/StatusBadge"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Select } from "@/components/ui/select"; import { toast } from "@/components/ui/toaster"; import { cn, relativeTime } from "@/lib/utils"; import type { AgentDefinition, TeamConfig, TeamDefinition, TeamRun, TeamStatus } from "@/types"; import { CreateTeamDialog, EditTeamDialog } from "./components/CreateTeamDialog"; import { TeamOverview } from "./components/TeamOverview"; import { TeamRuns } from "./components/TeamRuns"; type StatusFilter = "all" | TeamStatus; type SortMode = "recent" | "name" | "status"; export function TeamsPage() { const { t } = useTranslation(); const [teams, setTeams] = React.useState([]); const [configs, setConfigs] = React.useState([]); const [runs, setRuns] = React.useState([]); const [agents, setAgents] = React.useState([]); const [selectedTeamId, setSelectedTeamId] = React.useState(); const [search, setSearch] = React.useState(""); const [statusFilter, setStatusFilter] = React.useState("all"); const [sortMode, setSortMode] = React.useState("recent"); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(); const [relatedLoading, setRelatedLoading] = React.useState(false); const [createOpen, setCreateOpen] = React.useState(false); const [editOpen, setEditOpen] = React.useState(false); const selectedTeam = teams.find((team) => team.id === selectedTeamId); const sortedConfigs = React.useMemo( () => [...configs].sort((a, b) => b.version_no - a.version_no), [configs], ); const sortedRuns = React.useMemo( () => [...runs].sort((a, b) => new Date(b.created_time).getTime() - new Date(a.created_time).getTime()), [runs], ); const activeConfig = sortedConfigs[0]; const failedRunCount = sortedRuns.filter((run) => run.status === "failed").length; const activeTeams = teams.filter((team) => team.status === "active").length; const draftTeams = teams.filter((team) => team.status === "draft").length; const agentNameById = React.useMemo(() => { const result = new Map(); agents.forEach((agent) => result.set(agent.id, agent.name)); return result; }, [agents]); const filtered = teams .filter((team) => { const text = `${team.name} ${team.team_type} ${team.description ?? ""}`.toLowerCase(); const matchesSearch = text.includes(search.toLowerCase()); const matchesStatus = statusFilter === "all" || team.status === statusFilter; return matchesSearch && matchesStatus; }) .sort((first, second) => { if (sortMode === "name") return first.name.localeCompare(second.name); if (sortMode === "status") return first.status.localeCompare(second.status) || first.name.localeCompare(second.name); return new Date(second.created_time).getTime() - new Date(first.created_time).getTime(); }); const hasFilters = search.length > 0 || statusFilter !== "all" || sortMode !== "recent"; const load = React.useCallback(async () => { setLoading(true); setError(undefined); try { const [data, agentData] = await Promise.all([ listTeams(), listAgents().catch(() => [] as AgentDefinition[]), ]); setTeams(data); setAgents(agentData); setSelectedTeamId((current) => current ?? data[0]?.id); } catch (err) { setError(err instanceof Error ? err.message : t("errors.failedToLoad")); } finally { setLoading(false); } }, [t]); const reloadDetails = React.useCallback(async () => { if (!selectedTeamId) return; setRelatedLoading(true); setConfigs([]); setRuns([]); try { const [configData, runData] = await Promise.all([listTeamConfigs(selectedTeamId), listTeamRuns(selectedTeamId)]); setConfigs(configData); setRuns(runData); } catch { toast.error(t("errors.failedToLoad")); } finally { setRelatedLoading(false); } }, [selectedTeamId, t]); React.useEffect(() => { void load(); }, [load]); React.useEffect(() => { if (selectedTeamId) void reloadDetails(); }, [selectedTeamId, reloadDetails]); React.useEffect(() => { if (!selectedTeamId && teams[0]) setSelectedTeamId(teams[0].id); }, [selectedTeamId, teams]); function clearFilters() { setSearch(""); setStatusFilter("all"); setSortMode("recent"); } if (loading) return ; if (error) return void load()} />; return (
} />
{t("teams.teamDirectory")} {filtered.length} / {teams.length} - {draftTeams} {t("common.draft")}
setSortMode(event.target.value as SortMode)} options={[ { value: "recent", label: t("teams.newestFirst") }, { value: "name", label: t("common.name") }, { value: "status", label: t("common.status") }, ]} />
{hasFilters ? ( ) : null} {filtered.length ? (
{filtered.map((team) => ( setSelectedTeamId(team.id)} /> ))}
) : ( )}
{selectedTeam?.name ?? t("teams.teamCockpit")} {selectedTeam ? : null}
{selectedTeam ? teamValueLabel(t, selectedTeam.team_type) : t("teams.selectTeamInspect")}
{selectedTeam && activeConfig ? ( {sortedRuns.length} {t("common.runs")} ) : null} {selectedTeam ? ( ) : null}
{selectedTeam ? (
{ setRuns((current) => [run, ...current.filter((item) => item.id !== run.id)]); }} />
) : ( setCreateOpen(true)} /> )}
{ setSelectedTeamId(team.id); setSearch(""); void load(); }} /> { setTeams((current) => current.map((item) => (item.id === team.id ? team : item))); void reloadDetails(); }} />
); } function TeamDirectoryItem({ team, active, t, onClick, }: { team: TeamDefinition; active?: boolean; t: (key: string, options?: Record) => string; onClick: () => void; }) { return ( ); } function readableLabel(value: string) { return value.split(/[_-]+/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" "); } function teamValueLabel(t: (key: string, options?: Record) => string, value: string) { const translationKey = TEAM_VALUE_LABEL_KEYS[value] ?? value; return t(`teams.${translationKey}`, { defaultValue: readableLabel(value) }); } const TEAM_VALUE_LABEL_KEYS: Record = { collaborative: "collaborative", debate: "debate", parallel: "parallel", pipeline: "pipeline", supervisor: "supervisor", };