@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
211 lines (181 loc) • 7.21 kB
text/typescript
import {
ListLocalFileParams,
LocalMoveFilesResultItem,
LocalReadFileParams,
LocalReadFilesParams,
LocalSearchFilesParams,
MoveLocalFilesParams,
RenameLocalFileParams,
WriteLocalFileParams,
} from '@lobechat/electron-client-ipc';
import { StateCreator } from 'zustand/vanilla';
import { localFileService } from '@/services/electron/localFileService';
import { ChatStore } from '@/store/chat/store';
import {
LocalFileListState,
LocalFileSearchState,
LocalMoveFilesState,
LocalReadFileState,
LocalReadFilesState,
LocalRenameFileState,
} from '@/tools/local-system/type';
export interface LocalFileAction {
internal_triggerLocalFileToolCalling: <T = any>(
id: string,
callingService: () => Promise<{ content: any; state?: T }>,
) => Promise<boolean>;
listLocalFiles: (id: string, params: ListLocalFileParams) => Promise<boolean>;
moveLocalFiles: (id: string, params: MoveLocalFilesParams) => Promise<boolean>;
reSearchLocalFiles: (id: string, params: LocalSearchFilesParams) => Promise<boolean>;
readLocalFile: (id: string, params: LocalReadFileParams) => Promise<boolean>;
readLocalFiles: (id: string, params: LocalReadFilesParams) => Promise<boolean>;
renameLocalFile: (id: string, params: RenameLocalFileParams) => Promise<boolean>;
// Added rename action
searchLocalFiles: (id: string, params: LocalSearchFilesParams) => Promise<boolean>;
toggleLocalFileLoading: (id: string, loading: boolean) => void;
writeLocalFile: (id: string, params: WriteLocalFileParams) => Promise<boolean>;
}
export const localFileSlice: StateCreator<
ChatStore,
[['zustand/devtools', never]],
[],
LocalFileAction
> = (set, get) => ({
internal_triggerLocalFileToolCalling: async (id, callingService) => {
get().toggleLocalFileLoading(id, true);
try {
const { state, content } = await callingService();
if (state) {
await get().updatePluginState(id, state as any);
}
await get().internal_updateMessageContent(id, JSON.stringify(content));
} catch (error) {
await get().internal_updateMessagePluginError(id, {
body: error,
message: (error as Error).message,
type: 'PluginServerError',
});
}
get().toggleLocalFileLoading(id, false);
return true;
},
listLocalFiles: async (id, params) => {
return get().internal_triggerLocalFileToolCalling<LocalFileListState>(id, async () => {
const result = await localFileService.listLocalFiles(params);
const state: LocalFileListState = { listResults: result };
return { content: result, state };
});
},
moveLocalFiles: async (id, params) => {
return get().internal_triggerLocalFileToolCalling<LocalMoveFilesState>(id, async () => {
const results: LocalMoveFilesResultItem[] = await localFileService.moveLocalFiles(params);
// 检查所有文件是否成功移动以更新消息内容
const allSucceeded = results.every((r) => r.success);
const someFailed = results.some((r) => !r.success);
const successCount = results.filter((r) => r.success).length;
const failedCount = results.length - successCount;
let message = '';
if (allSucceeded) {
message = `Successfully moved ${results.length} item(s).`;
} else if (someFailed) {
message = `Moved ${successCount} item(s) successfully. Failed to move ${failedCount} item(s).`;
} else {
// 所有都失败了?
message = `Failed to move all ${results.length} item(s).`;
}
const state: LocalMoveFilesState = { results, successCount, totalCount: results.length };
return { content: { message, results }, state };
});
},
reSearchLocalFiles: async (id, params) => {
get().toggleLocalFileLoading(id, true);
await get().updatePluginArguments(id, params);
return get().searchLocalFiles(id, params);
},
readLocalFile: async (id, params) => {
return get().internal_triggerLocalFileToolCalling<LocalReadFileState>(id, async () => {
const result = await localFileService.readLocalFile(params);
const state: LocalReadFileState = { fileContent: result };
return { content: result, state };
});
},
readLocalFiles: async (id, params) => {
return get().internal_triggerLocalFileToolCalling<LocalReadFilesState>(id, async () => {
const results = await localFileService.readLocalFiles(params);
const state: LocalReadFilesState = { filesContent: results };
return { content: results, state };
});
},
renameLocalFile: async (id, params) => {
return get().internal_triggerLocalFileToolCalling<LocalRenameFileState>(id, async () => {
const { path: currentPath, newName } = params;
// Basic validation for newName (can be done here or backend, maybe better in backend)
if (
!newName ||
newName.includes('/') ||
newName.includes('\\') ||
newName === '.' ||
newName === '..' ||
/["*/:<>?\\|]/.test(newName)
) {
throw new Error(
'Invalid new name provided. It cannot be empty, contain path separators, or invalid characters.',
);
}
const result = await localFileService.renameLocalFile({ newName, path: currentPath }); // Call the specific service
let state: LocalRenameFileState;
let content: { message: string; success: boolean };
if (result.success) {
state = { newPath: result.newPath!, oldPath: currentPath, success: true };
// Simplified message
content = {
message: `Successfully renamed file ${currentPath} to ${newName}.`,
success: true,
};
} else {
const errorMessage = result.error;
state = {
error: errorMessage,
newPath: '',
oldPath: params.path,
success: false,
};
content = { message: errorMessage, success: false };
}
return { content, state };
});
},
searchLocalFiles: async (id, params) => {
return get().internal_triggerLocalFileToolCalling<LocalFileSearchState>(id, async () => {
const result = await localFileService.searchLocalFiles(params);
const state: LocalFileSearchState = { searchResults: result };
return { content: result, state };
});
},
toggleLocalFileLoading: (id, loading) => {
// Assuming a loading state structure similar to searchLoading
set(
(state) => ({
localFileLoading: { ...state.localFileLoading, [id]: loading },
}),
false,
`toggleLocalFileLoading/${loading ? 'start' : 'end'}`,
);
},
writeLocalFile: async (id, params) => {
return get().internal_triggerLocalFileToolCalling(id, async () => {
const result = await localFileService.writeFile(params);
let content: { message: string; success: boolean };
if (result.success) {
content = {
message: `成功写入文件 ${params.path}`,
success: true,
};
} else {
const errorMessage = result.error;
content = { message: errorMessage || '写入文件失败', success: false };
}
return { content };
});
},
});