import type { SidebarInitialData } from "@/components/sidebar/types"; import type { DocumentRecord } from "@/lib/documents"; import { buildKernelSidebarProjection as buildProjectionContract, buildSidebarTreeFromKernelProjection, type KernelSidebarProjection, } from "@/lib/kernel-sidebar"; import type { WorkspaceSummary } from "@/lib/workspaces"; import type { MediaAsset } from "@/types/media"; type MindmapRow = { mindmap_id: string; workspace_id?: string | null; document_id: string; data?: unknown; created_at?: string | null; updated_at?: string | null; deleted_at?: string | null; deleted_by?: string | null; }; type TableRow = { id: string; workspace_id?: string | null; document_id: string; title?: string | null; created_at?: string | null; updated_at?: string | null; deleted_at?: string | null; deleted_by?: string | null; purged_at?: string | null; is_archived?: boolean | null; }; export type SidebarDatasetInput = { activeWorkspaceId: string; workspaces: WorkspaceSummary[]; documents: DocumentRecord[]; trashedDocuments: SidebarInitialData["trashedDocuments"]; mindmaps: MindmapRow[]; mediaAssets?: MediaAsset[] | null; trashedMediaAssets?: MediaAsset[] | null; tables?: TableRow[] | null; }; export type SidebarDatasetListQueryPayload = { workspace_id: string; }; export type SidebarDatasetListQueryResult = { active_workspace_id: string; workspaces: WorkspaceSummary[]; documents: DocumentRecord[]; kernel_sidebar_projection?: KernelSidebarProjection; kernelSidebarProjection?: KernelSidebarProjection; trashed_documents: SidebarInitialData["trashedDocuments"]; media_assets: MediaAsset[]; trashed_media_assets: MediaAsset[]; mindmap_assets: MediaAsset[]; trashed_mindmap_assets: MediaAsset[]; table_assets: MediaAsset[]; trashed_table_assets: MediaAsset[]; mindmap_docs: string[]; mindmap_asset_children: Record; }; const EMPTY_KERNEL_SIDEBAR_PROJECTION: KernelSidebarProjection = { projectionId: "kernel_projection:sidebar_tree:missing", projection: "sidebar_tree", rootNodeId: null, items: [], edges: [], }; function isKernelSidebarProjection(value: unknown): value is KernelSidebarProjection { return ( Boolean(value) && typeof value === "object" && Array.isArray((value as KernelSidebarProjection).items) && Array.isArray((value as KernelSidebarProjection).edges) ); } function readKernelSidebarProjection( result: SidebarDatasetListQueryResult, ): KernelSidebarProjection { const snakeCaseProjection = "kernel_sidebar_projection" in result ? result.kernel_sidebar_projection : undefined; if (isKernelSidebarProjection(snakeCaseProjection)) { return snakeCaseProjection; } const camelCaseProjection = "kernelSidebarProjection" in result ? result.kernelSidebarProjection : undefined; if (isKernelSidebarProjection(camelCaseProjection)) { return camelCaseProjection; } if (Array.isArray(result.documents) && result.documents.length > 0) { // 说明:部分 query transport 只回 documents,没有同步附带 projection。 // 这里按同一协议即时重建,避免 UI 把缺失节点全部降级到根层。 return buildProjectionContract(result.documents); } return EMPTY_KERNEL_SIDEBAR_PROJECTION; } function normalizeStringArray(values: Iterable): string[] { return Array.from(new Set(values)).filter((value) => value.trim().length > 0); } export function buildSidebarDatasetListQueryPayload( workspaceId: string, ): SidebarDatasetListQueryPayload { return { workspace_id: workspaceId.trim(), }; } export function extractMindmapImageAssetIdsFromData(input: unknown): string[] { const root = (() => { if (!input || typeof input !== "object") return input; const record = input as Record; return record && typeof record === "object" && "root" in record ? record.root : input; })(); const ids: string[] = []; const seen = new Set(); const push = (value: unknown) => { if (typeof value !== "string") return; if (!value.startsWith("asset:")) return; const id = value.slice("asset:".length).trim(); if (!id || seen.has(id)) return; seen.add(id); ids.push(id); }; const get = (obj: unknown, key: string): unknown => { if (!obj || typeof obj !== "object") return undefined; return (obj as Record)[key]; }; const walk = (node: unknown) => { if (!node || typeof node !== "object") return; const data = get(node, "data"); const image = get(node, "image"); push(get(data, "image")); push(image); push(get(image, "url")); push(get(get(data, "image"), "url")); const children = get(node, "children"); if (Array.isArray(children)) { children.forEach(walk); } }; walk(root); return ids; } function toMindmapAsset(row: MindmapRow, workspaceId: string): MediaAsset { const isLegacy = row.mindmap_id.startsWith("legacy-"); return { id: row.mindmap_id, workspace_id: row.workspace_id ?? workspaceId, document_id: row.document_id, asset_type: "mindmap", file_url: null, thumbnail_url: null, bucket: null, storage_path: null, file_name: isLegacy ? "mindmap.json" : `mindmap-${row.mindmap_id}.json`, file_size: null, mime_type: "application/json", ocr_payload: undefined, ocr_strategy: null, ocr_text: null, ocr_status: null, signed_url: null, created_at: row.created_at ?? "", updated_at: row.updated_at ?? "", }; } function toTrashedMindmapAsset(row: MindmapRow, workspaceId: string): MediaAsset { return { ...toMindmapAsset(row, workspaceId), deleted_at: row.deleted_at ?? null, deleted_by: row.deleted_by ?? null, purged_at: null, }; } function toTableAsset(row: TableRow, workspaceId: string): MediaAsset { const base = String(row.title ?? "未命名表格").trim() || "未命名表格"; const fileName = base.toLowerCase().endsWith(".luckysheet") ? base : `${base}.luckysheet`; return { id: row.id, workspace_id: row.workspace_id ?? workspaceId, document_id: row.document_id, asset_type: "luckysheet", file_url: null, thumbnail_url: null, bucket: null, storage_path: null, file_name: fileName, file_size: null, mime_type: "application/json", ocr_payload: undefined, ocr_strategy: null, ocr_text: null, ocr_status: null, signed_url: null, created_at: row.created_at ?? "", updated_at: row.updated_at ?? "", }; } function toTrashedTableAsset(row: TableRow, workspaceId: string): MediaAsset { return { ...toTableAsset(row, workspaceId), deleted_at: row.deleted_at ?? row.updated_at ?? null, deleted_by: row.deleted_by ?? null, purged_at: row.purged_at ?? null, }; } function deriveSidebarDataset(input: SidebarDatasetInput) { const activeMindmaps = input.mindmaps.filter((row) => !row.deleted_at); const trashedMindmaps = input.mindmaps.filter((row) => Boolean(row.deleted_at)); const activeTables = (input.tables ?? []).filter((row) => !row.is_archived); const trashedTables = (input.tables ?? []).filter((row) => Boolean(row.is_archived)); const mindmapAssetChildren: Record = {}; activeMindmaps.forEach((row) => { const ids = extractMindmapImageAssetIdsFromData(row.data); if (ids.length > 0) { mindmapAssetChildren[row.mindmap_id] = ids; } }); return { mindmapAssetChildren, mindmapAssets: activeMindmaps.map((row) => toMindmapAsset(row, input.activeWorkspaceId)), mindmapDocs: normalizeStringArray(activeMindmaps.map((row) => row.document_id)), tableAssets: activeTables.map((row) => toTableAsset(row, input.activeWorkspaceId)), trashedMindmapAssets: trashedMindmaps.map((row) => toTrashedMindmapAsset(row, input.activeWorkspaceId), ), trashedTableAssets: trashedTables.map((row) => toTrashedTableAsset(row, input.activeWorkspaceId), ), }; } export function buildSidebarDatasetListQueryResult( input: SidebarDatasetInput, ): SidebarDatasetListQueryResult { const derived = deriveSidebarDataset(input); const kernelSidebarProjection = buildProjectionContract(input.documents); return { active_workspace_id: input.activeWorkspaceId, workspaces: [...input.workspaces], documents: [...input.documents], kernel_sidebar_projection: kernelSidebarProjection, trashed_documents: [...input.trashedDocuments], media_assets: [...(input.mediaAssets ?? [])], trashed_media_assets: [...(input.trashedMediaAssets ?? [])], mindmap_assets: derived.mindmapAssets, trashed_mindmap_assets: derived.trashedMindmapAssets, table_assets: derived.tableAssets, trashed_table_assets: derived.trashedTableAssets, mindmap_docs: derived.mindmapDocs, mindmap_asset_children: { ...derived.mindmapAssetChildren }, }; } export function mapSidebarDatasetListQueryResultToInitialData( result: SidebarDatasetListQueryResult, ): SidebarInitialData { const kernelSidebarProjection = readKernelSidebarProjection(result); return { activeWorkspaceId: result.active_workspace_id, workspaces: [...result.workspaces], documents: [...result.documents], kernelSidebarProjection, kernelSidebarTree: buildSidebarTreeFromKernelProjection({ records: result.documents, projection: kernelSidebarProjection, }), trashedDocuments: [...result.trashed_documents], trashedMediaAssets: [...result.trashed_media_assets], trashedMindmapAssets: [...result.trashed_mindmap_assets], trashedTableAssets: [...result.trashed_table_assets], mindmapDocs: [...result.mindmap_docs], mindmapAssets: [...result.mindmap_assets], mindmapAssetChildren: { ...result.mindmap_asset_children }, tableAssets: [...result.table_assets], mediaAssets: [...result.media_assets], }; } export function buildSidebarInitialData(input: SidebarDatasetInput): SidebarInitialData { const queryResult = buildSidebarDatasetListQueryResult(input); return mapSidebarDatasetListQueryResultToInitialData(queryResult); }