import { useState } from "react"; import { ArrowUpRight, Check, Copy, RotateCcw } from "lucide-react"; import type { AssistantAction, AssistantResult, } from "../../../shared/assets/assistant"; import { ResultView, type ResultViewProps } from "./ResultView"; import { GenerativeResultView } from "./GenerativeResultView"; import { copyResultText } from "./result-presentation"; export type CompoundResultViewProps = Omit< ResultViewProps, "ask" | "operate" | "activate" | "display" > & { activeResultId?: string; correctionFor?: ( resultId: string, ) => { message?: string; resultId?: string } | undefined; ask: (text: string, base: AssistantResult) => void; operate: ( action: AssistantAction, label: string, base: AssistantResult, ) => void; activate: (result: AssistantResult) => void; }; export function CompoundResultView({ result, activeResultId, correctionFor, ask, operate, activate, ...props }: CompoundResultViewProps) { const [selection, setSelection] = useState<{ sectionId: string; observedActiveId?: string; }>(); const [notice, setNotice] = useState(""); const sections = result.sections || []; if (!sections.length) { const Renderer = result.presentation ? GenerativeResultView : ResultView; return ( ask(text, result)} operate={(action, label) => operate(action, label, result)} activate={() => activate(result)} /> ); } // A failed section has no server result ID. Keep explicit user selection while // the active context is unchanged; follow a different context after inline updates. const locallySelected = sections.find( (section) => section.id === selection?.sectionId, ); const externallyActive = activeResultId ? sections.find( (section) => !!section.result?.resultId && section.result.resultId === activeResultId, ) : undefined; const selected = (selection?.observedActiveId === activeResultId ? locallySelected : externallyActive) || locallySelected || externallyActive || sections[0]; const selectedIndex = sections.indexOf(selected); const stacked = result.layout === "stack"; const renderSection = (section: typeof selected) => { const child = section.result; const correction = child?.resultId ? correctionFor?.(child.resultId) : undefined; const Renderer = child?.presentation ? GenerativeResultView : ResultView; return child ? ( ask(text, child)} operate={(action, label) => operate(action, label, child)} activate={() => activate(child)} disabled={props.disabled || !!correction} correctionMessage={correction?.message} correctionResultId={correction?.resultId} /> ) : (
本项未完成

{section.error || "这项分析暂未返回结果,可以重新尝试。"}

); }; const panelId = `aq-section-panel-${result.resultId || "current"}`; const tabId = (sectionId: string) => `aq-section-tab-${result.resultId || "current"}-${sectionId}`; const select = (section: typeof selected) => { setSelection({ sectionId: section.id, observedActiveId: activeResultId }); if (section.result) activate(section.result); }; return (
{result.title || "综合分析"} {sections.length}项分析

{result.summary}

{props.correctionMessage && (
此回答已撤回

{props.correctionMessage}

{props.correctionResultId && ( )}
)} {result.correction && (

已纠正上一条回答:{result.correction.message}

)} {result.outcome === "partial" && (

已返回可查结果,其余问题在下方单独说明。

)} {!!result.unanswered?.length && (
尚未回答 {result.unanswered.map((item) => (

{item.question}:{item.reason}

))}
)} {result.warning && (

{result.warning}

)} {result.gap && (

{result.gap.recorded ? "未满足的需求已记录,可在待完善需求中查看。" : "未满足的需求暂未保存。"}

)} {!stacked && (
{sections.map((section, index) => { const chosen = section.id === selected.id; return ( ); })}
)}
{stacked ? ( `共 ${sections.length} 项分析 · 点击任一项继续追问` ) : ( <> 当前项目 {selectedIndex + 1}/{sections.length} ·{" "} {selected.title} )}
{notice && (

{notice}

)} {stacked ? (
{sections.map((section, index) => (
{ if ( section.result && !props.disabled && !correctionFor?.(section.result.resultId || "") ) activate(section.result); }} >
{index + 1} {section.title} {!!section.result?.resultId && section.result.resultId === activeResultId && ( 当前追问范围 )}
{renderSection(section)}
))}
) : (
{renderSection(selected)}
)}
); }