From 3b8b8fc3700804139b6bbe4ba7985820b8841cb5 Mon Sep 17 00:00:00 2001 From: Jiale Lin <63439129+ljluestc@users.noreply.github.com> Date: Sat, 23 May 2026 19:24:51 -0700 Subject: [PATCH 1/2] fix(desktop): add proxy mode support for HTTP and SOCKS5 --- app/store/access.ts | 7 +++++++ app/utils/stream.ts | 12 ++++++++++++ src-tauri/Cargo.toml | 2 +- src-tauri/src/stream.rs | 18 +++++++++++++++--- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/app/store/access.ts b/app/store/access.ts index fd55fbdd3d1..33af4c44815 100644 --- a/app/store/access.ts +++ b/app/store/access.ts @@ -27,6 +27,8 @@ import { DEFAULT_CONFIG } from "./config"; import { getModelProvider } from "../utils/model"; let fetchState = 0; // 0 not fetch, 1 fetching, 2 done +export const PROXY_MODES = ["system", "http", "socks5"] as const; +export type ProxyMode = (typeof PROXY_MODES)[number]; const isApp = getClientConfig()?.buildMode === "export"; @@ -139,6 +141,11 @@ const DEFAULT_ACCESS_STATE = { ai302Url: DEFAULT_AI302_URL, ai302ApiKey: "", + // desktop proxy mode + proxyMode: "system" as ProxyMode, + proxyHost: "127.0.0.1", + proxyPort: "7890", + // server config needCode: true, hideUserApiKey: false, diff --git a/app/utils/stream.ts b/app/utils/stream.ts index f186730f6da..229aa6dc5e2 100644 --- a/app/utils/stream.ts +++ b/app/utils/stream.ts @@ -2,6 +2,17 @@ // see src-tauri/src/stream.rs, and src-tauri/src/main.rs // 1. invoke('stream_fetch', {url, method, headers, body}), get response with headers. // 2. listen event: `stream-response` multi times to get body +import { useAccessStore } from "../store/access"; + +function getDesktopProxyUrl(): string | undefined { + const access = useAccessStore.getState(); + if (access.proxyMode === "system") return undefined; + const host = access.proxyHost?.trim().replace(/^(https?|socks5):\/\//i, ""); + const port = access.proxyPort?.trim() ?? ""; + if (!host) return undefined; + const hp = host.includes(":") ? host : port ? `${host}:${port}` : host; + return `${access.proxyMode}://${hp}`; +} type ResponseEvent = { id: number; @@ -79,6 +90,7 @@ export function fetch(url: string, options?: RequestInit): Promise { method: method.toUpperCase(), url, headers, + proxy_url: getDesktopProxyUrl(), // TODO FormData body: typeof body === "string" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 8a11c3b6f98..9cabbc4b702 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -38,7 +38,7 @@ tauri = { version = "1.5.4", features = [ "http-all", ] } tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } percent-encoding = "2.3.1" -reqwest = "0.11.18" +reqwest = { version = "0.11.18", features = ["socks"] } futures-util = "0.3.30" bytes = "1.7.2" diff --git a/src-tauri/src/stream.rs b/src-tauri/src/stream.rs index 8320db3e48a..45ea39369b0 100644 --- a/src-tauri/src/stream.rs +++ b/src-tauri/src/stream.rs @@ -6,7 +6,7 @@ use std::error::Error; use std::sync::atomic::{AtomicU32, Ordering}; use std::collections::HashMap; use futures_util::{StreamExt}; -use reqwest::Client; +use reqwest::{Client, Proxy}; use reqwest::header::{HeaderName, HeaderMap}; static REQUEST_COUNTER: AtomicU32 = AtomicU32::new(0); @@ -38,6 +38,7 @@ pub async fn stream_fetch( url: String, headers: HashMap, body: Vec, + proxy_url: Option, ) -> Result { let event_name = "stream-response"; @@ -54,10 +55,21 @@ pub async fn stream_fetch( // println!("headers: {:?}", _headers); let method = method.parse::().map_err(|err| format!("failed to parse method: {}", err))?; - let client = Client::builder() + let mut builder = Client::builder() .default_headers(_headers) .redirect(reqwest::redirect::Policy::limited(3)) - .connect_timeout(Duration::new(3, 0)) + .connect_timeout(Duration::new(3, 0)); + + if let Some(ref pu) = proxy_url { + let pu = pu.trim().to_string(); + if !pu.is_empty() { + let proxy = Proxy::all(&pu) + .map_err(|err| format!("failed to parse proxy url: {}", err))?; + builder = builder.no_proxy().proxy(proxy); + } + } + + let client = builder .build() .map_err(|err| format!("failed to generate client: {}", err))?; From e987660535851f4129696b4fdb792f5bb393dc43 Mon Sep 17 00:00:00 2001 From: Jiale Lin <63439129+ljluestc@users.noreply.github.com> Date: Sat, 23 May 2026 19:28:39 -0700 Subject: [PATCH 2/2] feat(desktop): expose proxy mode controls in settings --- app/components/settings.tsx | 132 ++++++++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 35 deletions(-) diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 881c12caeb3..628bc673f64 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -86,6 +86,7 @@ import { getClientConfig } from "../config/client"; import { useSyncStore } from "../store/sync"; import { nanoid } from "nanoid"; import { useMaskStore } from "../store/mask"; +import { PROXY_MODES, ProxyMode } from "../store/access"; import { ProviderType } from "../utils/cloud"; import { TTSConfigList } from "./tts-config"; import { RealtimeConfigList } from "./realtime-chat/realtime-config"; @@ -737,6 +738,66 @@ export function Settings() { > ); + const desktopProxyConfigComponent = clientConfig?.isApp && ( + <> + + + + + {accessStore.proxyMode !== "system" && ( + <> + + + accessStore.update( + (access) => (access.proxyHost = e.currentTarget.value), + ) + } + > + + + + + accessStore.update( + (access) => (access.proxyPort = e.currentTarget.value), + ) + } + > + + + )} + + ); const openAIConfigComponent = accessStore.provider === ServiceProvider.OpenAI && ( @@ -1459,44 +1520,44 @@ export function Settings() { ); - const ai302ConfigComponent = accessStore.provider === ServiceProvider["302.AI"] && ( + const ai302ConfigComponent = accessStore.provider === + ServiceProvider["302.AI"] && ( <> + + accessStore.update( + (access) => (access.ai302Url = e.currentTarget.value), + ) } - > - - accessStore.update( - (access) => (access.ai302Url = e.currentTarget.value), - ) - } - > - - - { - accessStore.update( - (access) => (access.ai302ApiKey = e.currentTarget.value), - ); - }} - /> - - + > + + + { + accessStore.update( + (access) => (access.ai302ApiKey = e.currentTarget.value), + ); + }} + /> + + ); return ( @@ -1868,6 +1929,7 @@ export function Settings() { )} )} + {desktopProxyConfigComponent} {!shouldHideBalanceQuery && !clientConfig?.isApp ? (