feat(canvas): 画布同步 - 新增操作事务、预演保存与并发恢复能力
This commit is contained in:
@@ -151,20 +151,19 @@ function applyCanvasPersistenceJournal(document: CanvasStorageDocument, journal:
|
||||
}
|
||||
|
||||
async function preserveLocalRebaseConflicts(scope: string, projects: CanvasProject[], conflicts: ReturnType<typeof rebaseCanvasProjects>["conflicts"]) {
|
||||
if (!conflicts.length) return;
|
||||
if (!conflicts.length || getActiveUserScope() !== scope) return;
|
||||
const ids = new Set(conflicts.map((conflict) => conflict.projectId || conflict.id));
|
||||
const { preserveCanvasSyncDraftLocked } = await import("@/services/canvas-sync-drafts");
|
||||
for (const project of projects.filter((item) => ids.has(item.id))) {
|
||||
const draftCount = await preserveCanvasSyncDraftLocked(project, scope);
|
||||
if (getActiveUserScope() === scope) useSyncProgressStore.getState().setProjectProgress(project.id, {
|
||||
phase: "conflict", errorKind: "conflict", draftCount,
|
||||
message: "其他标签页也修改了此画布,本地版本已保留为草稿",
|
||||
useSyncProgressStore.getState().setProjectProgress(project.id, {
|
||||
phase: "pending",
|
||||
pendingChanges: 1,
|
||||
errorKind: "conflict",
|
||||
message: "其他标签页也修改了此画布,未确认操作会按字段重放",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function setCanvasLocalPersistenceProgress(projects: CanvasProject[], patch: Partial<SyncProjectProgress>) {
|
||||
const progress = useSyncProgressStore.getState();
|
||||
for (const project of projects) {
|
||||
@@ -462,6 +461,11 @@ function ordinaryCanvasProjectSnapshot(scope: string, project: CanvasProject, du
|
||||
if (durableKeys?.length) metadata.generationEffectKeys = [...durableKeys];
|
||||
else delete metadata.generationEffectKeys;
|
||||
nodes.push({ ...rolledBack, metadata });
|
||||
} else {
|
||||
// There is no durable ancestor for a locally-created result.
|
||||
// Preserve it in the recovery snapshot; the effect key makes a
|
||||
// later replay idempotent and the user must not lose the node.
|
||||
nodes.push(node);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -506,11 +510,14 @@ function ordinaryCanvasProjectSnapshot(scope: string, project: CanvasProject, du
|
||||
chatSessions.push(nextSession);
|
||||
}
|
||||
if (durableProject && hasUnconfirmedGeneration) {
|
||||
const localConnectionIds = new Set(project.connections.map((connection) => connection.id));
|
||||
return {
|
||||
...project,
|
||||
nodes,
|
||||
// Connection/active-chat records carry no effect stamp provenance, so keep them fail-closed while any generation entity is unconfirmed.
|
||||
connections: durableProject.connections,
|
||||
// Connection/active-chat records carry no effect stamp provenance. Keep
|
||||
// local connections and append durable-only ones instead of replacing
|
||||
// the whole list and dropping edits made during generation.
|
||||
connections: [...project.connections, ...durableProject.connections.filter((connection) => !localConnectionIds.has(connection.id))],
|
||||
chatSessions,
|
||||
activeChatId: durableProject.activeChatId,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user