From 3f11e7c3cd9599971dd4fa6b09f8341eb172c5a5 Mon Sep 17 00:00:00 2001 From: thzxx Date: Wed, 10 Jun 2026 15:38:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=BE=E7=BD=AE=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=90=86=E9=85=8D=E7=BD=AE=20?= =?UTF-8?q?=E2=80=94=20web=5Fsearch/web=5Ffetch=E8=B5=B0HTTP=E4=BB=A3?= =?UTF-8?q?=E7=90=86(Clash=E7=AD=89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/ipc.ts | 15 +++++++++++++++ src/main/preload.ts | 3 ++- src/renderer/components/settings-modal.ts | 12 ++++++++++++ src/renderer/index.html | 5 +++++ src/renderer/main.ts | 15 +++++++++++++++ src/renderer/types.d.ts | 1 + 6 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/main/ipc.ts b/src/main/ipc.ts index fd466cf..78303ee 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -231,6 +231,21 @@ export async function setupIPC(): Promise { return { success: true }; }); + // ── 代理设置(web_search/web_fetch 走代理)── + ipcMain.handle('proxy:set', (_, url: string) => { + if (url) { + process.env.HTTP_PROXY = url; + process.env.HTTPS_PROXY = url; + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + sendLog('info', `🌐 代理已设置`, url); + } else { + delete process.env.HTTP_PROXY; + delete process.env.HTTPS_PROXY; + delete process.env.NODE_TLS_REJECT_UNAUTHORIZED; + sendLog('info', `🌐 代理已禁用`); + } + }); + // ── Workspace IPC ── // 获取工作空间目录 diff --git a/src/main/preload.ts b/src/main/preload.ts index 85209c2..70bac6f 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -20,7 +20,8 @@ contextBridge.exposeInMainWorld('metonaDesktop', { execute: (toolName: string, args: Record) => ipcRenderer.invoke('tool:execute', toolName, args), getConfig: () => ipcRenderer.invoke('tool:getConfig'), setAllowedDirs: (dirs: string[]) => ipcRenderer.invoke('tool:setAllowedDirs', dirs), - setTimeouts: (timeouts: { http?: number; mcp?: number }) => ipcRenderer.invoke('tool:setTimeouts', timeouts) + setTimeouts: (timeouts: { http?: number; mcp?: number }) => ipcRenderer.invoke('tool:setTimeouts', timeouts), + setProxy: (url: string) => ipcRenderer.invoke('proxy:set', url) }, notify: (title: string, body: string) => ipcRenderer.invoke('notify', title, body), window: { diff --git a/src/renderer/components/settings-modal.ts b/src/renderer/components/settings-modal.ts index 7ea83b1..02d86d3 100644 --- a/src/renderer/components/settings-modal.ts +++ b/src/renderer/components/settings-modal.ts @@ -39,6 +39,18 @@ export function initSettingsModal(): void { }, 500); document.querySelector('#inputServerUrl')!.addEventListener('input', saveServerUrl); + // 代理设置 + const inputProxyUrl = document.querySelector('#inputProxyUrl') as HTMLInputElement; + if (inputProxyUrl) { + const saveProxyUrl = debounce(async () => { + const db = state.get(KEYS.DB); + const url = inputProxyUrl.value.trim(); + if (db) await db.saveSetting('proxyUrl', url); + logSetting('代理', url || '已禁用'); + }, 500); + inputProxyUrl.addEventListener('input', saveProxyUrl); + } + const tempSlider = document.querySelector('#inputTemperature') as HTMLInputElement; const tempDisplay = document.querySelector('#tempValue')!; tempSlider.addEventListener('input', () => { diff --git a/src/renderer/index.html b/src/renderer/index.html index 3fc7e2a..8ea0a6a 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -271,6 +271,11 @@ OLLAMA_ORIGINS="*" ollama serve +
+ + +

留空则不使用代理。支持 HTTP 代理,例如 Clash(http://127.0.0.1:7890)。修改后需重启应用生效。

+
diff --git a/src/renderer/main.ts b/src/renderer/main.ts index 60b7c21..135e5f2 100644 --- a/src/renderer/main.ts +++ b/src/renderer/main.ts @@ -394,6 +394,21 @@ async function init(): Promise { (document.querySelector('#inputMCPTimeout') as HTMLInputElement).value = mcpTimeout >= 0 ? String(mcpTimeout) : ''; logInit(`超时设置: HTTP=${httpTimeout>=0?httpTimeout:30}s 流式=${streamTimeout>=0?streamTimeout:300}s MCP=${mcpTimeout>=0?mcpTimeout:60}s`); + // ── 代理设置 ── + const proxyUrl = await db.getSetting('proxyUrl', ''); + if (proxyUrl) { + try { + await fetch(proxyUrl, { method: 'HEAD', signal: AbortSignal.timeout(2000) }); + logInit(`代理: ${proxyUrl} ✓`); + } catch { logInit(`代理: ${proxyUrl} (无法连接,但已设置)`); } + state.set('proxyUrl', proxyUrl); + // 通知主进程设置环境变量 + if (bridge?.tool?.setProxy) { + await bridge.tool.setProxy(proxyUrl); + } + } + (document.querySelector('#inputProxyUrl') as HTMLInputElement).value = proxyUrl; + // ── Tool Calling 设置(永久开启,不可关闭)── state.set('toolCallingEnabled', true); const runCommandMode = await db.getSetting('runCommandMode', 'confirm') as 'auto' | 'confirm' | 'disabled'; diff --git a/src/renderer/types.d.ts b/src/renderer/types.d.ts index e2aa888..5050258 100644 --- a/src/renderer/types.d.ts +++ b/src/renderer/types.d.ts @@ -221,6 +221,7 @@ export interface MetonaDesktopAPI { getConfig: () => Promise<{ allowedDirs: string[]; blockedDirs: string[] }>; setAllowedDirs: (dirs: string[]) => Promise; setTimeouts: (timeouts: { http?: number; mcp?: number }) => Promise<{ success: boolean }>; + setProxy: (url: string) => Promise; }; notify: (title: string, body: string) => void; window: {