/** * @file NeedsYouStrip.tsx * @description The Dashboard's pinned "Needs you" strip: every active session * blocked on the user (`awaiting_input_since`), each linking straight to its * workspace pane (or replay), else its analysis page. Renders nothing while no * session is waiting. `compact` is the one-line form shown above Analytics. * @author Nguyễn Ngọc Trí Vĩ */ import { useCallback, useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { BellRing } from "lucide-react"; import { api, type RunHandle } from "../lib/api"; import { timeAgo } from "../lib/format"; import { useLiveRefresh } from "../hooks/useLiveRefresh"; import { workspaceHref } from "./SessionRowMenu"; import type { Session } from "../lib/types"; export function NeedsYouStrip({ compact = false }: { compact?: boolean }) { const { t } = useTranslation("dashboard"); const [sessions, setSessions] = useState([]); // The real count: only the first 20 are fetched and listed. const [total, setTotal] = useState(0); const [runs, setRuns] = useState([]); const load = useCallback(() => { api.sessions .list({ awaiting: true, limit: 20 }) .then((r) => { setSessions(r.sessions); setTotal(r.total); }) .catch(() => undefined); // the strip is a shortcut; the pages show errors api.run .list() .then((r) => setRuns(r.items)) .catch(() => undefined); }, []); useEffect(() => { load(); }, [load]); useLiveRefresh(load, { intervalMs: 15000, match: (msg) => msg.type === "session_updated" || msg.type === "session_created", }); if (sessions.length === 0) return null; return (

{t("needsYou.title")}{" "} ({Math.max(total, sessions.length)})

{compact && sessions.slice(0, 5).map((s) => ( {s.name || s.id.slice(0, 8)} ))}
{!compact && (
    {sessions.map((s) => (
  • {s.name || s.id.slice(0, 8)} {s.cwd} {s.awaiting_input_since && ( {t("needsYou.since", { ago: timeAgo(s.awaiting_input_since) })} )}
  • ))}
)}
); }