refactor: repartition server-side and client-side code

This commit is contained in:
LIlGG
2025-10-11 18:26:07 +08:00
parent 7acc4949fb
commit e9b573a276
309 changed files with 631 additions and 962 deletions

View File

@@ -0,0 +1,186 @@
import { useFetcher, useNavigate } from '@remix-run/react';
import { useCallback } from 'react';
import { toast } from 'sonner';
import {
deleteEditorProject,
duplicateEditorProject,
forkEditorProject,
openEditorDatabase,
} from '~/.client/persistence/editor';
import { getChatId } from '~/.client/stores/ai-state';
import { useProject } from './useProject';
export const editorDb = await openEditorDatabase();
export function useChatOperate() {
const navigate = useNavigate();
const deleteChatFetcher = useFetcher();
const updateChatFetcher = useFetcher();
const { forkChat: forkRemoteChat } = useProject();
/**
* 聊天分叉功能
*
* @param chatId 要复制的聊天ID
* @param messageId 消息ID指定复制到哪条消息为止
* @returns 新聊天的ID
*/
const forkMessage = async (chatId: string, messageId: string) => {
if (!chatId) {
return;
}
// 后端 fork 聊天信息,并返回新的聊天 ID
const newId = await forkRemoteChat(chatId, messageId);
// 前端 fork editor 项目信息
if (newId && editorDb) {
await forkEditorProject(editorDb, chatId, messageId, newId);
}
return newId;
};
/**
* 根据 ID 复制聊天
*
* @param listItemId 聊天 ID如果不提供则复制当前聊天
* @returns
*/
const duplicateCurrentChat = async (chatId?: string) => {
if (!chatId && !getChatId()) {
return;
}
const duplicateChatId = (chatId || getChatId()) as string;
try {
const newId = await forkRemoteChat(duplicateChatId);
if (newId && editorDb) {
await duplicateEditorProject(editorDb, duplicateChatId, newId);
}
navigate(`/chat/${newId}`, { replace: true });
toast.success('聊天复制成功');
} catch (error) {
toast.error('复制聊天失败');
console.log(error);
}
};
/**
* 根据聊天 ID 删除聊天
* @param chatId 聊天 ID
* @returns
*/
const deleteChat = async (chatId: string): Promise<void> => {
try {
// 尝试通过API删除
deleteChatFetcher.submit({ chatId }, { method: 'POST', action: '/api/chat/delete' });
// 同时从本地删除
if (editorDb) {
await deleteEditorProject(editorDb, chatId);
}
console.log('Successfully deleted chat:', chatId);
return Promise.resolve();
} catch (error) {
console.error('Failed to delete chat:', error);
throw error;
}
};
/**
* 根据选择的聊天 ID 批量删除聊天
* @param itemsToDeleteIds 要删除的聊天 ID 列表
* @returns
*/
const deleteSelectedItems = async (chatIds: string[]) => {
if (chatIds.length === 0) {
console.log('跳过批量删除: 没有要删除的聊天');
return;
}
console.log(`开始批量删除 ${chatIds.length} 个聊天`, chatIds);
// 通过 API 删除多个聊天
deleteChatFetcher.submit({ ids: JSON.stringify(chatIds) }, { method: 'POST', action: '/api/chat/delete' });
// 同时从本地删除
if (editorDb) {
let deletedCount = 0;
const errors: string[] = [];
for (const id of chatIds) {
try {
await deleteEditorProject(editorDb, id);
deletedCount++;
} catch (error) {
console.error(`Error deleting local chat ${id}:`, error);
errors.push(id);
}
}
// 日志本地删除结果
if (errors.length === 0) {
console.log(`Local deletion: ${deletedCount} chats deleted successfully`);
} else {
console.warn(`Local deletion: ${deletedCount} chats deleted. ${errors.length} failed.`);
}
}
};
/**
* 通过API更新聊天描述
* @param chatId 待更新的聊天 ID
* @param description 更新后的描述
* @returns
*/
const updateDescriptionViaApi = useCallback(
async (chatId: string, description: string): Promise<boolean> => {
try {
// 使用表单格式提交数据
updateChatFetcher.submit(
{
id: chatId,
description: description,
},
{
method: 'POST',
action: '/api/chat/update',
},
);
return true;
} catch (error) {
console.error('Failed to update description via API:', error);
return false;
}
},
[updateChatFetcher],
);
/**
* 更新 Chat 描述
* @param chatId 待更新的聊天 ID
* @param description 更新后的描述
*/
const updateChatDescription = async (description: string, chatId?: string) => {
const id = chatId || getChatId();
if (!id) {
return;
}
try {
await updateDescriptionViaApi(id, description);
} catch (error) {
toast.error('更新聊天描述失败: ' + (error as Error).message);
}
};
return {
updateChatFetcher,
deleteChatFetcher,
deleteChat,
deleteSelectedItems,
forkMessage,
duplicateCurrentChat,
updateChatDescription,
};
}