"use client" import { useState } from "react" import { ArrowRightIcon, CheckCircle2Icon, ChevronDownIcon, ChevronUpIcon, CircleHelpIcon, CircleIcon, RefreshCwIcon, RotateCcwIcon, SearchIcon, SquareIcon, XCircleIcon, } from "lucide-react" import { cn } from "@lib/utils" import { SuperLoader } from "@/components/superloader" import { dmSansClassName } from "@/lib/fonts" import { pendingResearchClarification, type NovaResearchClarificationAnswer, type NovaResearchConnectionState, type NovaResearchEvent, type NovaResearchRun, researchFailurePresentation, researchPlanStepDisplayStatus, researchToolFailureDescription, } from "@/lib/nova-research" import { ResearchClarification } from "./research-clarification" function toolLabel(name: string | null, fallback: string | null): string { if (fallback) return fallback return (name ?? "Research tool") .replace(/_/g, " ") .replace(/\b\w/g, (character) => character.toUpperCase()) } function eventLabel(event: NovaResearchEvent): string | null { if (event.type === "error") return "A research step could not complete." if (event.type === "tool" && event.status === "failed") { return `${toolLabel(event.toolName, null)} failed` } if (event.status === "failed") return "A research step could not complete." if (event.title === "Research capabilities enabled" && event.message) { return `Enabled ${event.message} research` } if (event.type === "tool") return toolLabel(event.toolName, event.title) const label = event.message || event.title if (!label) return null if ( event.type === "assistant" && (label.length > 600 || / 600 ? `${label.slice(0, 597)}…` : label } function eventIcon(event: NovaResearchEvent) { if (event.status === "running" || event.status === "pending") { return } if (event.status === "failed" || event.type === "error") { return } if (event.type === "tool") { return } if (event.type === "assistant") { return } return } function isUsefulTimelineEvent(event: NovaResearchEvent): boolean { return ( event.type === "assistant" || event.type === "tool" || event.type === "status" || event.type === "artifact" || event.type === "error" ) } export function ResearchProgress({ run, onCancel, onSubmitClarification, connectionState = { status: "connected", attempts: 0 }, onRetryConnection, onRetryFinalization, onRunAgain, className, }: { run: NovaResearchRun onCancel: () => Promise | void onSubmitClarification: ( requestId: string, answers: NovaResearchClarificationAnswer[], ) => Promise connectionState?: NovaResearchConnectionState onRetryConnection?: () => void onRetryFinalization?: () => Promise onRunAgain?: () => Promise className?: string }) { const active = run.status === "queued" || run.status === "running" || run.status === "awaiting_input" const failed = run.status === "failed" const cancelled = run.status === "cancelled" const awaitingInput = run.status === "awaiting_input" const [expanded, setExpanded] = useState(true) const [pendingAction, setPendingAction] = useState< "cancel" | "retry-finalization" | "run-again" | null >(null) const [actionError, setActionError] = useState(null) const timeline = run.events.filter(isUsefulTimelineEvent) const clarification = pendingResearchClarification(run) const failure = failed ? researchFailurePresentation(run) : null const statusLabel = awaitingInput ? "Waiting for your answers" : active ? "Researching" : failed ? (failure?.title ?? "Research could not complete") : cancelled ? "Research stopped" : "Research complete" const invokeAction = async ( action: NonNullable, callback: (() => Promise | void) | undefined, ) => { if (!callback || pendingAction) return setPendingAction(action) setActionError(null) try { await callback() } catch { setActionError( action === "cancel" ? "Could not stop this research. Please try again." : action === "retry-finalization" ? "Could not retry the report yet. Your collected sources are still preserved." : "Could not start a new research run. Please try again.", ) } finally { setPendingAction(null) } } return (
{awaitingInput ? ( ) : active ? ( ) : failed ? ( ) : cancelled ? ( ) : ( )} {active ? ( ) : null}
{active && connectionState.status === "reconnecting" ? ( {connectionState.attempts >= 3 ? "Connection lost. Reconnecting to research updates…" : "Research updates are delayed. Reconnecting…"} {onRetryConnection ? ( ) : null} ) : null} {failure ? (

{failure.title}

{failure.description}

{failure.incidentId ? (

Reference: {failure.incidentId}

) : null}
{failure.canRetryFinalization && onRetryFinalization ? ( ) : null} {onRunAgain ? ( ) : null}
) : cancelled && onRunAgain ? (
) : null} {actionError ? (

{actionError}

) : null} {clarification ? ( onSubmitClarification(clarification.id, answers) } /> ) : null} {expanded && !clarification ? (
{run.plan ? (
Plan
{run.plan.steps.map((step) => { const displayStatus = researchPlanStepDisplayStatus( run.status, step.status, ) return (
{displayStatus === "complete" ? ( ) : displayStatus === "in_progress" ? ( ) : displayStatus === "failed" ? ( ) : displayStatus === "cancelled" ? ( ) : ( )} {step.title}
) })}
) : null}
{timeline.length === 0 ? (
Preparing the investigation…
) : ( timeline.map((event) => { const label = eventLabel(event) if (!label) return null const toolFailureDescription = researchToolFailureDescription(event) return (
{eventIcon(event)}
{label} {toolFailureDescription ? (
{toolFailureDescription}
) : event.type === "tool" && event.toolName ? (
{event.toolName}
) : null}
) }) )}
) : null}
) }