import { useState } from 'react'; import type { AgentAction, AgentView, WatchState } from '@stream-control/shared'; import { formatBytes, formatPercent, formatRelative, formatTimecode } from '../format'; interface Props { agent: AgentView; selected: boolean; onToggleSelect: (id: string) => void; onCommand: (id: string, action: AgentAction, params?: Record) => Promise; onOpenSettings: (agent: AgentView) => void; } export function AgentCard({ agent, selected, onToggleSelect, onCommand, onOpenSettings }: Props) { const [pending, setPending] = useState(null); const { status } = agent; async function run(action: AgentAction, params?: Record) { setPending(action); try { await onCommand(agent.id, action, params); } finally { setPending(null); } } const busy = pending !== null; const obsReady = agent.online && status.obsConnected; const watch = status.watch; const pausedLabel = watch?.autoPaused ? 'Pause · show privé' : 'En pause'; const state = !agent.online ? { label: 'Hors-ligne', tone: 'offline' as const } : !status.obsConnected ? { label: 'OBS déconnecté', tone: 'warn' as const } : status.recording ? { label: status.recordPaused ? pausedLabel : 'Enregistre', tone: status.recordPaused ? ('warn' as const) : ('rec' as const), } : { label: 'Prêt', tone: 'ok' as const }; return (

{agent.name}

{agent.hostname ?? '—'} · {agent.platform} {agent.agentVersion ? ` · v${agent.agentVersion}` : ''}
{state.label}
{status.obsError && !status.obsConnected && (

{status.obsError}

)} {watch?.enabled && ( void run('watch.check')} onFullscreen={() => void run('hotkey.fullscreen')} /> )}
{status.recording ? ( <> {status.recordPaused ? ( ) : ( )} ) : ( )} {status.streaming ? ( ) : ( )} {status.obsConnected ? ( ) : ( )}
{status.lastRecordingPath ? `Dernier fichier : ${basename(status.lastRecordingPath)}` : (status.recordDirectory ?? 'Dossier inconnu')} vu {formatRelative(agent.lastSeenAt)}
); } const WATCH_LABELS: Record = { public: { text: 'public', tone: 'ok' }, private: { text: 'show privé', tone: 'rec' }, offline: { text: 'hors-ligne', tone: 'offline' }, unknown: { text: 'inconnu', tone: 'offline' }, }; function WatchStrip({ watch, busy, onCheck, onFullscreen, }: { watch: WatchState; busy: boolean; onCheck: () => void; onFullscreen: () => void; }) { const info = WATCH_LABELS[watch.state]; const stale = watch.lastCheckedAt > 0 && Date.now() - watch.lastCheckedAt > 60_000; return (
{info.text} {watch.username || '(aucun pseudo)'}
{watch.lastError ? ( Sonde en échec : {watch.lastError} ) : ( {watch.rawStatus ? `statut brut « ${watch.rawStatus} »` : 'pas encore sondé'} {watch.autoPaused && ' · pause automatique active'} {stale && ' · dernière sonde ancienne'} )}
); } function Metric({ label, value, mono }: { label: string; value: string; mono?: boolean }) { return (
{label} {value}
); } function basename(filePath: string): string { const parts = filePath.split(/[\\/]/); return parts[parts.length - 1] ?? filePath; }