import { memo, useMemo, useState } from "react"; import { ArrowDownToLine, ArrowUpRight, ChevronLeft, ChevronRight, Copy, } from "lucide-react"; import { DIMENSIONS, GROUP_LABELS, type AssistantAction, type AssistantResult, type AssistantRow, type Dimension, type QueryPlan, } from "../../../shared/assets/assistant"; import { fetchAuthenticated } from "../../../auth/api-client"; import { Chart } from "./Chart"; import { copyResultText, displayTime, resultScope, resultDisplay, queryPath, compactAnalysisPath, } from "./result-presentation"; import { useMobileLayout } from '../useMobileLayout'; import { mobileResultSummary } from './mobile-presentation'; import { ResultActions } from './ResultActions'; function Records({ rows, flow }: { rows: AssistantRow[]; flow: boolean }) { const mobile = useMobileLayout(); const [page, setPage] = useState(0), [search, setSearch] = useState(""); const filtered = rows.filter((r) => [r.plate, r.city, r.type, r.model, r.department, r.abnormalType].some((v) => v?.includes(search), ), ); const pages = Math.max(1, Math.ceil(filtered.length / 8)), current = Math.min(page, pages - 1); return ( <>
{filtered.length}条
{mobile ?
{filtered.slice(current * 8, current * 8 + 8).map(r =>
{r.plate || '未登记车牌'}{flow ? r.event : r.status}

{r.type} · {r.city || '未标注城市'}

更多资料
{[['型号', r.model], ['部门', r.department], ...(flow ? [['日期', r.date]] : [['库存子集', r.abnormal ? '异动' : r.pending ? '待交车' : '—'], ['异动类型', r.abnormalType], ['预计完成时间', r.targetCompletionTime]])].map(([label,value]) =>
{label}
{value || '—'}
)}
)}
:
{[ "车牌", "车型 / 型号", "城市", flow ? "业务类型" : "运营状态", ...(flow ? ["日期"] : ["库存子集", "异动类型", "预计完成时间"]), ].map((h) => ( ))} {filtered.slice(current * 8, current * 8 + 8).map((r) => ( {flow ? ( ) : ( <> )} ))}
{h}
{r.plate || "未登记车牌"} {r.type} {r.model} {r.city} {flow ? r.event : r.status}{r.date}{r.abnormal ? "异动" : r.pending ? "待交车" : "—"} {r.abnormalType || "—"} {r.targetCompletionTime || "—"}
} {!filtered.length &&

没有符合条件的明细

}
第{current + 1}/{pages}页
{search && (

此搜索仅筛选当前明细表;继续追问仍使用上方分析条件。

)} ); } export type ResultViewProps = { result: AssistantResult; ask: (text: string) => void; operate: (action: AssistantAction, label: string) => void; restore: (resultId: string) => void; active: boolean; activate: () => void; subject: string | null; disabled: boolean; correctionMessage?: string; correctionResultId?: string; showCorrection: (resultId: string) => void; display?: "metrics" | "chart" | "table" | "list"; }; function ResultViewContent({ result, ask, operate, restore, active, activate, subject, disabled, correctionMessage, correctionResultId, showCorrection, display: preferredDisplay, }: ResultViewProps) { const mobile = useMobileLayout(); const [expandedGroupResult, setExpandedGroupResult] = useState( null, ), [notice, setNotice] = useState(""), [flagged, setFlagged] = useState(false), [saving, setSaving] = useState(false); const { plan } = result, flow = plan.kind === "flow"; const run = (action: AssistantAction, label: string) => operate(action, label); const flag = async () => { setSaving(true); setNotice(""); try { const r = await fetchAuthenticated("/api/asset-assistant/feedback", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sessionId: result.sessionId, resultId: result.resultId, subject, }), }); if (!r.ok) throw new Error(); setFlagged(true); setNotice("已记录,我们会据此完善问答能力。"); } catch { setNotice("需求未保存,请稍后重试。"); } finally { setSaving(false); } }; const download = async () => { setSaving(true); setNotice(""); try { const r = await fetchAuthenticated("/api/asset-assistant/export", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sessionId: result.sessionId, resultId: result.resultId, subject, }), }); if (!r.ok) throw new Error(); const url = URL.createObjectURL(await r.blob()); const a = document.createElement("a"); a.href = url; a.download = "资产分析明细.csv"; a.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); setNotice("已按当前权限重新查询并导出明细,数量可能随业务更新变化。"); } catch { setNotice("导出失败,请重新查询后重试。"); } finally { setSaving(false); } }; const showAll = expandedGroupResult === (result.resultId || result.fetchedAt); const suggestions = [...new Set(result.suggestions || [])]; const currentPath = queryPath(plan); const analysisPath = compactAnalysisPath(result.breadcrumbs); const currentPathLabel = result.breadcrumbs?.find( (crumb) => crumb.resultId === result.resultId, )?.label; const ancestors = analysisPath.filter( (crumb) => crumb.resultId !== result.resultId && crumb.label !== currentPathLabel, ); const parent = ancestors.at(-1); const scope = resultScope(result, subject); const briefing = plan.analysis === "briefing"; const display = preferredDisplay || resultDisplay(plan); const viewLabels = [ !flow && plan.groupBy === "none" && !plan.secondaryGroupBy ? "指标卡" : "图表", "统计表", "明细", ]; const showFourTotals = briefing || (plan.kind === "assets" && plan.category === "all" && result.metrics.some((metric) => /库存/.test(metric.label))); const metricPriority = preferredDisplay === "metrics" && plan.category === "inventory" ? ["库存车辆", "待交车占库存", "异动占库存"] : undefined; const orderedMetrics = metricPriority ? [...result.metrics].sort((a, b) => { const rank = (label: string) => metricPriority.includes(label) ? metricPriority.indexOf(label) : metricPriority.length; return rank(a.label) - rank(b.label); }) : result.metrics; const visibleMetrics = orderedMetrics.slice(0, showFourTotals ? 4 : 3); const extraMetrics = orderedMetrics.slice(visibleMetrics.length); const outcomeLabels = { partial: "已回答可查部分", empty: "暂无符合条件数据", unsupported: "暂不支持该指标", clarification: "需要补充一个条件", help: "使用帮助", complete: "", }; const largest = useMemo( () => Math.max(1, ...result.groups.map((g) => g.count)), [result.groups], ); const option = useMemo( () => ({ color: ["#668874", "#b5bdc4", "#a58b65"], tooltip: { trigger: "axis", renderMode: "richText" }, legend: { bottom: 0 }, grid: { left: 36, right: 10, top: 12, bottom: 45 }, xAxis: { type: "category", data: result.daily?.map((d) => d.date.slice(5)), }, yAxis: { type: "value", minInterval: 1 }, series: (["delivered", "returned", "replaced"] as const).map( (key, i) => ({ name: ["交车(含替换)", "还车", "其中替换"][i], type: "line", data: result.daily?.map((d) => d[key]), }), ), animation: false, }), [result.daily], ); return (
{plan.kind !== "clarify" && !result.conciseCount && (
{currentPath.map((step, index) => ( {index > 0 && index < currentPath.length - 1 && ( ))}
{scope.company} {flow ? ` · ${scope.period}` : ""} {parent && ( )} {analysisPath.length > 1 && (
完整分析路径
)}
)} {correctionMessage && (
此回答已撤回

{correctionMessage}

{correctionResultId && ( )}
)} {result.correction && (
已纠正上一条回答

{result.correction.message}

)} {result.outcome && outcomeLabels[result.outcome] && (

{outcomeLabels[result.outcome]}

)}

{mobile ? mobileResultSummary(result) : result.summary}

{!result.conciseCount && !!visibleMetrics.length && (
{visibleMetrics.map((metric) => (
{metric.label} {metric.value.toLocaleString()} {metric.unit}
))}
)} {!result.conciseCount && !!extraMetrics.length && (
查看其余 {extraMetrics.length} 项指标
{extraMetrics.map((metric) => ( {metric.label}: {metric.value.toLocaleString()} {metric.unit} ))}
)} {!!result.unanswered?.length && (
尚未回答的部分 {result.unanswered.map((item) => (

{item.question} {item.reason}

))}
)} {result.gap && (

{result.gap.recorded ? "这个需求已记下。" : "可以点击下方「未解决我的问题」记录需求。"}

)} {plan.kind !== "clarify" && !result.conciseCount && ( <>
{result.inventoryBreakdown && display !== "metrics" && (

{result.inventoryBreakdown.title}

{result.inventoryBreakdown.summary}

{briefing && !!result.insights?.length && (
分析建议 · {result.insights.length}项
    {result.insights.map((text) => (
  • {text}
  • ))}
)}
{result.inventoryBreakdown.groups.slice(0, 5).map((group) => (
{group.name} {group.count}台 {group.share === undefined ? "" : `${group.share}%`}
))}
)} {!briefing && ( <>
维度与分析
{!flow && ( <> )} {flow && ( )}
{!!result.insights?.length && (
分析发现 · {result.insights.length}项
    {result.insights.map((text) => (
  • {text}
  • ))}
)} {!flow && !!result.exceptions?.some((item) => item.count) && (
异常与待交车
{result.exceptions?.map((e) => ( ))}
)} {result.groupComparison && display !== "metrics" && (

当前分组对比 · 数量最多的分组为基准: {result.groupComparison.baseline}

{[ "分组", "车辆数", "较基准差值", "占比", "运营率差(百分点)", ].map((h) => ( ))} {result.groupComparison.rows.map((r) => ( ))}
{h}
{r.name} {r.count} {r.delta} {r.share}% {r.rateDelta}
)} {result.comparison && display !== "metrics" && (

上一等长周期:{result.comparisonPeriod?.start} —{" "} {result.comparisonPeriod?.end}

{["指标", "本期", "上期", "差值", "变化率"].map( (h) => ( ), )} {result.comparison.map((m) => ( ))}
{h}
{m.label} {m.current} {m.previous} {m.delta > 0 ? "+" : ""} {m.delta} {m.rate === null ? "基期为0,不计算" : `${m.rate}%`}
)}
{(["chart", "table", "list"] as const).map((v, i) => ( ))}
{result.rows.length} {flow ? "笔业务" : "台车辆"}
{display === "metrics" ? null : plan.view === "list" ? ( ) : flow ? ( <> {plan.view === "chart" && (
)}
每日车次与下钻明细 · {result.daily?.length || 0}天
{[ "日期", "交车(含替换)", "还车", "其中替换", "查看", ].map((h) => ( ))} {result.daily?.map((d) => ( ))}
{h}
{d.date} {d.delivered} {d.returned} {d.replaced}
) : plan.view === "chart" ? (
{(showAll ? result.groups : result.groups.slice(0, 5)).map( (g) => ( ), )} {result.groups.length > 5 && ( )}

点击分组继续下钻 · 筛选条件自动用于下一次追问

) : mobile ? (
{result.groups.map(g =>
分组{g.name}
车辆数{g.count}
占比{g.share == null ? '—' : `${g.share}%`}
{plan.category !== 'operating' &&
更多指标

运营率 {g.rate}% · 异动 {g.abnormal ?? '—'} · 待交车 {g.pending ?? '—'}

}
)}
) : (
{[ "分组", "车辆数", "占比", ...(result.plan.category === "operating" ? [] : ["运营率", "异动", "待交车"]), "下钻", ].map((h) => ( ))} {result.groups.map((g) => ( {result.plan.category !== "operating" && <> } ))}
{h}
{g.name} {g.count} {g.share}%{g.rate}% {g.abnormal} {g.pending}
)} {!result.rows.length && (

当前条件没有匹配数据,可返回上层调整。

)} )}
)} {result.conciseCount && result.metrics[0]?.value > 0 && ( )}
统计口径、权限与数据时间{result.warning ? " · 有补充说明" : ""} {mobile &&

{result.summary}

}

公司:{scope.company} · 权限:{scope.permission}

生效条件:{scope.filters.join(" · ") || "无额外筛选"}

分组:{scope.grouping} · 期间:{scope.period}

源数据更新时间:{displayTime(scope.sourceUpdatedAt)} · 查询时间: {displayTime(result.fetchedAt)}

{result.contextNote &&

{result.contextNote}

} {result.warning &&

{result.warning}

} {scope.definitions.map((definition) => (

{definition}

))}

来源:{result.source}

{mobile ? (active ? "当前追问范围" : "继续追问") : (active ? "正在基于此结果追问" : "基于此结果追问")} )}> {plan.kind !== "clarify" && (!result.conciseCount || result.metrics[0]?.value > 0) && ( )} {notice && (

{notice}

)}
{!result.conciseCount && !!suggestions.length && (
{plan.kind === "clarify" ? "你可以这样继续" : "继续分析"} {suggestions.slice(0, 4).map((question) => ( ))}
)}
); } export const ResultView = memo( ResultViewContent, // Event handlers close over this result; restore reads the latest turns from turnsRef. (previous, next) => previous.result === next.result && previous.active === next.active && previous.subject === next.subject && previous.disabled === next.disabled && previous.correctionMessage === next.correctionMessage && previous.correctionResultId === next.correctionResultId && previous.display === next.display, );