import { useId, useState } from "react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { ChevronDown, Eye, EyeOff, LocateFixed, MapPinned, Pause, X } from "lucide-react"; import { AgentChartView, type AnswerUiBlock } from "@/features/agent"; import { formatChartDisplayText } from "@/shared/utils/date-time-text"; import { Badge } from "@/shared/ui/badge"; import { Button } from "@/shared/ui/button"; import { Card, CardContent, CardHeader } from "@/shared/ui/card"; import { PresentationItem, PresentationViewport, PresentationReferences } from "./investigation-presentation-motion"; import { presentationFade, presentationTransition } from "../utils/investigation-presentation-motion"; import type { OperationalTask } from "../api/operational-task-client"; import type { InvestigationOverlay } from "../api/investigation-presentation"; import type { InvestigationChart } from "../hooks/use-investigation-presentation"; import { FloatingWindow } from "@/shared/ui/floating-window"; import { PresentationHistorySwitcher } from "./presentation-history-switcher"; import { PresentationBusinessResults } from "./presentation-business-results"; import type { PresentationHistoryControls } from "../utils/presentation-view-history"; import type { InvestigationMapTarget } from "../api/investigation-report"; type Props = { blocks?: AnswerUiBlock[]; records?: Array>; evidence?: Array<{ evidence_id: string; summary: string }>; history?: PresentationHistoryControls; onLocateTargets?: (targets: InvestigationMapTarget[]) => Promise; scope?: string; embedded?: boolean; task: OperationalTask | null; localPreview?: boolean; charts: InvestigationChart[]; overlay: InvestigationOverlay | null; cameraPaused: boolean; status: "connecting" | "live" | "history" | "disconnected" | "error"; error: string | null; width: number; relatedLayers: Array<{ id: string; label: string; visible: boolean }>; onResumeCamera: () => void; onCloseChart: (id: string) => void; onToggleOverlay: () => void; onFitOverlay: () => void; onToggleLayer: (id: string, visible: boolean) => void; }; const statusLabels = { connecting: "连接中", live: "实时跟随", history: "已载入历史", disconnected: "连接中断", error: "部分未完成" }; export function InvestigationPresentationPanel({ scope = "investigation", embedded = false, task, blocks = [], records = [], evidence, history, onLocateTargets, localPreview = false, charts, overlay, cameraPaused, status, error, width, relatedLayers, onResumeCamera, onCloseChart, onToggleOverlay, onFitOverlay, onToggleLayer }: Props) { const [collapsed, setCollapsed] = useState(false); const [keyboardAction, setKeyboardAction] = useState(false); const reducedMotion = useReducedMotion(); const immediate = Boolean(reducedMotion) || keyboardAction; const contentId = useId(); const reviewing = history?.selectedId != null; const chartWindowKey = scope === "chat" ? undefined : JSON.stringify([scope, task?.task_id, reviewing ? "history" : "live"]); const selected = history?.entries.find((entry) => entry.id === history.selectedId); const pending = selected ? history!.entries.filter((entry) => entry.id > selected.id).length : 0; const title = scope === "chat" ? "对话展示" : localPreview ? "本地预览展示" : "调查实时展示"; const references = (ids: string[]) => ids.length ? ( ({ id, text: (evidence ?? task?.evidence)?.find((item) => item.evidence_id === id)?.summary ?? (localPreview && id === "dev:synthetic" ? "合成测试数据,未保存为调查证据。" : `关联资料:${id}`) }))} /> ) : null; return ( setKeyboardAction(true)} onPointerDownCapture={() => setKeyboardAction(false)} aria-label={title} data-testid="investigation-presentation-panel" data-presentation-scope={scope} data-investigation-collapsed={collapsed} className={`workbench-shadcn-surface pointer-events-auto ${embedded ? "relative shrink-0" : "absolute bottom-[calc(var(--workbench-timeline-height)+1.25rem)] left-3 z-40"} flex max-h-[55dvh] flex-col overflow-hidden rounded-xl border border-border bg-background text-foreground shadow-lg ${localPreview ? `${collapsed ? "h-28" : "h-[min(480px,55dvh)]"} [&>[data-investigation-content]]:flex-1` : ""}`} style={{ width, maxWidth: embedded ? "100%" : "calc(100% - 24px)" }} >

{title}

{reviewing ? "历史回看" : scope === "chat" ? "对话联动" : localPreview ? "本地测试" : statusLabels[status]} 图表 {charts.length} · 范围 {overlay ? 1 : 0} · 数据 {records.length} {reviewing && pending > 0 ? ( 后续 {pending} 个动作 ) : null}

{selected ? `${selected.taskTitle ? `${selected.taskTitle} · ` : ""}${new Date(selected.createdAt).toLocaleTimeString("zh-CN", { hour12: false })} · ${selected.label}${pending ? ` · 后续 ${pending} 个动作` : ""}` : scope === "chat" ? "展示当前对话的地图操作与结果" : localPreview ? "仅当前浏览器,不写入调查任务" : status === "history" ? "已载入所选调查保存的展示结果" : "跟随所选调查,展示地图操作与结果"}

{history?.entries.length ? : null}
{!collapsed ? ( {selected?.message ? (

{selected.message}

) : null} {!reviewing && !localPreview && error ? (

{error}

) : null} {cameraPaused ? (
镜头跟随已暂停
) : null}
{relatedLayers.length ? (

相关图层

{relatedLayers.map((layer) => ( ))}
) : null} {overlay ? (

{overlay.title}

{localPreview ? "本地空间预览,范围以输入 JSONL 为准" : overlay.mode === "heatmap" ? "记录集中度,颜色不表示已确认影响范围" : "调查范围与关联空间资料"}

{references([overlay.evidence_id])}
) : null} {charts.map((item) => (

{formatChartDisplayText(item.chart.title)}

{item.chart.series.some((series) => series.type === "scatter") ? "折线与历史散点" : item.chart.chart_type === "bar" ? "柱状图" : "折线图"} ·{" "} {item.chart.x_data.length} 个 {item.chart.chart_type === "bar" ? "类别" : "时间点"}

{() =>
{item.evidence_refs.length ?
{references(item.evidence_refs)}
: null}
}
{references(item.evidence_refs)}
))}
{!charts.length && !overlay && !blocks.length && !records.length && !relatedLayers.length ? (

当前没有展示结果,可切换历史动作回看地图与结果。

) : null}
) : null}
); }