feat: 插件声明式设置体系——registerSettings schema + 标准控件表单 + 一级导航入口开关

This commit is contained in:
Rick Ye
2026-09-11 01:45:32 +08:00
parent 8349db9947
commit 2ad1743e10
10 changed files with 887 additions and 13 deletions
+43 -1
View File
@@ -1,5 +1,5 @@
import { app, BrowserWindow, dialog, ipcMain, shell } from 'electron'
import { mkdirSync } from 'node:fs'
import { mkdirSync, readFileSync } from 'node:fs'
import { userInfo } from 'node:os'
import { AgentService } from './pi/agent-service'
import { getPluginHost } from './plugins/plugin-host'
@@ -404,6 +404,48 @@ export function registerIpc(): AgentService {
if (typeof id !== 'string' || !/^[a-z][a-z0-9-]*$/.test(id)) throw new Error('invalid plugin id')
return getPluginHost().readRendererSource(id)
})
// ---- 插件设置表单(声明式 schema;值持久化与动作回推在 plugin-host) ----
ipcMain.handle('plugins:settingsSchemas', () => getPluginHost().settingsSchemasList())
ipcMain.handle('plugins:settingsValues', () => getPluginHost().settingsValuesList())
ipcMain.handle('plugins:setSettingValue', (_e, id: unknown, key: unknown, value: unknown) => {
if (typeof id !== 'string' || typeof key !== 'string' || !key) throw new Error('invalid setting value')
getPluginHost().setSettingValue(id, key, value)
})
ipcMain.handle('plugins:setSettingsNav', (_e, id: unknown, on: unknown) => {
if (typeof id !== 'string' || typeof on !== 'boolean') throw new Error('invalid settings nav toggle')
return getPluginHost().setSettingsNav(id, on)
})
ipcMain.handle('plugins:settingsAction', (_e, id: unknown, key: unknown) => {
if (typeof id !== 'string' || typeof key !== 'string') throw new Error('invalid settings action')
getPluginHost().settingsAction(id, key)
})
// path/image 控件的系统选择框
ipcMain.handle('plugins:pickPath', async (event, mode: unknown) => {
if (mode !== 'file' && mode !== 'directory' && mode !== 'image') throw new Error('invalid path mode')
const win = BrowserWindow.fromWebContents(event.sender)
const filters =
mode === 'image'
? [{ name: '图片', extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg', 'ico', 'avif'] }]
: undefined
const options = { filters }
const result =
mode === 'directory'
? await (win ? dialog.showOpenDialog(win, { ...options, properties: ['openDirectory'] }) : dialog.showOpenDialog({ ...options, properties: ['openDirectory'] }))
: await (win ? dialog.showOpenDialog(win, { ...options, properties: ['openFile'] }) : dialog.showOpenDialog({ ...options, properties: ['openFile'] }))
return result.canceled || result.filePaths.length === 0 ? null : result.filePaths[0]
})
// image 控件缩略图:渲染层(dev 是 http 源)不能直接 <img src="file://">,回传 data URL
ipcMain.handle('plugins:readImage', (_e, path: unknown) => {
if (typeof path !== 'string' || !path || !/\.(png|jpe?g|gif|webp|bmp|svg|ico|avif)$/i.test(path)) return null
try {
const buf = readFileSync(path)
if (buf.length > 5 * 1024 * 1024) return null
const ext = path.split('.').pop()!.toLowerCase()
return `data:image/${ext === 'jpg' ? 'jpeg' : ext};base64,${buf.toString('base64')}`
} catch {
return null
}
})
ipcMain.handle('plugins:setEnabled', (_e, id: unknown, enabled: unknown) => {
if (typeof id !== 'string' || typeof enabled !== 'boolean') throw new Error('invalid plugin toggle')
return getPluginHost().setEnabled(id, enabled)