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.
This commit is contained in:
kangfenmao 2025-01-22 14:35:38 +08:00
parent a566b0e91a
commit 8aec8a60b3
11 changed files with 69 additions and 18 deletions

View File

@ -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({

View File

@ -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')
}
}

View File

@ -56,6 +56,9 @@ declare global {
copy: (fileId: string, destPath: string) => Promise<void>
binaryFile: (fileId: string) => Promise<{ data: Buffer; mime: string }>
}
fs: {
read: (path: string) => Promise<string>
}
export: {
toWord: (markdown: string, fileName: string) => Promise<void>
}

View File

@ -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)
},

View File

@ -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<Props> = ({ resolve }) => {
const { defaultAssistant } = useDefaultAssistant()
const { assistants, addAssistant } = useAssistants()
const inputRef = useRef<InputRef>(null)
const systemAgents = useSystemAgents()
const agents = useMemo(() => {
const allAgents = [...userAgents, ...systemAgents] as Agent[]
@ -48,7 +49,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
return [newAgent, ...filtered]
}
return filtered
}, [assistants, defaultAssistant, searchText, userAgents])
}, [assistants, defaultAssistant, searchText, systemAgents, userAgents])
const onCreateAssistant = async (agent: Agent) => {
let assistant: Assistant

View File

@ -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])

View File

@ -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<string, Agent[]> = {}
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,

View File

@ -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<Agent[]>(_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
}

View File

@ -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<string>) => {
state.filesPath = action.payload
},
setResourcesPath: (state, action: PayloadAction<string>) => {
state.resourcesPath = action.payload
},
setUpdateState: (state, action: PayloadAction<Partial<UpdateState>>) => {
state.update = { ...state.update, ...action.payload }
},
@ -80,6 +85,7 @@ export const {
setMinappShow,
setSearching,
setFilesPath,
setResourcesPath,
setUpdateState,
setWebDAVSyncState
} = runtimeSlice.actions

View File

@ -191,6 +191,7 @@ export type AppInfo = {
isPackaged: boolean
appPath: string
appDataPath: string
resourcesPath: string
filesPath: string
logsPath: string
}