"use client"; import { useState } from "react"; import { ChevronDownIcon, Loader2Icon, PauseIcon, PinIcon, PlusIcon, SquareIcon, TelescopeIcon, XIcon, } from "lucide-react"; import type { SearchHandoff } from "yt-dlp-transcript-common/lib/aiHandoff"; import type { GroundingMode } from "yt-dlp-transcript-common/components/SearchSessionContext"; // Shows the search results handed off from the search page as the chat's pinned // grounding: what they are, whether the assistant may look beyond them (the // strict/expand toggle), a way to read more transcript around any hit, and a way // to detach them. export function PinnedResultsPanel({ pinned, groundingMode, strictGrounding, busy, expanding, expandError, onSetStrict, onClear, onExpandVideo, // ── Whole-corpus sweep ── canSweep, sweeping, sweepProgress, rateLimitNotice, totalGroundingVideos, sweepCapped, hasReport, hasKey, directive, defaultDirective, onSweep, onStopSweep, onPauseSweep, }: { pinned: SearchHandoff; // Whole search vs. hand-picked selection — drives the header copy. groundingMode: GroundingMode; strictGrounding: boolean; busy: boolean; expanding: Record; expandError: Record; onSetStrict: (on: boolean) => void; onClear: () => void; onExpandVideo: (key: string, aroundSeconds?: number) => void; // The sweep can run at all (native-tool-capable provider). canSweep: boolean; // A sweep is in flight. sweeping: boolean; // Per-batch progress while sweeping. sweepProgress: { done: number; total: number; sections: number }; // A rate-limiter pacing/backoff notice ("Rate limited — retrying in Ns"), or // null when nothing is waiting. Shown in the sweep strip so a paced run's waits // are visible instead of looking frozen. rateLimitNotice: string | null; // Full matched-set size (before caps) + whether the search itself was capped. totalGroundingVideos: number; sweepCapped: boolean; // A report already exists (→ fresh-vs-extend split instead of one button). hasReport: boolean; // An API key is set (else the sweep button is disabled). hasKey: boolean; // The composer text used as the report directive, and the empty-box fallback. directive: string; defaultDirective: string; onSweep: (mode: "fresh" | "extend") => void; onStopSweep: () => void; // Courtesy pause: checkpoint + stop at the next batch boundary (resumable). onPauseSweep: () => void; }) { const [showList, setShowList] = useState(false); const n = pinned.videos.length; // The sweep reads the WHOLE matched set (which can exceed the ~100 pinned here). const sweepN = totalGroundingVideos || n; const focus = directive.trim(); const pct = sweepProgress.total > 0 ? Math.round((sweepProgress.done / sweepProgress.total) * 100) : 0; return (
{groundingMode === "selection" ? `Grounded in ${n} selected video${n === 1 ? "" : "s"}` : `Grounded in ${n} result${n === 1 ? "" : "s"} from your current search`} {pinned.label} {pinned.truncated ? ` · showing first ${n}` : ""}
{showList && (
    {pinned.videos.map((v) => { const loading = !!expanding[v.key]; const failed = !!expandError[v.key]; return (
  • {v.title} {v.channel ? ` — ${v.channel}` : ""} {" "} · {v.snippets.length} excerpt {v.snippets.length === 1 ? "" : "s"}
    {failed && ( Couldn't load this transcript — try again. )} {v.snippets.length > 0 && (
      {v.snippets.map((sn) => (
    • {sn.text}
    • ))}
    )}
  • ); })}
)}

Click a timestamp (or “context”) to add the surrounding transcript to what the assistant reads.

{/* ── Whole-corpus report (sweep) — a heavier, corpus-scale action than a normal turn: it reads EVERY match in batches, folding findings into one persistent report. Its own labeled zone at the panel's foot. ── */}
{sweeping ? ( // Signature moment: a determinate batch-progress strip. As the driver // setReport()s each batch, the Report panel's document grows below while // this bar climbs — chunked reduction made legible. <>
Sweeping {sweepN} result{sweepN === 1 ? "" : "s"}…
Reading batch {Math.min(sweepProgress.done + 1, sweepProgress.total)}{" "} of {sweepProgress.total} {sweepProgress.sections > 0 ? ` · ${sweepProgress.sections} section${sweepProgress.sections === 1 ? "" : "s"} written` : ""} {rateLimitNotice && ( {rateLimitNotice} )} ) : ( <>
Whole-corpus report {!hasReport && ( Reads all {sweepN} match{sweepN === 1 ? "" : "es"} in batches, folding findings into one report. )}
{/* Directive = the composer text, shown inline so the scope is visible. */}

Report on:{" "} {focus ? ( {focus} ) : ( <> {defaultDirective} {" "} — type in the box below to focus it. )}

{!canSweep ? ( <>

Needs a tool-capable provider — switch the search mode off Scripted.

) : !hasReport ? ( // Nothing to overwrite → a single primary button. ) : ( // A report exists → split into two weighted actions, each captioned // so replacement is never implicit.
Replaces the current report.
Keeps it and folds these in.
)} {canSweep && sweepCapped && (

The search matched more than we can sweep — narrow it for full coverage.

)} )}
); }