feat: support bypass proxy (#8791)

* feat(ProxyManager): implement SelectiveDispatcher for localhost handling

- Added SelectiveDispatcher to manage proxy and direct connections based on the hostname.
- Introduced isLocalhost function to check for localhost addresses.
- Updated ProxyManager to bypass proxy for localhost in dispatch methods and set proxy bypass rules.
- Enhanced global dispatcher setup to utilize SelectiveDispatcher for both EnvHttpProxyAgent and SOCKS dispatcher.

* refactor(ProxyManager): update axios configuration to use fetch adapter

- Changed axios to use the 'fetch' adapter for proxy requests.
- Removed previous proxy settings for axios, streamlining the configuration.
- Updated HTTP methods to bind with the new proxy agent.

* feat(Proxy): add support for proxy bypass rules

- Updated IPC handler to accept optional bypass rules for proxy configuration.
- Enhanced ProxyManager to store and utilize bypass rules for localhost and other specified addresses.
- Modified settings and UI components to allow users to input and manage bypass rules.
- Added translations for bypass rules in multiple languages.

* feat(ProxyManager): add HTTP_PROXY environment variable support

- Added support for the HTTP_PROXY environment variable in ProxyManager to enhance proxy configuration capabilities.

* lint

* refactor(ProxyManager): optimize bypass rules handling

- Updated bypass rules initialization to split the rules string into an array for improved performance.
- Simplified the isByPass function to directly check against the array of bypass rules.
- Enhanced configuration handling to ensure bypass rules are correctly parsed from the provided settings.

* refactor(ProxyManager): streamline bypass rules initialization

- Consolidated the initialization of bypass rules by directly splitting the default rules string into an array.
- Updated configuration handling to ensure bypass rules are correctly assigned without redundant splitting.

* style(GeneralSettings): adjust proxy bypass rules input width to improve UI layout

* refactor(ProxyManager): enhance proxy configuration logging and handling

- Added proxy bypass rules to the configuration method for improved flexibility.
- Updated logging to include bypass rules for better debugging.
- Refactored the setGlobalProxy method to accept configuration parameters directly, streamlining proxy setup.
- Adjusted the useAppInit hook to handle proxy settings more cleanly.

* refactor(ProxyManager): implement close and destroy methods for proxy dispatcher

- Added close and destroy methods to the SelectiveDispatcher class for better resource management.
- Updated ProxyManager to handle the lifecycle of the proxyDispatcher, ensuring proper closure and destruction.
- Enhanced error handling during dispatcher closure and destruction to prevent resource leaks.

* refactor(ProxyManager): manage proxy agent lifecycle

- Introduced proxyAgent property to ProxyManager for better management of the proxy agent.
- Implemented error handling during the destruction of the proxy agent to prevent potential issues.
- Updated the proxy setup process to ensure the proxy agent is correctly initialized and cleaned up.

* refactor(ProxyManager): centralize default bypass rules management

- Moved default bypass rules to a shared constant for consistency across components.
- Updated ProxyManager and GeneralSettings to utilize the centralized bypass rules.
- Adjusted migration logic to set default bypass rules from the shared constant, ensuring uniformity in configuration.
This commit is contained in:
beyondkmp 2025-08-04 19:24:28 +08:00 committed by GitHub
parent 0e1df2460e
commit efda20c143
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 163 additions and 45 deletions

View File

@ -206,3 +206,5 @@ export enum UpgradeChannel {
export const defaultTimeout = 10 * 1000 * 60 export const defaultTimeout = 10 * 1000 * 60
export const occupiedDirs = ['logs', 'Network', 'Partitions/webview/Network'] export const occupiedDirs = ['logs', 'Network', 'Partitions/webview/Network']
export const defaultByPassRules = 'localhost,127.0.0.1,::1'

View File

@ -90,7 +90,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
installPath: path.dirname(app.getPath('exe')) installPath: path.dirname(app.getPath('exe'))
})) }))
ipcMain.handle(IpcChannel.App_Proxy, async (_, proxy: string) => { ipcMain.handle(IpcChannel.App_Proxy, async (_, proxy: string, bypassRules?: string) => {
let proxyConfig: ProxyConfig let proxyConfig: ProxyConfig
if (proxy === 'system') { if (proxy === 'system') {
@ -101,6 +101,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
proxyConfig = { mode: 'direct' } proxyConfig = { mode: 'direct' }
} }
if (bypassRules) {
proxyConfig.proxyBypassRules = bypassRules
}
await proxyManager.configureProxy(proxyConfig) await proxyManager.configureProxy(proxyConfig)
}) })

View File

@ -7,14 +7,63 @@ import https from 'https'
import { getSystemProxy } from 'os-proxy-config' import { getSystemProxy } from 'os-proxy-config'
import { ProxyAgent } from 'proxy-agent' import { ProxyAgent } from 'proxy-agent'
import { Dispatcher, EnvHttpProxyAgent, getGlobalDispatcher, setGlobalDispatcher } from 'undici' import { Dispatcher, EnvHttpProxyAgent, getGlobalDispatcher, setGlobalDispatcher } from 'undici'
import { defaultByPassRules } from '@shared/config/constant'
const logger = loggerService.withContext('ProxyManager') const logger = loggerService.withContext('ProxyManager')
let byPassRules = defaultByPassRules.split(',')
const isByPass = (hostname: string) => {
return byPassRules.includes(hostname)
}
class SelectiveDispatcher extends Dispatcher {
private proxyDispatcher: Dispatcher
private directDispatcher: Dispatcher
constructor(proxyDispatcher: Dispatcher, directDispatcher: Dispatcher) {
super()
this.proxyDispatcher = proxyDispatcher
this.directDispatcher = directDispatcher
}
dispatch(opts: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandlers) {
if (opts.origin) {
const url = new URL(opts.origin)
// 检查是否为 localhost 或本地地址
if (isByPass(url.hostname)) {
return this.directDispatcher.dispatch(opts, handler)
}
}
return this.proxyDispatcher.dispatch(opts, handler)
}
async close(): Promise<void> {
try {
await this.proxyDispatcher.close()
} catch (error) {
logger.error('Failed to close dispatcher:', error as Error)
this.proxyDispatcher.destroy()
}
}
async destroy(): Promise<void> {
try {
await this.proxyDispatcher.destroy()
} catch (error) {
logger.error('Failed to destroy dispatcher:', error as Error)
}
}
}
export class ProxyManager { export class ProxyManager {
private config: ProxyConfig = { mode: 'direct' } private config: ProxyConfig = { mode: 'direct' }
private systemProxyInterval: NodeJS.Timeout | null = null private systemProxyInterval: NodeJS.Timeout | null = null
private isSettingProxy = false private isSettingProxy = false
private proxyDispatcher: Dispatcher | null = null
private proxyAgent: ProxyAgent | null = null
private originalGlobalDispatcher: Dispatcher private originalGlobalDispatcher: Dispatcher
private originalSocksDispatcher: Dispatcher private originalSocksDispatcher: Dispatcher
// for http and https // for http and https
@ -44,7 +93,8 @@ export class ProxyManager {
await this.configureProxy({ await this.configureProxy({
mode: 'system', mode: 'system',
proxyRules: currentProxy?.proxyUrl.toLowerCase() proxyRules: currentProxy?.proxyUrl.toLowerCase(),
proxyBypassRules: this.config.proxyBypassRules
}) })
}, 1000 * 60) }, 1000 * 60)
} }
@ -57,7 +107,8 @@ export class ProxyManager {
} }
async configureProxy(config: ProxyConfig): Promise<void> { async configureProxy(config: ProxyConfig): Promise<void> {
logger.debug(`configureProxy: ${config?.mode} ${config?.proxyRules}`) logger.info(`configureProxy: ${config?.mode} ${config?.proxyRules} ${config?.proxyBypassRules}`)
if (this.isSettingProxy) { if (this.isSettingProxy) {
return return
} }
@ -65,11 +116,6 @@ export class ProxyManager {
this.isSettingProxy = true this.isSettingProxy = true
try { try {
if (config?.mode === this.config?.mode && config?.proxyRules === this.config?.proxyRules) {
logger.debug('proxy config is the same, skip configure')
return
}
this.config = config this.config = config
this.clearSystemProxyMonitor() this.clearSystemProxyMonitor()
if (config.mode === 'system') { if (config.mode === 'system') {
@ -81,7 +127,8 @@ export class ProxyManager {
this.monitorSystemProxy() this.monitorSystemProxy()
} }
this.setGlobalProxy() byPassRules = config.proxyBypassRules?.split(',') || defaultByPassRules.split(',')
this.setGlobalProxy(this.config)
} catch (error) { } catch (error) {
logger.error('Failed to config proxy:', error as Error) logger.error('Failed to config proxy:', error as Error)
throw error throw error
@ -115,12 +162,12 @@ export class ProxyManager {
} }
} }
private setGlobalProxy() { private setGlobalProxy(config: ProxyConfig) {
this.setEnvironment(this.config.proxyRules || '') this.setEnvironment(config.proxyRules || '')
this.setGlobalFetchProxy(this.config) this.setGlobalFetchProxy(config)
this.setSessionsProxy(this.config) this.setSessionsProxy(config)
this.setGlobalHttpProxy(this.config) this.setGlobalHttpProxy(config)
} }
private setGlobalHttpProxy(config: ProxyConfig) { private setGlobalHttpProxy(config: ProxyConfig) {
@ -129,21 +176,18 @@ export class ProxyManager {
http.request = this.originalHttpRequest http.request = this.originalHttpRequest
https.get = this.originalHttpsGet https.get = this.originalHttpsGet
https.request = this.originalHttpsRequest https.request = this.originalHttpsRequest
try {
axios.defaults.proxy = undefined this.proxyAgent?.destroy()
axios.defaults.httpAgent = undefined } catch (error) {
axios.defaults.httpsAgent = undefined logger.error('Failed to destroy proxy agent:', error as Error)
}
this.proxyAgent = null
return return
} }
// ProxyAgent 从环境变量读取代理配置 // ProxyAgent 从环境变量读取代理配置
const agent = new ProxyAgent() const agent = new ProxyAgent()
this.proxyAgent = agent
// axios 使用代理
axios.defaults.proxy = false
axios.defaults.httpAgent = agent
axios.defaults.httpsAgent = agent
http.get = this.bindHttpMethod(this.originalHttpGet, agent) http.get = this.bindHttpMethod(this.originalHttpGet, agent)
http.request = this.bindHttpMethod(this.originalHttpRequest, agent) http.request = this.bindHttpMethod(this.originalHttpRequest, agent)
@ -176,16 +220,19 @@ export class ProxyManager {
callback = args[1] callback = args[1]
} }
// filter localhost
if (url) {
const hostname = typeof url === 'string' ? new URL(url).hostname : url.hostname
if (isByPass(hostname)) {
return originalMethod(url, options, callback)
}
}
// for webdav https self-signed certificate // for webdav https self-signed certificate
if (options.agent instanceof https.Agent) { if (options.agent instanceof https.Agent) {
;(agent as https.Agent).options.rejectUnauthorized = options.agent.options.rejectUnauthorized ;(agent as https.Agent).options.rejectUnauthorized = options.agent.options.rejectUnauthorized
} }
options.agent = agent
// 确保只设置 agent不修改其他网络选项
if (!options.agent) {
options.agent = agent
}
if (url) { if (url) {
return originalMethod(url, options, callback) return originalMethod(url, options, callback)
} }
@ -198,22 +245,33 @@ export class ProxyManager {
if (config.mode === 'direct' || !proxyUrl) { if (config.mode === 'direct' || !proxyUrl) {
setGlobalDispatcher(this.originalGlobalDispatcher) setGlobalDispatcher(this.originalGlobalDispatcher)
global[Symbol.for('undici.globalDispatcher.1')] = this.originalSocksDispatcher global[Symbol.for('undici.globalDispatcher.1')] = this.originalSocksDispatcher
axios.defaults.adapter = 'http'
this.proxyDispatcher?.close()
this.proxyDispatcher = null
return return
} }
// axios 使用 fetch 代理
axios.defaults.adapter = 'fetch'
const url = new URL(proxyUrl) const url = new URL(proxyUrl)
if (url.protocol === 'http:' || url.protocol === 'https:') { if (url.protocol === 'http:' || url.protocol === 'https:') {
setGlobalDispatcher(new EnvHttpProxyAgent()) this.proxyDispatcher = new SelectiveDispatcher(new EnvHttpProxyAgent(), this.originalGlobalDispatcher)
setGlobalDispatcher(this.proxyDispatcher)
return return
} }
global[Symbol.for('undici.globalDispatcher.1')] = socksDispatcher({ this.proxyDispatcher = new SelectiveDispatcher(
port: parseInt(url.port), socksDispatcher({
type: url.protocol === 'socks4:' ? 4 : 5, port: parseInt(url.port),
host: url.hostname, type: url.protocol === 'socks4:' ? 4 : 5,
userId: url.username || undefined, host: url.hostname,
password: url.password || undefined userId: url.username || undefined,
}) password: url.password || undefined
}),
this.originalSocksDispatcher
)
global[Symbol.for('undici.globalDispatcher.1')] = this.proxyDispatcher
} }
private async setSessionsProxy(config: ProxyConfig): Promise<void> { private async setSessionsProxy(config: ProxyConfig): Promise<void> {

View File

@ -41,7 +41,8 @@ export function tracedInvoke(channel: string, spanContext: SpanContext | undefin
const api = { const api = {
getAppInfo: () => ipcRenderer.invoke(IpcChannel.App_Info), getAppInfo: () => ipcRenderer.invoke(IpcChannel.App_Info),
reload: () => ipcRenderer.invoke(IpcChannel.App_Reload), reload: () => ipcRenderer.invoke(IpcChannel.App_Reload),
setProxy: (proxy: string | undefined) => ipcRenderer.invoke(IpcChannel.App_Proxy, proxy), setProxy: (proxy: string | undefined, bypassRules?: string) =>
ipcRenderer.invoke(IpcChannel.App_Proxy, proxy, bypassRules),
checkForUpdate: () => ipcRenderer.invoke(IpcChannel.App_CheckForUpdate), checkForUpdate: () => ipcRenderer.invoke(IpcChannel.App_CheckForUpdate),
showUpdateDialog: () => ipcRenderer.invoke(IpcChannel.App_ShowUpdateDialog), showUpdateDialog: () => ipcRenderer.invoke(IpcChannel.App_ShowUpdateDialog),
setLanguage: (lang: string) => ipcRenderer.invoke(IpcChannel.App_SetLanguage, lang), setLanguage: (lang: string) => ipcRenderer.invoke(IpcChannel.App_SetLanguage, lang),

View File

@ -27,7 +27,16 @@ const logger = loggerService.withContext('useAppInit')
export function useAppInit() { export function useAppInit() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { proxyUrl, language, windowStyle, autoCheckUpdate, proxyMode, customCss, enableDataCollection } = useSettings() const {
proxyUrl,
proxyBypassRules,
language,
windowStyle,
autoCheckUpdate,
proxyMode,
customCss,
enableDataCollection
} = useSettings()
const { minappShow } = useRuntime() const { minappShow } = useRuntime()
const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel() const { setDefaultModel, setTopicNamingModel, setTranslateModel } = useDefaultModel()
const avatar = useLiveQuery(() => db.settings.get('image://avatar')) const avatar = useLiveQuery(() => db.settings.get('image://avatar'))
@ -77,13 +86,13 @@ export function useAppInit() {
useEffect(() => { useEffect(() => {
if (proxyMode === 'system') { if (proxyMode === 'system') {
window.api.setProxy('system') window.api.setProxy('system', proxyBypassRules)
} else if (proxyMode === 'custom') { } else if (proxyMode === 'custom') {
proxyUrl && window.api.setProxy(proxyUrl) proxyUrl && window.api.setProxy(proxyUrl, proxyBypassRules)
} else { } else {
window.api.setProxy('') window.api.setProxy('')
} }
}, [proxyUrl, proxyMode]) }, [proxyUrl, proxyMode, proxyBypassRules])
useEffect(() => { useEffect(() => {
i18n.changeLanguage(language || navigator.language || defaultLanguage) i18n.changeLanguage(language || navigator.language || defaultLanguage)

View File

@ -3238,6 +3238,7 @@
}, },
"proxy": { "proxy": {
"address": "Proxy Address", "address": "Proxy Address",
"bypass": "Bypass Rules",
"mode": { "mode": {
"custom": "Custom Proxy", "custom": "Custom Proxy",
"none": "No Proxy", "none": "No Proxy",

View File

@ -3238,6 +3238,7 @@
}, },
"proxy": { "proxy": {
"address": "プロキシアドレス", "address": "プロキシアドレス",
"bypass": "バイパスルール",
"mode": { "mode": {
"custom": "カスタムプロキシ", "custom": "カスタムプロキシ",
"none": "プロキシを使用しない", "none": "プロキシを使用しない",

View File

@ -3238,6 +3238,7 @@
}, },
"proxy": { "proxy": {
"address": "Адрес прокси", "address": "Адрес прокси",
"bypass": "Правила обхода",
"mode": { "mode": {
"custom": "Пользовательский прокси", "custom": "Пользовательский прокси",
"none": "Не использовать прокси", "none": "Не использовать прокси",

View File

@ -3238,6 +3238,7 @@
}, },
"proxy": { "proxy": {
"address": "代理地址", "address": "代理地址",
"bypass": "代理绕过规则",
"mode": { "mode": {
"custom": "自定义代理", "custom": "自定义代理",
"none": "不使用代理", "none": "不使用代理",

View File

@ -3238,6 +3238,7 @@
}, },
"proxy": { "proxy": {
"address": "代理伺服器位址", "address": "代理伺服器位址",
"bypass": "代理略過規則",
"mode": { "mode": {
"custom": "自訂代理伺服器", "custom": "自訂代理伺服器",
"none": "不使用代理伺服器", "none": "不使用代理伺服器",

View File

@ -3236,6 +3236,7 @@
}, },
"proxy": { "proxy": {
"address": "Διεύθυνση διαμεσολάβησης", "address": "Διεύθυνση διαμεσολάβησης",
"bypass": "Κανόνες Παράκαμψης",
"mode": { "mode": {
"custom": "προσαρμοσμένη προξενική", "custom": "προσαρμοσμένη προξενική",
"none": "χωρίς πρόξενο", "none": "χωρίς πρόξενο",

View File

@ -3236,6 +3236,7 @@
}, },
"proxy": { "proxy": {
"address": "Dirección del proxy", "address": "Dirección del proxy",
"bypass": "Reglas de omisión",
"mode": { "mode": {
"custom": "Proxy personalizado", "custom": "Proxy personalizado",
"none": "No usar proxy", "none": "No usar proxy",

View File

@ -3236,6 +3236,7 @@
}, },
"proxy": { "proxy": {
"address": "Adresse du proxy", "address": "Adresse du proxy",
"bypass": "Règles de contournement",
"mode": { "mode": {
"custom": "Proxy personnalisé", "custom": "Proxy personnalisé",
"none": "Ne pas utiliser de proxy", "none": "Ne pas utiliser de proxy",

View File

@ -3236,6 +3236,7 @@
}, },
"proxy": { "proxy": {
"address": "Endereço do proxy", "address": "Endereço do proxy",
"bypass": "Regras de Contorno",
"mode": { "mode": {
"custom": "Proxy Personalizado", "custom": "Proxy Personalizado",
"none": "Não Usar Proxy", "none": "Não Usar Proxy",

View File

@ -10,6 +10,7 @@ import {
setEnableSpellCheck, setEnableSpellCheck,
setLanguage, setLanguage,
setNotificationSettings, setNotificationSettings,
setProxyBypassRules as _setProxyBypassRules,
setProxyMode, setProxyMode,
setProxyUrl as _setProxyUrl, setProxyUrl as _setProxyUrl,
setSpellCheckLanguages setSpellCheckLanguages
@ -17,7 +18,7 @@ import {
import { LanguageVarious } from '@renderer/types' import { LanguageVarious } from '@renderer/types'
import { NotificationSource } from '@renderer/types/notification' import { NotificationSource } from '@renderer/types/notification'
import { isValidProxyUrl } from '@renderer/utils' import { isValidProxyUrl } from '@renderer/utils'
import { defaultLanguage } from '@shared/config/constant' import { defaultByPassRules, defaultLanguage } from '@shared/config/constant'
import { Flex, Input, Switch, Tooltip } from 'antd' import { Flex, Input, Switch, Tooltip } from 'antd'
import { FC, useState } from 'react' import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -29,6 +30,7 @@ const GeneralSettings: FC = () => {
const { const {
language, language,
proxyUrl: storeProxyUrl, proxyUrl: storeProxyUrl,
proxyBypassRules: storeProxyBypassRules,
setLaunch, setLaunch,
setTray, setTray,
launchOnBoot, launchOnBoot,
@ -42,6 +44,7 @@ const GeneralSettings: FC = () => {
setDisableHardwareAcceleration setDisableHardwareAcceleration
} = useSettings() } = useSettings()
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl) const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
const [proxyBypassRules, setProxyBypassRules] = useState<string | undefined>(storeProxyBypassRules)
const { theme } = useTheme() const { theme } = useTheme()
const { enableDeveloperMode, setEnableDeveloperMode } = useEnableDeveloperMode() const { enableDeveloperMode, setEnableDeveloperMode } = useEnableDeveloperMode()
@ -97,6 +100,10 @@ const GeneralSettings: FC = () => {
dispatch(_setProxyUrl(proxyUrl)) dispatch(_setProxyUrl(proxyUrl))
} }
const onSetProxyBypassRules = () => {
dispatch(_setProxyBypassRules(proxyBypassRules))
}
const proxyModeOptions: { value: 'system' | 'custom' | 'none'; label: string }[] = [ const proxyModeOptions: { value: 'system' | 'custom' | 'none'; label: string }[] = [
{ value: 'system', label: t('settings.proxy.mode.system') }, { value: 'system', label: t('settings.proxy.mode.system') },
{ value: 'custom', label: t('settings.proxy.mode.custom') }, { value: 'custom', label: t('settings.proxy.mode.custom') },
@ -109,6 +116,7 @@ const GeneralSettings: FC = () => {
dispatch(_setProxyUrl(undefined)) dispatch(_setProxyUrl(undefined))
} else if (mode === 'none') { } else if (mode === 'none') {
dispatch(_setProxyUrl(undefined)) dispatch(_setProxyUrl(undefined))
dispatch(_setProxyBypassRules(undefined))
} }
} }
@ -210,6 +218,7 @@ const GeneralSettings: FC = () => {
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.proxy.address')}</SettingRowTitle> <SettingRowTitle>{t('settings.proxy.address')}</SettingRowTitle>
<Input <Input
spellCheck={false}
placeholder="socks5://127.0.0.1:6153" placeholder="socks5://127.0.0.1:6153"
value={proxyUrl} value={proxyUrl}
onChange={(e) => setProxyUrl(e.target.value)} onChange={(e) => setProxyUrl(e.target.value)}
@ -220,6 +229,22 @@ const GeneralSettings: FC = () => {
</SettingRow> </SettingRow>
</> </>
)} )}
{(storeProxyMode === 'custom' || storeProxyMode === 'system') && (
<>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.proxy.bypass')}</SettingRowTitle>
<Input
spellCheck={false}
placeholder={defaultByPassRules}
value={proxyBypassRules}
onChange={(e) => setProxyBypassRules(e.target.value)}
style={{ width: 180 }}
onBlur={() => onSetProxyBypassRules()}
/>
</SettingRow>
</>
)}
<SettingDivider /> <SettingDivider />
<SettingRow> <SettingRow>
<HStack justifyContent="space-between" alignItems="center" style={{ flex: 1, marginRight: 16 }}> <HStack justifyContent="space-between" alignItems="center" style={{ flex: 1, marginRight: 16 }}>

View File

@ -14,7 +14,7 @@ import db from '@renderer/databases'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import { Assistant, LanguageCode, Model, Provider, WebSearchProvider } from '@renderer/types' import { Assistant, LanguageCode, Model, Provider, WebSearchProvider } from '@renderer/types'
import { getDefaultGroupName, getLeadingEmoji, runAsyncFunction, uuid } from '@renderer/utils' import { getDefaultGroupName, getLeadingEmoji, runAsyncFunction, uuid } from '@renderer/utils'
import { UpgradeChannel } from '@shared/config/constant' import { defaultByPassRules, UpgradeChannel } from '@shared/config/constant'
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
import { createMigrate } from 'redux-persist' import { createMigrate } from 'redux-persist'
@ -1969,6 +1969,10 @@ const migrateConfig = {
try { try {
addProvider(state, 'poe') addProvider(state, 'poe')
if (!state.settings.proxyBypassRules) {
state.settings.proxyBypassRules = defaultByPassRules
}
// 迁移api选项设置 // 迁移api选项设置
state.llm.providers.forEach((provider) => { state.llm.providers.forEach((provider) => {
// 新字段默认支持 // 新字段默认支持

View File

@ -49,6 +49,7 @@ export interface SettingsState {
targetLanguage: TranslateLanguageVarious targetLanguage: TranslateLanguageVarious
proxyMode: 'system' | 'custom' | 'none' proxyMode: 'system' | 'custom' | 'none'
proxyUrl?: string proxyUrl?: string
proxyBypassRules?: string
userName: string userName: string
userId: string userId: string
showPrompt: boolean showPrompt: boolean
@ -220,6 +221,7 @@ export const initialState: SettingsState = {
targetLanguage: 'en-us', targetLanguage: 'en-us',
proxyMode: 'system', proxyMode: 'system',
proxyUrl: undefined, proxyUrl: undefined,
proxyBypassRules: undefined,
userName: '', userName: '',
userId: uuid(), userId: uuid(),
showPrompt: true, showPrompt: true,
@ -423,6 +425,9 @@ const settingsSlice = createSlice({
setProxyUrl: (state, action: PayloadAction<string | undefined>) => { setProxyUrl: (state, action: PayloadAction<string | undefined>) => {
state.proxyUrl = action.payload state.proxyUrl = action.payload
}, },
setProxyBypassRules: (state, action: PayloadAction<string | undefined>) => {
state.proxyBypassRules = action.payload
},
setUserName: (state, action: PayloadAction<string>) => { setUserName: (state, action: PayloadAction<string>) => {
state.userName = action.payload state.userName = action.payload
}, },
@ -826,6 +831,7 @@ export const {
setTargetLanguage, setTargetLanguage,
setProxyMode, setProxyMode,
setProxyUrl, setProxyUrl,
setProxyBypassRules,
setUserName, setUserName,
setShowPrompt, setShowPrompt,
setShowTokens, setShowTokens,