136 lines
4.0 KiB
TypeScript
136 lines
4.0 KiB
TypeScript
import { useEditorStorage } from '~/.client/persistence/editor';
|
||
import { webBuilderStore } from '~/.client/stores/web-builder';
|
||
import type { ApiResponse } from '~/types/global';
|
||
import { createScopedLogger } from '~/utils/logger';
|
||
|
||
const logger = createScopedLogger('useGrapesProject');
|
||
|
||
export function useProject() {
|
||
const { saveEditorProject } = useEditorStorage();
|
||
|
||
/**
|
||
* 保存项目数据到后端数据库
|
||
*
|
||
* @param messageId 消息ID
|
||
* @param projectData GrapesJS项目数据
|
||
* @param sections 页面区块数据
|
||
* @returns 保存是否成功
|
||
*/
|
||
async function saveProject(messageId: string) {
|
||
if (!messageId) {
|
||
logger.error('保存项目失败: 消息ID不能为空');
|
||
return false;
|
||
}
|
||
|
||
// 保存之前,先保存所有页面
|
||
await webBuilderStore.saveAllPages('auto-save');
|
||
const projectPages = Object.values(webBuilderStore.pagesStore.pages.get()).filter((page) => page !== undefined);
|
||
const projectSections = Object.values(webBuilderStore.pagesStore.sections.get())
|
||
.filter((section) => section !== undefined)
|
||
.map((section) => ({
|
||
...section,
|
||
actionId: section.id,
|
||
}));
|
||
if (projectPages.length === 0 || projectSections.length === 0) {
|
||
logger.error('保存项目失败: 页面或 Section 不能为空');
|
||
return false;
|
||
}
|
||
const isConsistent = projectPages.every((page) => {
|
||
const actionIds = page.actionIds;
|
||
const content = page.content;
|
||
if (actionIds.length === 0) {
|
||
return true;
|
||
}
|
||
if (!content) {
|
||
return false;
|
||
}
|
||
return true;
|
||
});
|
||
if (!isConsistent) {
|
||
logger.error(
|
||
'保存项目失败: 页面内容与 actions 不一致',
|
||
JSON.stringify({
|
||
projectPages,
|
||
projectSections,
|
||
}),
|
||
);
|
||
return false;
|
||
}
|
||
try {
|
||
// 先保存在本地数据中
|
||
saveEditorProject(messageId, projectPages, projectSections);
|
||
// 再调用远程接口保存到后端数据库
|
||
// 使用原生 fetch 而非 Remix fetcher,避免触发 revalidation 导致流式请求中断
|
||
const response = await fetch('/api/project', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
messageId,
|
||
pages: projectPages,
|
||
sections: projectSections,
|
||
}),
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json().catch(() => ({}));
|
||
logger.error('保存项目失败:', errorData.message || '服务器错误');
|
||
return false;
|
||
}
|
||
|
||
logger.info('项目保存成功');
|
||
return true;
|
||
} catch (error) {
|
||
logger.error('保存GrapesJS项目失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 复制聊天及其相关内容(消息、GrapesJS项目数据和区块)
|
||
*
|
||
* @param chatId 要复制的聊天ID
|
||
* @param messageId 可选参数,当提供时只复制到该消息为止的消息(包含该消息);不提供时复制整个聊天
|
||
* @returns 成功时返回新聊天的ID,失败时返回undefined
|
||
*/
|
||
async function forkChat(chatId: string, messageId?: string) {
|
||
if (!chatId) {
|
||
logger.error('复制聊天失败: 聊天ID不能为空');
|
||
return undefined;
|
||
}
|
||
|
||
try {
|
||
// 调用后端API复制聊天
|
||
const response = await fetch('/api/chat/fork', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
sourceChatId: chatId,
|
||
messageId,
|
||
}),
|
||
});
|
||
|
||
const { data, success, message } = (await response.json()) as ApiResponse<string>;
|
||
|
||
if (!response.ok || !success) {
|
||
logger.error('复制聊天失败:', message);
|
||
return undefined;
|
||
}
|
||
|
||
logger.info(`成功复制聊天 ${chatId},新聊天ID: ${data}`);
|
||
return data;
|
||
} catch (error) {
|
||
logger.error('复制聊天过程中发生错误:', error);
|
||
return undefined;
|
||
}
|
||
}
|
||
|
||
return {
|
||
saveProject,
|
||
forkChat,
|
||
};
|
||
}
|