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.title}
{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 (
依据:{source}
{feedback ? (
{feedback}
) : null}
);
}
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 (
);
}