From 8aec8a60b3d0905d6844589a8824cd6da7cd3d42 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Wed, 22 Jan 2025 14:35:38 +0800 Subject: [PATCH] feat: add file reading functionality and integrate system agents - Introduced FileService to handle file reading operations via IPC. - Implemented a new IPC handler for reading files, enhancing the application's ability to access and manage data. - Integrated system agents from a JSON file, allowing dynamic loading of agent data into the application. - Updated the AgentsPage and AddAssistantPopup components to utilize the new system agents, improving user experience and functionality. - Enhanced application state management by adding resourcesPath to the runtime state, ensuring proper resource handling across components. --- .../src/config => resources/data}/agents.json | 0 src/main/ipc.ts | 6 ++++ src/main/services/FileService.ts | 7 ++++ src/preload/index.d.ts | 3 ++ src/preload/index.ts | 3 ++ .../components/Popups/AddAssistantPopup.tsx | 5 +-- src/renderer/src/hooks/useAppInit.ts | 3 +- src/renderer/src/pages/agents/AgentsPage.tsx | 20 +++-------- src/renderer/src/pages/agents/index.ts | 33 +++++++++++++++++++ src/renderer/src/store/runtime.ts | 6 ++++ src/renderer/src/types/index.ts | 1 + 11 files changed, 69 insertions(+), 18 deletions(-) rename {src/renderer/src/config => resources/data}/agents.json (100%) create mode 100644 src/main/services/FileService.ts create mode 100644 src/renderer/src/pages/agents/index.ts diff --git a/src/renderer/src/config/agents.json b/resources/data/agents.json similarity index 100% rename from src/renderer/src/config/agents.json rename to resources/data/agents.json diff --git a/src/main/ipc.ts b/src/main/ipc.ts index cc203cc0..a859117b 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -10,12 +10,14 @@ import AppUpdater from './services/AppUpdater' import BackupManager from './services/BackupManager' import { configManager } from './services/ConfigManager' import { ExportService } from './services/ExportService' +import FileService from './services/FileService' import FileStorage from './services/FileStorage' import { GeminiService } from './services/GeminiService' import KnowledgeService from './services/KnowledgeService' import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService' import { TrayService } from './services/TrayService' import { windowService } from './services/WindowService' +import { getResourcePath } from './utils' import { compress, decompress } from './utils/zip' const fileManager = new FileStorage() @@ -31,6 +33,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { appPath: app.getAppPath(), filesPath: path.join(app.getPath('userData'), 'Data', 'Files'), appDataPath: app.getPath('userData'), + resourcesPath: getResourcePath(), logsPath: log.transports.file.getFile().path })) @@ -130,6 +133,9 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle('file:copy', fileManager.copyFile) ipcMain.handle('file:binaryFile', fileManager.binaryFile) + // fs + ipcMain.handle('fs:read', FileService.readFile) + // minapp ipcMain.handle('minapp', (_, args) => { windowService.createMinappWindow({ diff --git a/src/main/services/FileService.ts b/src/main/services/FileService.ts new file mode 100644 index 00000000..39255e15 --- /dev/null +++ b/src/main/services/FileService.ts @@ -0,0 +1,7 @@ +import fs from 'node:fs' + +export default class FileService { + public static async readFile(_: Electron.IpcMainInvokeEvent, path: string) { + return fs.readFileSync(path, 'utf8') + } +} diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index efc7eeda..cbc98f03 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -56,6 +56,9 @@ declare global { copy: (fileId: string, destPath: string) => Promise binaryFile: (fileId: string) => Promise<{ data: Buffer; mime: string }> } + fs: { + read: (path: string) => Promise + } export: { toWord: (markdown: string, fileName: string) => Promise } diff --git a/src/preload/index.ts b/src/preload/index.ts index ce0801f5..2cdb5c69 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -47,6 +47,9 @@ const api = { copy: (fileId: string, destPath: string) => ipcRenderer.invoke('file:copy', fileId, destPath), binaryFile: (fileId: string) => ipcRenderer.invoke('file:binaryFile', fileId) }, + fs: { + read: (path: string) => ipcRenderer.invoke('fs:read', path) + }, export: { toWord: (markdown: string, fileName: string) => ipcRenderer.invoke('export:word', markdown, fileName) }, diff --git a/src/renderer/src/components/Popups/AddAssistantPopup.tsx b/src/renderer/src/components/Popups/AddAssistantPopup.tsx index 17678503..e902542e 100644 --- a/src/renderer/src/components/Popups/AddAssistantPopup.tsx +++ b/src/renderer/src/components/Popups/AddAssistantPopup.tsx @@ -1,8 +1,8 @@ import { SearchOutlined } from '@ant-design/icons' import { TopView } from '@renderer/components/TopView' -import systemAgents from '@renderer/config/agents.json' import { useAgents } from '@renderer/hooks/useAgents' import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant' +import { useSystemAgents } from '@renderer/pages/agents' import { createAssistantFromAgent } from '@renderer/services/AssistantService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { Agent, Assistant } from '@renderer/types' @@ -28,6 +28,7 @@ const PopupContainer: React.FC = ({ resolve }) => { const { defaultAssistant } = useDefaultAssistant() const { assistants, addAssistant } = useAssistants() const inputRef = useRef(null) + const systemAgents = useSystemAgents() const agents = useMemo(() => { const allAgents = [...userAgents, ...systemAgents] as Agent[] @@ -48,7 +49,7 @@ const PopupContainer: React.FC = ({ resolve }) => { return [newAgent, ...filtered] } return filtered - }, [assistants, defaultAssistant, searchText, userAgents]) + }, [assistants, defaultAssistant, searchText, systemAgents, userAgents]) const onCreateAssistant = async (agent: Agent) => { let assistant: Assistant diff --git a/src/renderer/src/hooks/useAppInit.ts b/src/renderer/src/hooks/useAppInit.ts index 0d3de29d..8697b36d 100644 --- a/src/renderer/src/hooks/useAppInit.ts +++ b/src/renderer/src/hooks/useAppInit.ts @@ -3,7 +3,7 @@ import { isLocalAi } from '@renderer/config/env' import db from '@renderer/databases' import i18n from '@renderer/i18n' import { useAppDispatch } from '@renderer/store' -import { setAvatar, setFilesPath, setUpdateState } from '@renderer/store/runtime' +import { setAvatar, setFilesPath, setResourcesPath, setUpdateState } from '@renderer/store/runtime' import { delay, runAsyncFunction } from '@renderer/utils' import { useLiveQuery } from 'dexie-react-hooks' import { useEffect } from 'react' @@ -71,6 +71,7 @@ export function useAppInit() { // set files path window.api.getAppInfo().then((info) => { dispatch(setFilesPath(info.filesPath)) + dispatch(setResourcesPath(info.resourcesPath)) }) }, [dispatch]) diff --git a/src/renderer/src/pages/agents/AgentsPage.tsx b/src/renderer/src/pages/agents/AgentsPage.tsx index a8defebe..dfc55891 100644 --- a/src/renderer/src/pages/agents/AgentsPage.tsx +++ b/src/renderer/src/pages/agents/AgentsPage.tsx @@ -1,7 +1,6 @@ import { SearchOutlined } from '@ant-design/icons' import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import Scrollbar from '@renderer/components/Scrollbar' -import SystemAgents from '@renderer/config/agents.json' import { createAssistantFromAgent } from '@renderer/services/AssistantService' import { Agent } from '@renderer/types' import { uuid } from '@renderer/utils' @@ -12,35 +11,26 @@ import { useTranslation } from 'react-i18next' import ReactMarkdown from 'react-markdown' import styled from 'styled-components' +import { useSystemAgents } from '.' import { groupTranslations } from './agentGroupTranslations' import AgentCard from './components/AgentCard' import MyAgents from './components/MyAgents' const { Title } = Typography -const getAgentsFromSystemAgents = () => { - const agents: Agent[] = [] - for (let i = 0; i < SystemAgents.length; i++) { - for (let j = 0; j < SystemAgents[i].group.length; j++) { - const agent = { ...SystemAgents[i], group: SystemAgents[i].group[j], topics: [], type: 'agent' } as Agent - agents.push(agent) - } - } - return agents -} - let _agentGroups: Record = {} const AgentsPage: FC = () => { const [search, setSearch] = useState('') const [searchInput, setSearchInput] = useState('') + const systemAgents = useSystemAgents() const agentGroups = useMemo(() => { if (Object.keys(_agentGroups).length === 0) { - _agentGroups = groupBy(getAgentsFromSystemAgents(), 'group') + _agentGroups = groupBy(systemAgents, 'group') } return _agentGroups - }, []) + }, [systemAgents]) const { t, i18n } = useTranslation() @@ -102,7 +92,7 @@ const AgentsPage: FC = () => { [t] ) - const getAgentFromSystemAgent = (agent: (typeof SystemAgents)[number]) => { + const getAgentFromSystemAgent = (agent: (typeof systemAgents)[number]) => { return { ...omit(agent, 'group'), name: agent.name, diff --git a/src/renderer/src/pages/agents/index.ts b/src/renderer/src/pages/agents/index.ts new file mode 100644 index 00000000..ecc5fc1b --- /dev/null +++ b/src/renderer/src/pages/agents/index.ts @@ -0,0 +1,33 @@ +import { useRuntime } from '@renderer/hooks/useRuntime' +import { Agent } from '@renderer/types' +import { runAsyncFunction } from '@renderer/utils' +import { useEffect, useState } from 'react' + +let _agents: Agent[] = [] + +const getAgentsFromSystemAgents = (systemAgents: any) => { + const agents: Agent[] = [] + for (let i = 0; i < systemAgents.length; i++) { + for (let j = 0; j < systemAgents[i].group.length; j++) { + const agent = { ...systemAgents[i], group: systemAgents[i].group[j], topics: [], type: 'agent' } as Agent + agents.push(agent) + } + } + return agents +} + +export function useSystemAgents() { + const [agents, setAgents] = useState(_agents) + const { resourcesPath } = useRuntime() + + useEffect(() => { + runAsyncFunction(async () => { + if (_agents.length > 0) return + const agents = await window.api.fs.read(resourcesPath + '/data/agents.json') + _agents = getAgentsFromSystemAgents(JSON.parse(agents) as Agent[]) + setAgents(_agents) + }) + }, [resourcesPath]) + + return agents +} diff --git a/src/renderer/src/store/runtime.ts b/src/renderer/src/store/runtime.ts index 2bd8ecc8..bf6a229d 100644 --- a/src/renderer/src/store/runtime.ts +++ b/src/renderer/src/store/runtime.ts @@ -22,6 +22,7 @@ export interface RuntimeState { minappShow: boolean searching: boolean filesPath: string + resourcesPath: string update: UpdateState webdavSync: WebDAVSyncState } @@ -32,6 +33,7 @@ const initialState: RuntimeState = { minappShow: false, searching: false, filesPath: '', + resourcesPath: '', update: { info: null, checking: false, @@ -65,6 +67,9 @@ const runtimeSlice = createSlice({ setFilesPath: (state, action: PayloadAction) => { state.filesPath = action.payload }, + setResourcesPath: (state, action: PayloadAction) => { + state.resourcesPath = action.payload + }, setUpdateState: (state, action: PayloadAction>) => { state.update = { ...state.update, ...action.payload } }, @@ -80,6 +85,7 @@ export const { setMinappShow, setSearching, setFilesPath, + setResourcesPath, setUpdateState, setWebDAVSyncState } = runtimeSlice.actions diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index fd3a56f1..a382e208 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -191,6 +191,7 @@ export type AppInfo = { isPackaged: boolean appPath: string appDataPath: string + resourcesPath: string filesPath: string logsPath: string }