feat: 收口 tree-first graph 主链与前端测试修复
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
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],
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user