import { LocateButton, FollowUpForm } from "./answer-action-components"; import { Renderer, defineRegistry } from "@json-render/react"; import { useContext, useRef, useState, type ReactNode } from "react"; import { Info, TriangleAlert, ClipboardList, Database, FileCheck2, FlaskConical, Lightbulb, MessageSquare } from "lucide-react"; import { Button } from "@/shared/ui/button"; import { Badge } from "@/shared/ui/badge"; import { Alert, AlertDescription, AlertTitle } from "@/shared/ui/alert"; import { Card, CardContent } from "@/shared/ui/card"; import { AnswerDisclosure, AnswerDisclosureChevron, AnswerMotionScope } from "./answer-motion"; import { cn } from "@/shared/ui/cn"; import { InteractiveChart } from "./interactive-chart"; import { InteractiveDataTable } from "./interactive-data-table"; import { AnswerResult } from "./answer-result"; import { AnswerInteractionContext as InteractionContext, type AnswerUiInteractions } from "./answer-ui-interaction-context"; export type { AnswerUiMapTarget, AnswerUiSelection } from "./answer-ui-interaction-context"; import { answerUiCatalog } from "./catalog"; import type { AnswerUiBlock } from "./contract"; import { AnswerContentNav } from "./answer-content-nav"; import { getAnswerTaskLayout } from "./answer-task-layout"; import { AnswerViewStateProvider, useAnswerBlockState, useAnswerNodeState, withAnswerNodeIdentity, withAnswerNodeIds } from "./answer-view-state-provider"; import { type AnswerEvidenceState, type AnswerViewState, type AnswerViewStateChange } from "./answer-view-state"; import { withAnswerDisplayTimes } from "./answer-display-time"; const { registry: baseRegistry } = defineRegistry(answerUiCatalog, { actions: {}, components: { Stack: ({ props, children }) => (
{children}
), Section: ({ props, children }) => (

{props.description ? (

{props.description}

) : null}
{children}
), Text: ({ props }) => props.tone === "neutral" ? (
{props.title ?

{props.title}

: null}

{props.text}

) : ( {props.tone === "warning" ? : } {props.title ?? (props.tone === "warning" ? "注意事项" : "补充说明")} {props.text} ), Result: ({ props }) => , Metric: ({ props }) => (

{props.label}

{props.value} {props.unit ? ( {props.unit} ) : null}

{props.source ? (

来源:{props.source}

) : null}
), Table: ({ props }) => (
), Chart: ({ props }) => , Evidence: ({ props }) => , MapLocation: ({ props }) => , FollowUpForm: ({ props }) => } }); const registry = withAnswerNodeIdentity(baseRegistry); type AnswerUiViewProps = { block: AnswerUiBlock; viewState?: AnswerViewState; onViewStateChange?: AnswerViewStateChange } & Partial; export function AnswerUiView({ viewState, onViewStateChange, ...props }: AnswerUiViewProps) { return ; } function AnswerUiBody({ block, disabled = false, onLocate, onSubmit, chartWindowKey, submitMode = "send" }: { block: AnswerUiBlock } & Partial) { const [{ evidenceFilter: filter }, setFilter] = useAnswerBlockState(); const contentRef = useRef(null); const taskLayout = getAnswerTaskLayout(block.spec); const root = block.spec.elements[block.spec.root]; const density = root.type === "Stack" ? root.props.density : undefined; const intent = root.type === "Stack" ? root.props.intent : undefined; const hasTaskLayout = intent !== "response" && Boolean(taskLayout?.analysis.length && (taskLayout.evidence.length || taskLayout.followUp.length)); const kinds = [...new Set(Object.values(block.spec.elements).filter((node) => node.type === "Evidence").map((node) => String(node.props.kind)))]; const evidenceFilters = kinds.length > 1 ?
{["all", ...kinds].map((kind) => )}
: null; const isGroup = (ids: string[]) => hasTaskLayout && ids.length > 1 && !ids.every((id) => block.spec.elements[id].type === "Section"); const renderNodes = (ids: string[]) => ids.map((id) => ); return (
<> {taskLayout ?
{taskLayout.overview.length ?
{renderNodes(taskLayout.overview)}
: null} {taskLayout.analysis.length ?
{renderNodes(taskLayout.analysis)}
: null} {taskLayout.evidence.length || taskLayout.followUp.length ?
{taskLayout.evidence.length ? }> {evidenceFilters}{renderNodes(taskLayout.evidence)} : null} {taskLayout.followUp.length ? }> {renderNodes(taskLayout.followUp)} : null}
: null}
: <>{evidenceFilters}}
); } function TaskSupport({ title, icon, children, grouped }: { title: string; icon: ReactNode; children: ReactNode; grouped: boolean }) { const [open, setOpen] = useState(true); return ; } const evidenceKinds = { observed: { label: "查询记录", icon: Database }, human_input: { label: "用户提供", icon: MessageSquare }, inference: { label: "分析推断", icon: Lightbulb }, demo: { label: "演练数据", icon: FlaskConical } }; function EvidenceCard(props: { title: string; summary: string; source: string; kind: keyof typeof evidenceKinds; observed_at: string | null; evidence_id: string | null; }) { const [{ open }, patch] = useAnswerNodeState("Evidence", props); const setOpen = (open: boolean) => patch({ open }); const meta = evidenceKinds[props.kind]; const Icon = meta.icon; const { evidenceFilter } = useContext(InteractionContext); if (evidenceFilter && evidenceFilter !== "all" && evidenceFilter !== props.kind) return null; return ( {props.title} {props.source} {meta.label} }>

{props.summary}

{props.observed_at || props.evidence_id ? (
{props.observed_at ? (
记录时间:
{props.observed_at}
) : null} {props.evidence_id ? (
资料编号:
{props.evidence_id}
) : null}
) : null}
); }