import { useCallback, useMemo, useState } from "react"; import { usePolling } from "../hooks/usePolling"; import { fetchRoles } from "../api/client"; import type { RoleInfo } from "../types"; import AsyncPageState from "./ui/AsyncPageState"; import Button from "./ui/Button"; import Chip from "./ui/Chip"; import RoleBadge from "./RoleBadge"; import PageHero from "./ui/PageHero"; import PageSectionCard from "./ui/PageSectionCard"; import SummaryStat from "./ui/SummaryStat"; import StatusDot from "./ui/StatusDot"; import RoleEditor from "./RoleEditor"; import { useI18n } from "../i18n"; import { roleListChangeKey } from "../utils/pollingKeys"; interface RoleStatusProps { workspace: string; } function getRoleState(role: RoleInfo, copy: ReturnType["copy"]) { if (role.pending > 0) { return { label: copy.roleStatus.states.attention, dotClassName: "bg-[color:var(--app-accent-warm)]", textClassName: "text-[color:var(--app-accent-warm)]", }; } if (role.session) { return { label: copy.roleStatus.states.active, dotClassName: "app-dot-success", textClassName: "app-text-success", }; } return { label: copy.roleStatus.states.idle, dotClassName: "app-dot-idle", textClassName: "app-text-idle", }; } function getRolePriority(role: RoleInfo) { return (role.pending > 0 ? 2 : 0) + (role.session ? 1 : 0); } export default function RoleStatus({ workspace }: RoleStatusProps) { const { copy, formatAbsoluteDateTime, formatRelativeTime } = useI18n(); const fetcher = useCallback(() => fetchRoles(workspace), [workspace]); const { data, loading, error, refresh } = usePolling( fetcher, 5000, { getChangeKey: roleListChangeKey }, ); // Editor state: string = editing role name, undefined = closed const [editorTarget, setEditorTarget] = useState(); const sortedRoles = useMemo(() => { if (!data) return []; return [...data].sort((left, right) => { if (left.sort_order !== right.sort_order) { return left.sort_order - right.sort_order; } const priorityDiff = getRolePriority(right) - getRolePriority(left); if (priorityDiff !== 0) { return priorityDiff; } return left.name.localeCompare(right.name); }); }, [data]); const activeSessions = data?.filter((role) => role.session).length ?? 0; const attentionCount = data?.filter((role) => role.pending > 0).length ?? 0; const idleCount = data ? data.length - activeSessions : 0; const waitingMessages = data?.reduce((sum, role) => sum + role.pending, 0) ?? 0; return ( 0)} error={error} loadingEyebrow={copy.roleStatus.eyebrow} loadingTitle={copy.roleStatus.loadingTitle} errorEyebrow={copy.roleStatus.eyebrow} errorTitle={copy.roleStatus.errorTitle} emptyEyebrow={copy.roleStatus.eyebrow} emptyTitle={copy.roleStatus.emptyTitle} emptyDetail={copy.roleStatus.emptyDetail} retryLabel={copy.common.retry} onRetry={refresh} >
} /> {sortedRoles.length}} className="rounded-[26px]" headerClassName="px-4 py-4 sm:px-5" bodyClassName="grid gap-3 p-4 sm:p-5 md:grid-cols-2" > {sortedRoles.map((role) => { const state = getRoleState(role, copy); return (
{state.label} {role.pending > 0 && ( {copy.roleStatus.states.waiting(role.pending)} )}

{copy.roleStatus.conciseDescription[role.name as keyof typeof copy.roleStatus.conciseDescription] ?? role.description}

{role.session?.last_message ? (

{role.session.last_message}

) : null}
{copy.roleStatus.lastActive}
{role.session ? (
{formatRelativeTime(role.session.last_used_at)}
) : (
{copy.common.notStarted}
)}
#{role.sort_order}
); })}
{/* Role Editor slide-out panel */} {editorTarget !== undefined && ( setEditorTarget(undefined)} onSaved={() => { setEditorTarget(undefined); refresh(); }} /> )}
); }