import { useMemo } from "react"; import { ArrowDown, ArrowUp, ArrowUpDown } from "lucide-react"; import { Input } from "@/shared/ui/input"; import { Button } from "@/shared/ui/button"; import { Checkbox } from "@/shared/ui/checkbox"; import { cn } from "@/shared/ui/cn"; import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/shared/ui/table"; import { AnswerReveal } from "./answer-motion"; import { AskAboutContent, SelectForAnalysis } from "./answer-ui-interactions"; import { useAnswerNodeState } from "./answer-view-state-provider"; import type { AnswerTableState } from "./answer-view-state"; export function InteractiveDataTable({ title, columns, rows, source, state: controlledState, onStateChange }: { title: string; columns: string[]; rows: string[][]; source?: string; state?: Omit; onStateChange?: (patch: Partial>) => void; }) { const [localState, patchLocal] = useAnswerNodeState("Table", { title, columns, rows }); const state = controlledState ?? localState; const patch = onStateChange ?? patchLocal; // The owning schema node migrates data changes. A complete restored snapshot // can change chart columns/range and restore a valid selection atomically. const { query, selection, sort } = state; const setSelection = (selection: number[]) => patch({ selection }); const filtered = useMemo(() => { const items = rows.map((cells, id) => ({ cells, id })) .filter(({ cells }) => cells.join(" ").toLocaleLowerCase().includes(query.trim().toLocaleLowerCase())); if (sort) items.sort((a, b) => { const aValue = a.cells[sort.column], bValue = b.cells[sort.column]; const compared = /^[-+]?\d+(\.\d+)?%?$/.test(aValue) && /^[-+]?\d+(\.\d+)?%?$/.test(bValue) ? Number.parseFloat(aValue) - Number.parseFloat(bValue) : aValue.localeCompare(bValue, "zh-CN", { numeric: true }); return sort.descending ? -compared : compared; }); return items; }, [rows, sort, query]); const selected = filtered.filter(({ id }) => selection.includes(id)); const quote = selected.map(({ cells }) => cells.map((cell, index) => `${columns[index]}:${cell}`).join(";")).join("\n"); return
{ patch({ query: event.currentTarget.value, selection: [] }); }} /> {filtered.length} / {rows.length} 行
{title} 0 && selected.length === Math.min(filtered.length, 100)} disabled={!filtered.length} onChange={(event) => setSelection(event.target.checked ? filtered.slice(0, 100).map(({ id }) => id) : [])} /> {columns.map((column, index) => )} {filtered.map(({ cells, id }) => = 100} onChange={(event) => setSelection(event.target.checked ? [...selection, id] : selection.filter((value) => value !== id))} /> {cells.map((cell, index) => {cell})} )} {!filtered.length ? {rows.length ? "当前筛选没有匹配的数据。" : "没有已加载的数据。"} : null}
0} className="flex flex-wrap items-center justify-between gap-1"> 已选 {selected.length} 行{filtered.length > 100 ? " · 每次最多选择前 100 行" : ""}
{!selected.length && filtered.length ?

勾选数据行可加入分析或继续提问。

: null}
; }