2026-04-18 05:43:49 +08:00
|
|
|
import type { SidebarInitialData } from "@/components/sidebar/types";
|
|
|
|
|
import type { SidebarDatasetListQueryResult } from "@/lib/sidebar-data";
|
|
|
|
|
import {
|
|
|
|
|
buildSidebarDatasetListQueryResult,
|
|
|
|
|
mapSidebarDatasetListQueryResultToInitialData,
|
|
|
|
|
} from "@/lib/sidebar-data";
|
|
|
|
|
import type { DocumentRecord } from "@/lib/documents";
|
|
|
|
|
import type { WorkspaceSummary } from "@/lib/workspaces";
|
|
|
|
|
import type { MediaAsset } from "@/types/media";
|
|
|
|
|
|
|
|
|
|
export type TreeStreamDeltaOp =
|
|
|
|
|
| "upsert_document"
|
|
|
|
|
| "remove_document"
|
|
|
|
|
| "replace_documents"
|
|
|
|
|
| "replace_sidebar";
|
|
|
|
|
|
|
|
|
|
export type TreeStreamDeltaEvent = {
|
|
|
|
|
op: TreeStreamDeltaOp;
|
|
|
|
|
node?: DocumentRecord | null;
|
|
|
|
|
document?: DocumentRecord | null;
|
|
|
|
|
documentId?: string | null;
|
|
|
|
|
documents?: DocumentRecord[] | null;
|
|
|
|
|
sidebar?: SidebarDatasetListQueryResult | SidebarInitialData | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function cloneSidebarData(data: SidebarInitialData): SidebarInitialData {
|
|
|
|
|
return {
|
|
|
|
|
...data,
|
|
|
|
|
workspaces: [...data.workspaces],
|
|
|
|
|
documents: [...data.documents],
|
2026-04-24 06:10:18 +08:00
|
|
|
kernelFileTreeProjection: {
|
|
|
|
|
...data.kernelFileTreeProjection,
|
|
|
|
|
items: [...data.kernelFileTreeProjection.items],
|
|
|
|
|
edges: [...data.kernelFileTreeProjection.edges],
|
|
|
|
|
},
|
2026-04-18 05:43:49 +08:00
|
|
|
kernelSidebarProjection: {
|
|
|
|
|
...data.kernelSidebarProjection,
|
|
|
|
|
items: [...data.kernelSidebarProjection.items],
|
|
|
|
|
edges: [...data.kernelSidebarProjection.edges],
|
|
|
|
|
},
|
|
|
|
|
kernelSidebarTree: [...data.kernelSidebarTree],
|
|
|
|
|
trashedDocuments: [...data.trashedDocuments],
|
|
|
|
|
trashedMediaAssets: [...(data.trashedMediaAssets ?? [])],
|
|
|
|
|
trashedMindmapAssets: [...(data.trashedMindmapAssets ?? [])],
|
|
|
|
|
trashedTableAssets: [...(data.trashedTableAssets ?? [])],
|
|
|
|
|
mindmapDocs: [...(data.mindmapDocs ?? [])],
|
|
|
|
|
mindmapAssets: [...(data.mindmapAssets ?? [])],
|
|
|
|
|
mindmapAssetChildren: { ...(data.mindmapAssetChildren ?? {}) },
|
|
|
|
|
tableAssets: [...(data.tableAssets ?? [])],
|
|
|
|
|
mediaAssets: [...(data.mediaAssets ?? [])],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildSidebarFromDocuments(input: {
|
|
|
|
|
base: SidebarInitialData;
|
|
|
|
|
documents: DocumentRecord[];
|
|
|
|
|
}): SidebarInitialData {
|
|
|
|
|
const queryResult = buildSidebarDatasetListQueryResult({
|
|
|
|
|
activeWorkspaceId: input.base.activeWorkspaceId,
|
|
|
|
|
workspaces: input.base.workspaces as WorkspaceSummary[],
|
|
|
|
|
documents: input.documents,
|
|
|
|
|
trashedDocuments: input.base.trashedDocuments,
|
|
|
|
|
mindmaps: (input.base.mindmapAssets ?? []).map((asset) => ({
|
|
|
|
|
mindmap_id: asset.id,
|
|
|
|
|
workspace_id: asset.workspace_id,
|
|
|
|
|
document_id: asset.document_id,
|
|
|
|
|
created_at: asset.created_at,
|
|
|
|
|
updated_at: asset.updated_at,
|
|
|
|
|
deleted_at: asset.deleted_at ?? null,
|
|
|
|
|
deleted_by: asset.deleted_by ?? null,
|
|
|
|
|
})),
|
|
|
|
|
mediaAssets: input.base.mediaAssets as MediaAsset[] | null,
|
|
|
|
|
trashedMediaAssets: input.base.trashedMediaAssets as MediaAsset[] | null,
|
|
|
|
|
tables: (input.base.tableAssets ?? []).map((asset) => ({
|
|
|
|
|
id: asset.id,
|
|
|
|
|
workspace_id: asset.workspace_id,
|
|
|
|
|
document_id: asset.document_id,
|
|
|
|
|
title: asset.file_name ?? null,
|
|
|
|
|
created_at: asset.created_at,
|
|
|
|
|
updated_at: asset.updated_at,
|
|
|
|
|
deleted_at: asset.deleted_at ?? null,
|
|
|
|
|
deleted_by: asset.deleted_by ?? null,
|
|
|
|
|
purged_at: asset.purged_at ?? null,
|
|
|
|
|
is_archived: Boolean(asset.deleted_at),
|
|
|
|
|
})),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return mapSidebarDatasetListQueryResultToInitialData(queryResult);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeUpsertDocument(event: TreeStreamDeltaEvent): DocumentRecord | null {
|
|
|
|
|
const candidate = event.node ?? event.document ?? null;
|
|
|
|
|
return candidate && typeof candidate === "object" ? candidate : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeDocumentId(event: TreeStreamDeltaEvent): string | null {
|
|
|
|
|
const candidate = typeof event.documentId === "string" ? event.documentId.trim() : "";
|
|
|
|
|
return candidate || null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function applyTreeStreamDelta(
|
|
|
|
|
base: SidebarInitialData,
|
|
|
|
|
event: TreeStreamDeltaEvent,
|
|
|
|
|
): SidebarInitialData {
|
|
|
|
|
if (event.op === "replace_sidebar" && event.sidebar) {
|
|
|
|
|
if ("activeWorkspaceId" in event.sidebar) {
|
|
|
|
|
return cloneSidebarData(event.sidebar as SidebarInitialData);
|
|
|
|
|
}
|
|
|
|
|
return mapSidebarDatasetListQueryResultToInitialData(event.sidebar as SidebarDatasetListQueryResult);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event.op === "replace_documents" && Array.isArray(event.documents)) {
|
|
|
|
|
return buildSidebarFromDocuments({
|
|
|
|
|
base,
|
|
|
|
|
documents: [...event.documents],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event.op === "upsert_document") {
|
|
|
|
|
const nextDocument = normalizeUpsertDocument(event);
|
|
|
|
|
if (!nextDocument) {
|
|
|
|
|
return base;
|
|
|
|
|
}
|
|
|
|
|
const nextDocuments = [...base.documents];
|
|
|
|
|
const existingIndex = nextDocuments.findIndex((item) => item.id === nextDocument.id);
|
|
|
|
|
if (existingIndex >= 0) {
|
|
|
|
|
nextDocuments[existingIndex] = nextDocument;
|
|
|
|
|
} else {
|
|
|
|
|
nextDocuments.push(nextDocument);
|
|
|
|
|
}
|
|
|
|
|
return buildSidebarFromDocuments({
|
|
|
|
|
base,
|
|
|
|
|
documents: nextDocuments,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event.op === "remove_document") {
|
|
|
|
|
const documentId = normalizeDocumentId(event);
|
|
|
|
|
if (!documentId) {
|
|
|
|
|
return base;
|
|
|
|
|
}
|
|
|
|
|
const removedIds = new Set<string>([documentId]);
|
|
|
|
|
let changed = true;
|
|
|
|
|
while (changed) {
|
|
|
|
|
changed = false;
|
|
|
|
|
for (const document of base.documents) {
|
|
|
|
|
if (document.parent_id && removedIds.has(document.parent_id) && !removedIds.has(document.id)) {
|
|
|
|
|
removedIds.add(document.id);
|
|
|
|
|
changed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return buildSidebarFromDocuments({
|
|
|
|
|
base,
|
|
|
|
|
documents: base.documents.filter((item) => !removedIds.has(item.id)),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return base;
|
|
|
|
|
}
|