import { AskAboutContent } from "./answer-ui-interactions"; import { JSONUIProvider, Renderer, defineRegistry } from "@json-render/react"; import { useContext, useId, useState } from "react"; import { ArrowUpRight, ChevronDown, ClipboardList, Database, FileCheck2, FlaskConical, Lightbulb, MapPin, MessageSquare } from "lucide-react"; import { Button } from "@/shared/ui/button"; import { Badge } from "@/shared/ui/badge"; import { Card, CardContent } from "@/shared/ui/card"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/shared/ui/collapsible"; import { DateTimeField } from "@/shared/ui/date-time-field"; import { Input } from "@/shared/ui/input"; import { Textarea } from "@/shared/ui/textarea"; import { cn } from "@/shared/ui/cn"; import { InteractiveChart } from "./interactive-chart"; import { InteractiveDataTable } from "./interactive-data-table"; import { AnswerInteractionContext as InteractionContext, type AnswerUiInteractions, type AnswerUiMapTarget } from "./answer-ui-interaction-context"; export type { AnswerUiMapTarget, AnswerUiSelection } from "./answer-ui-interaction-context"; import { answerUiCatalog } from "./catalog"; import type { AnswerUiBlock } from "./contract"; const { registry } = defineRegistry(answerUiCatalog, { actions: {}, components: { Stack: ({ props, children }) => (
{children}
), Section: ({ props, children }) => (

{props.description ? (

{props.description}

) : null}
{children}
), Text: ({ props }) => (

{props.text}

), 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 }) => } }); export function AnswerUiView({ block, disabled = false, onLocate, onSubmit, onAsk, submitMode = "send" }: { block: AnswerUiBlock } & Partial) { const [filter, setFilter] = useState("all"); const kinds = [...new Set(Object.values(block.spec.elements).filter((node) => node.type === "Evidence").map((node) => String(node.props.kind)))]; return ( onAsk({ ...selection, text: `${selection.text}\n引用内容块:${block.block_id}` }) : undefined }}>
{kinds.length > 1 ?
{["all", ...kinds].map((kind) => )}
: null}
); } 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, setOpen] = useState(false); const meta = evidenceKinds[props.kind]; const Icon = meta.icon; const { evidenceFilter } = useContext(InteractionContext); if (evidenceFilter && evidenceFilter !== "all" && evidenceFilter !== props.kind) return null; return (

{props.summary}

{props.observed_at || props.evidence_id ? (
{props.observed_at ? (
记录时间:
{props.observed_at}
) : null} {props.evidence_id ? (
资料编号:
{props.evidence_id}
) : null}
) : null}
); } function LocateButton({ label, source, ...target }: AnswerUiMapTarget & { label: string; source: string }) { const { disabled, onLocate } = useContext(InteractionContext); const [running, setRunning] = useState(false); const [feedback, setFeedback] = useState(null); return (
); } type FormField = { name: string; label: string; kind: "text" | "datetime" | "number"; required: boolean; }; function FollowUpForm({ title, description, fields }: { title: string; description: string; fields: FormField[]; }) { const { disabled, onSubmit, submitMode } = useContext(InteractionContext); const prefix = useId(); const [values, setValues] = useState>({}); const [submitting, setSubmitting] = useState(false); const [feedback, setFeedback] = useState(null); return (
{ event.preventDefault(); if (fields.some((field) => field.required && !values[field.name]?.trim())) { setFeedback("请填写必填资料。"); return; } if (!onSubmit || disabled || submitting) return; const entries = fields .filter((field) => values[field.name]?.trim()) .map( (field) => `${field.label}:${values[field.name].trim()}${field.kind === "datetime" ? "(北京时间)" : ""}` ); if (!entries.length) { setFeedback("请至少填写一项资料。"); return; } if (entries.join("\n").length > 9000) { setFeedback("补充资料过长,请分次发送。"); return; } setSubmitting(true); setFeedback(null); try { await onSubmit( `我补充以下资料,请按用户提供的信息处理,说明保存或调查的实际结果:\n${entries.join("\n")}` ); setFeedback(submitMode === "draft" ? "资料已带入对话,请确认后发送。" : "资料已发送,请查看 Agent 回复。"); } catch (error) { setFeedback(error instanceof Error ? error.message : "资料发送失败,请重试。"); } finally { setSubmitting(false); } }} >

{title}

{description}

{fields.map((field) => field.kind === "datetime" ? ( setValues((current) => ({ ...current, [field.name]: value }))} /> ) : (
{field.kind === "text" ? (