diff --git a/desktop/README.md b/desktop/README.md index e8961da..2f6ae39 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -8,7 +8,10 @@ - 静态托管 `renderer-dist`(即 `ui/dist` 的副本); - 把 `/api` 反代到**可配置的后端地址**。 - 前端源码 `ui/` **零改动**、后端 **CORS 零改动**:相对路径 `/api` 与 SSE 流靠同源天然可用。 -- 后端地址配置(`config.js`)优先级:`PLANTAI_BFF_URL` 环境变量 > `userData/config.json` > 默认 `http://127.0.0.1:8090`。可在应用内菜单「设置 → 后端地址…」修改(保存后重载)。 +- 后端各服务地址配置(`config.js`,存 `userData/config.json` 的 `services` 对象;应用内菜单「设置 → 后端服务地址…」逐项修改,保存后重载): + - `bff`(8090,总入口)、`qa`(8091)、`rag`(8092)、`legacy`(8093)、`vstore`(8094)、`paper`(8097)、`bioflow`(8098)——每项都可指向本机或其它服务器。 + - `PLANTAI_BFF_URL` 环境变量可覆盖 `bff`(脚本/CI 用)。 +- **关于多服务路由**:前端只调用 `/api/*`,`bff`(BFF)是唯一入口;反代把 `/api` 转到 `bff`,由 BFF 内部再路由到 qa/rag/vstore/paper/bioflow。因此要把某个服务挪到别的服务器,需改 **BFF 自己的配置**(`sdk/app/config.py` 的 `qa_base_url` / `rag_base_url` / `rag_legacy_base_url` / `vstore_base_url` / `paper_writer_url` / `bioflow_service_url`,或用 `.env` 覆盖);桌面端设置里其余服务的地址当前仅作记录/预留。 ## 环境要求 diff --git a/desktop/config.js b/desktop/config.js index dd4b95d..ece1910 100644 --- a/desktop/config.js +++ b/desktop/config.js @@ -1,39 +1,60 @@ -// config.js —— 后端地址配置的读写(主进程)。 -// 优先级:环境变量 PLANTAI_BFF_URL > userData/config.json > 默认 http://127.0.0.1:8090。 +// config.js —— 后端各服务地址配置的读写(主进程)。 +// 设置文件 userData/config.json 形如 { services: { bff, qa, rag, legacy, vstore, paper, bioflow } }。 +// 每个地址都可指向本机或其它服务器。功能上桌面端反代只走 bff(BFF 是前端 /api/* 的唯一入口, +// 由 BFF 内部再路由到 qa/rag/vstore/paper/bioflow——见 README)。 +// 环境变量 PLANTAI_BFF_URL 覆盖 bff(供脚本/CI 用)。 const fs = require('fs'); const path = require('path'); -const DEFAULT_BACKEND_URL = 'http://127.0.0.1:8090'; +const DEFAULT_SERVICES = { + bff: 'http://127.0.0.1:8090', // BFF 总入口(前端 /api/* 的唯一入口) + qa: 'http://127.0.0.1:8091', // QA 问答(qa_base_url) + rag: 'http://127.0.0.1:8092', // 方法学 RAG(rag_base_url) + legacy: 'http://127.0.0.1:8093', // 旧版 RAG(rag_legacy_base_url) + vstore: 'http://127.0.0.1:8094', // 文档向量库(vstore_base_url) + paper: 'http://127.0.0.1:8097', // 论文撰写(paper_writer_url) + bioflow: 'http://127.0.0.1:8098', // 生信 bioflow(bioflow_service_url) +}; + +const SERVICE_KEYS = Object.keys(DEFAULT_SERVICES); + +function normalizeUrl(url) { + return String(url || '').trim().replace(/\/+$/, ''); +} function configPath(userDataDir) { return path.join(userDataDir, 'config.json'); } -function normalizeBackendUrl(url) { - return String(url || '').trim().replace(/\/+$/, ''); -} - -function loadConfig(userDataDir) { - const envUrl = normalizeBackendUrl(process.env.PLANTAI_BFF_URL); - if (envUrl) return { backendUrl: envUrl, source: 'env' }; +function loadServices(userDataDir) { + const services = { ...DEFAULT_SERVICES }; + // 设置文件覆盖默认 try { const raw = fs.readFileSync(configPath(userDataDir), 'utf8'); const cfg = JSON.parse(raw); - const fileUrl = normalizeBackendUrl(cfg && cfg.backendUrl); - if (fileUrl) return { backendUrl: fileUrl, source: 'file' }; + const saved = (cfg && cfg.services) || {}; + for (const k of SERVICE_KEYS) { + const v = normalizeUrl(saved[k]); + if (v) services[k] = v; + } } catch (_e) { // 无配置文件或解析失败 → 落到默认 } - return { backendUrl: DEFAULT_BACKEND_URL, source: 'default' }; + // 环境变量最高优先级,最后覆盖 bff + const envBff = normalizeUrl(process.env.PLANTAI_BFF_URL); + if (envBff) services.bff = envBff; + return services; } -function saveConfig(userDataDir, backendUrl) { +function saveServices(userDataDir, services) { fs.mkdirSync(userDataDir, { recursive: true }); - fs.writeFileSync( - configPath(userDataDir), - JSON.stringify({ backendUrl: normalizeBackendUrl(backendUrl) }, null, 2), - 'utf8' - ); + const out = {}; + for (const k of SERVICE_KEYS) { + const v = normalizeUrl(services && services[k]); + out[k] = v || DEFAULT_SERVICES[k]; + } + fs.writeFileSync(configPath(userDataDir), JSON.stringify({ services: out }, null, 2), 'utf8'); + return out; } -module.exports = { loadConfig, saveConfig, DEFAULT_BACKEND_URL }; +module.exports = { loadServices, saveServices, DEFAULT_SERVICES, SERVICE_KEYS }; diff --git a/desktop/main.js b/desktop/main.js index ece5fd6..a790b4a 100644 --- a/desktop/main.js +++ b/desktop/main.js @@ -1,11 +1,13 @@ // main.js —— Electron 主进程(瘦客户端壳)。 -// 启动流程:读后端地址配置 → 起本地静态 + /api 反代服务器(仅监听 127.0.0.1)→ 建窗口加载。 +// 启动流程:读后端各服务地址配置 → 起本地静态 + /api 反代服务器(仅监听 127.0.0.1)→ 建窗口加载。 // 前端 ui/ 源码零改动:相对路径 /api 与 SSE 流靠「同源」天然可用,后端 CORS 也无需改。 +// 反代功能上只走 bff(BFF 是前端唯一入口);qa/rag/vstore/paper/bioflow 地址在设置里可改、持久化, +// 实际的各服务再路由由 BFF 自己的 config/.env 完成(见 README)。 const { app, BrowserWindow, Menu, ipcMain, dialog } = require('electron'); const path = require('path'); const fs = require('fs'); const { createServer } = require('./server'); -const { loadConfig, saveConfig } = require('./config'); +const { loadServices, saveServices } = require('./config'); let mainWindow = null; let settingsWindow = null; @@ -19,9 +21,9 @@ function staticDir() { : path.join(__dirname, 'renderer-dist'); } -function startServer(backendUrl) { +function startServer(bffUrl) { return new Promise((resolve, reject) => { - const srv = createServer({ staticDir: staticDir(), backendUrl }); + const srv = createServer({ staticDir: staticDir(), backendUrl: bffUrl }); srv.once('error', reject); srv.listen(0, '127.0.0.1', () => { server = srv; @@ -37,9 +39,9 @@ function stopServer() { } } -async function restartServer(backendUrl) { +async function restartServer(bffUrl) { stopServer(); - currentOrigin = await startServer(backendUrl); + currentOrigin = await startServer(bffUrl); return currentOrigin; } @@ -71,12 +73,12 @@ function openSettingsWindow() { return; } settingsWindow = new BrowserWindow({ - width: 460, - height: 300, + width: 500, + height: 560, resizable: false, minimizable: false, maximizable: false, - title: '后端地址设置', + title: '后端服务设置', parent: mainWindow || undefined, modal: !!mainWindow, autoHideMenuBar: true, @@ -98,7 +100,7 @@ function buildMenu() { { label: '设置', submenu: [ - { label: '后端地址…', click: openSettingsWindow }, + { label: '后端服务地址…', click: openSettingsWindow }, { type: 'separator' }, { role: 'reload' }, { role: 'toggleDevTools' }, @@ -129,18 +131,18 @@ app.whenReady().then(async () => { return; } - ipcMain.handle('desktop:get-backend-url', () => loadConfig(app.getPath('userData')).backendUrl); - ipcMain.handle('desktop:set-backend-url', async (_event, url) => { + ipcMain.handle('desktop:get-config', () => loadServices(app.getPath('userData'))); + ipcMain.handle('desktop:set-config', async (_event, services) => { // 用户显式保存后,文件配置优先于环境变量(否则 env 会一直盖住用户设置) delete process.env.PLANTAI_BFF_URL; - saveConfig(app.getPath('userData'), url); - await restartServer(loadConfig(app.getPath('userData')).backendUrl); + const saved = saveServices(app.getPath('userData'), services); + await restartServer(saved.bff); if (mainWindow) await mainWindow.loadURL(currentOrigin); - return loadConfig(app.getPath('userData')).backendUrl; + return saved; }); - const { backendUrl } = loadConfig(app.getPath('userData')); - currentOrigin = await startServer(backendUrl); + const services = loadServices(app.getPath('userData')); + currentOrigin = await startServer(services.bff); createMainWindow(); app.on('activate', () => { diff --git a/desktop/preload.js b/desktop/preload.js index f7e055d..78a0754 100644 --- a/desktop/preload.js +++ b/desktop/preload.js @@ -2,6 +2,6 @@ const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('desktopApi', { - getBackendUrl: () => ipcRenderer.invoke('desktop:get-backend-url'), - setBackendUrl: (url) => ipcRenderer.invoke('desktop:set-backend-url', url), + getConfig: () => ipcRenderer.invoke('desktop:get-config'), + setConfig: (services) => ipcRenderer.invoke('desktop:set-config', services), }); diff --git a/desktop/settings.html b/desktop/settings.html index 8cb6cfc..ae89f4c 100644 --- a/desktop/settings.html +++ b/desktop/settings.html @@ -3,7 +3,7 @@ - 后端地址设置 + 后端服务设置 -

后端地址

-

PlantAI 后端(BFF)地址,例如 http://127.0.0.1:8090。保存后立即重载页面。

- +

后端服务地址

+

各服务地址可指向本机或其它服务器。前端统一经 BFF 总入口访问,由 BFF 内部再路由到其余服务。保存后立即重载。

+ +
+
+
+
+
+
+
+

+