Files
ttstd-infinite-canvas/web/src/services/creation-canvas-conversation.ts
ddcat666 080989ad5f fix(canvas): 画布同步 - 版本冲突先核对并合并,本地保存与云端状态分开展示
本地持久化记录保存时间和日志令牌,同步面板区分本地与云端,不再用提示打断编辑。冲突时先尝试合并,无法合并才保留草稿并暂停自动提交;未登录时生成结果留在本地,登录后再继续上传。
2026-10-09 17:17:38 +08:00

67 lines
4.6 KiB
TypeScript

import type { CanvasAssistantMessage, CanvasAssistantSession } from "@/types/canvas";
import { getActiveUserScope } from "@/lib/user-scope";
import { flushCanvasStorePersistence, useCanvasStore } from "@/stores/canvas/use-canvas-store";
import { createCanvasProjectWithRemoteSync, hasRemoteUserDataSyncSession, loadCanvasProjectForEditing, saveRemoteUserDataNow } from "@/services/user-data-sync";
type SourceMessage = {
id: string; role: "user" | "assistant"; content: string; createdAt: string;
mode?: string; status?: string; taskIds?: string[]; error?: string; model?: string;
settings?: unknown; attachments?: Array<{ name: string; storageKey?: string }>;
references?: Array<{ id: string; label: string; kind: string }>;
};
type SourceConversation = { id: string; title: string; updatedAt: string; canvasId?: string; messages: SourceMessage[] };
// Same source IDs merge into the same canvas conversation, without replaying tasks
// or replacing messages produced by the canvas Agent after the handoff.
export async function continueCreationConversationOnCanvas(source: SourceConversation) {
const scope = getActiveUserScope();
const assertScope = () => { if (scope !== getActiveUserScope()) throw new DOMException("账号已切换,请重新打开创作", "AbortError"); };
if (!source.id || !source.messages.length) throw new Error("请先开始一段创作对话");
if (source.messages.some((item) => item.status === "pending" || item.status === "streaming")) throw new Error("当前创作仍在生成,请完成或停止后再转入画布。");
if (!useCanvasStore.getState().hydrated) throw new Error("画布资料正在加载,请稍后继续。");
const sessionId = `creation:${source.id}`;
const messages: CanvasAssistantMessage[] = source.messages.map((item) => ({
id: `creation:${source.id}:${item.id}`, role: item.role,
text: item.content || (item.status === "done" ? "首页作品已生成,可在画布中继续创作。" : item.status === "error" ? "首页生成未完成。" : item.status === "cancelled" ? "首页生成已停止。" : "来自首页的创作记录"),
meta: "来自首页",
detail: { kind: "creation-handoff", sourceConversationId: source.id, sourceMessageId: item.id, createdAt: item.createdAt,
status: item.status, mode: item.mode, model: item.model, taskIds: item.taskIds, error: item.error, settings: item.settings,
references: item.references?.map(({ id, label, kind }) => ({ id, label, kind })),
attachments: item.attachments?.map(({ name, storageKey }) => ({ name, storageKey })),
},
}));
const local = useCanvasStore.getState().projects.find((item) => item.chatSessions?.some((session) => session.id === sessionId));
const existingId = source.canvasId || local?.id;
let id: string;
let syncError: unknown;
if (existingId) {
const project = await loadCanvasProjectForEditing(existingId);
assertScope();
if (!project) throw new Error("关联画布已不存在,请先恢复画布后继续。");
const sessions = project.chatSessions || [];
const previous = sessions.find((item) => item.id === sessionId);
const incoming = new Map(messages.map((item) => [item.id, item]));
const merged = (previous?.messages || []).map((item) => {
const replacement = incoming.get(item.id);
incoming.delete(item.id);
return replacement || item;
});
const session: CanvasAssistantSession = { ...previous, id: sessionId, title: source.title, createdAt: previous?.createdAt || source.messages[0].createdAt, updatedAt: new Date().toISOString(), messages: [...merged, ...incoming.values()] };
useCanvasStore.getState().updateProject(project.id, { chatSessions: [...sessions.filter((item) => item.id !== sessionId), session], activeChatId: sessionId });
id = project.id;
await flushCanvasStorePersistence();
assertScope();
try {
if (hasRemoteUserDataSyncSession()) await saveRemoteUserDataNow();
} catch (cause) { syncError = cause; }
} else {
const session: CanvasAssistantSession = { id: sessionId, title: source.title, createdAt: source.messages[0].createdAt, updatedAt: source.updatedAt, messages };
const created = await createCanvasProjectWithRemoteSync(source.title || "创作画布", undefined, { chatSessions: [session], activeChatId: sessionId });
id = created.id;
syncError = created.syncError;
await flushCanvasStorePersistence();
}
assertScope();
return { id, sessionId, syncError };
}