"use client";
import { Activity, BarChart3, Database, History, LineChart, MonitorCog } from "lucide-react";
import { AnimatePresence, MotionConfig, motion } from "motion/react";
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
import { normalizeSafeChartData, parseChartSpec, type SafeChartData, type SafeChartSeries, type SafeChartSpec } from "../chart-data";
import type { AgentUiResult } from "../types";
import type { UIEnvelope } from "../ui-envelope";
import {
agentLayoutTransition,
agentPanelItemVariants,
agentPanelListVariants
} from "./agent-motion";
type AgentUiEnvelopeRendererProps = {
results: AgentUiResult[];
};
export function AgentUiEnvelopeRenderer({ results }: AgentUiEnvelopeRendererProps) {
if (results.length === 0) {
return null;
}
return (
{results.map((result) => (
))}
);
}
function AgentUiResultCard({ result }: { result: AgentUiResult }) {
const { envelope } = result;
return (
{getEnvelopeIcon(envelope)}
{getEnvelopeTitle(envelope)}
{getSurfaceLabel(envelope.surface)}
agent-ui@1
{envelope.kind === "chart" ?
: null}
{envelope.kind === "registered_component" ?
: null}
{envelope.kind === "map_action" ? null : null}
);
}
function SafeChartEnvelope({ envelope }: { envelope: Extract }) {
const spec = parseChartSpec(envelope.spec);
const data = normalizeSafeChartData(envelope.data);
if (!data || data.series.length === 0 || data.xData.length === 0) {
return ;
}
return (
{spec.title ?? "Agent 图表"}
{spec.chartType === "bar" ? "柱状图" : "折线图"}
{spec.yAxisName ? ` · ${spec.yAxisName}` : ""}
{data.series.slice(0, 3).map((series, index) => (
{series.name}
))}
{spec.xAxisName ?
{spec.xAxisName}
: null}
);
}
function SafeChartSvg({ spec, data }: { spec: SafeChartSpec; data: SafeChartData }) {
const width = 360;
const height = 190;
const padding = { top: 16, right: 18, bottom: 32, left: 38 };
const plotWidth = width - padding.left - padding.right;
const plotHeight = height - padding.top - padding.bottom;
const values = data.series.flatMap((series) => series.data);
const min = Math.min(0, ...values);
const max = Math.max(...values);
const range = max - min || 1;
const xFor = (index: number) =>
padding.left + (data.xData.length === 1 ? plotWidth / 2 : (index / (data.xData.length - 1)) * plotWidth);
const yFor = (value: number) => padding.top + plotHeight - ((value - min) / range) * plotHeight;
const ticks = createTicks(min, max);
return (
);
}
function LineSeries({
series,
seriesIndex,
xFor,
yFor
}: {
series: SafeChartSeries;
seriesIndex: number;
xFor: (index: number) => number;
yFor: (value: number) => number;
}) {
const points = series.data.map((value, index) => `${xFor(index)},${yFor(value)}`).join(" ");
const stroke = chartStroke(seriesIndex);
return (
{series.data.map((value, index) => (
))}
);
}
function BarSeries({
series,
seriesIndex,
seriesCount,
xFor,
yFor,
baselineY
}: {
series: SafeChartSeries;
seriesIndex: number;
seriesCount: number;
xFor: (index: number) => number;
yFor: (value: number) => number;
baselineY: number;
}) {
const fill = chartStroke(seriesIndex);
const barWidth = Math.max(5, 22 / seriesCount);
const offset = (seriesIndex - (seriesCount - 1) / 2) * (barWidth + 2);
return (
{series.data.map((value, index) => {
const y = yFor(value);
return (
);
})}
);
}
function RegisteredComponentEnvelope({
envelope
}: {
envelope: Extract;
}) {
if (envelope.component === "HistoryPanel") {
return (
} title="历史数据面板">
);
}
if (envelope.component === "ScadaPanel") {
return (
} title="SCADA 面板">
);
}
return ;
}
function TrustedPanelShell({
icon,
title,
children
}: {
icon: ReactNode;
title: string;
children: ReactNode;
}) {
return (
{icon}
{title}
{children}
);
}
function PropRows({ props, labels }: { props: unknown; labels: Record }) {
const rows = safePropRows(props, labels);
if (rows.length === 0) {
return 已打开可信面板,暂无可展示参数。
;
}
return (
{rows.map((row) => (
- {row.label}
-
{row.value}
))}
);
}
function FallbackText({ text }: { text: string }) {
return (
{text}
);
}
function safePropRows(props: unknown, labels: Record) {
if (!isRecord(props)) {
return [];
}
return Object.entries(labels).flatMap(([key, label]) => {
const value = props[key];
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
return [];
}
return [{ label, value: String(value) }];
});
}
function getEnvelopeIcon(envelope: UIEnvelope) {
if (envelope.kind === "chart") {
return envelope.spec && isRecord(envelope.spec) && envelope.spec.chart_type === "bar" ? (
) : (
);
}
if (envelope.kind === "registered_component") {
return ;
}
return ;
}
function getEnvelopeTitle(envelope: UIEnvelope) {
if (envelope.kind === "chart") {
const spec = parseChartSpec(envelope.spec);
return spec.title ?? "Agent 图表";
}
if (envelope.kind === "registered_component") {
return envelope.component;
}
return envelope.action;
}
function getSurfaceLabel(surface: UIEnvelope["surface"]) {
if (surface === "chat_inline") return "聊天内联展示";
if (surface === "side_panel") return "侧栏展示";
if (surface === "canvas") return "画布展示";
return "地图叠加";
}
function createTicks(min: number, max: number) {
const range = max - min || 1;
return [max, min + range * 0.5, min];
}
function chartStroke(index: number) {
return ["#2563eb", "#0aa6a6", "#ff7a45", "#0477bf"][index % 4] ?? "#2563eb";
}
function chartColorClass(index: number) {
return ["bg-blue-600", "bg-teal-600", "bg-orange-500", "bg-sky-700"][index % 4] ?? "bg-blue-600";
}
function formatChartNumber(value: number) {
if (Math.abs(value) >= 100) {
return value.toFixed(0);
}
if (Math.abs(value) >= 10) {
return value.toFixed(1);
}
return value.toFixed(2);
}
function shortLabel(value: string) {
return value.length > 8 ? `${value.slice(0, 7)}...` : value;
}
function isRecord(value: unknown): value is Record {
return typeof value === "object" && value !== null;
}