feat(agent): 会话接续 - 首页创作带入画布原助手
This commit is contained in:
67
web/src/services/creation-canvas-conversation.ts
Normal file
67
web/src/services/creation-canvas-conversation.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
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()) throw new Error("尚未建立云端同步会话");
|
||||
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 };
|
||||
}
|
||||
@@ -207,7 +207,7 @@ export function scheduleRemoteUserDataSync() {
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
export async function createCanvasProjectWithRemoteSync(title: string, projectId?: string, initialContent?: Partial<Pick<CanvasProject, "nodes" | "connections">>) {
|
||||
export async function createCanvasProjectWithRemoteSync(title: string, projectId?: string, initialContent?: Partial<Pick<CanvasProject, "nodes" | "connections" | "chatSessions" | "activeChatId">>) {
|
||||
const id = useCanvasStore.getState().createProject(title, projectId);
|
||||
if (initialContent) useCanvasStore.getState().updateProject(id, initialContent);
|
||||
if (!activeRemoteUserId) return { id, syncError: new Error("尚未建立云端同步会话") };
|
||||
|
||||
Reference in New Issue
Block a user