mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 14:59:27 +08:00
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:
parent
0e1df2460e
commit
efda20c143
@ -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'
|
||||||
|
|||||||
@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -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> {
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -3238,6 +3238,7 @@
|
|||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"address": "プロキシアドレス",
|
"address": "プロキシアドレス",
|
||||||
|
"bypass": "バイパスルール",
|
||||||
"mode": {
|
"mode": {
|
||||||
"custom": "カスタムプロキシ",
|
"custom": "カスタムプロキシ",
|
||||||
"none": "プロキシを使用しない",
|
"none": "プロキシを使用しない",
|
||||||
|
|||||||
@ -3238,6 +3238,7 @@
|
|||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"address": "Адрес прокси",
|
"address": "Адрес прокси",
|
||||||
|
"bypass": "Правила обхода",
|
||||||
"mode": {
|
"mode": {
|
||||||
"custom": "Пользовательский прокси",
|
"custom": "Пользовательский прокси",
|
||||||
"none": "Не использовать прокси",
|
"none": "Не использовать прокси",
|
||||||
|
|||||||
@ -3238,6 +3238,7 @@
|
|||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"address": "代理地址",
|
"address": "代理地址",
|
||||||
|
"bypass": "代理绕过规则",
|
||||||
"mode": {
|
"mode": {
|
||||||
"custom": "自定义代理",
|
"custom": "自定义代理",
|
||||||
"none": "不使用代理",
|
"none": "不使用代理",
|
||||||
|
|||||||
@ -3238,6 +3238,7 @@
|
|||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"address": "代理伺服器位址",
|
"address": "代理伺服器位址",
|
||||||
|
"bypass": "代理略過規則",
|
||||||
"mode": {
|
"mode": {
|
||||||
"custom": "自訂代理伺服器",
|
"custom": "自訂代理伺服器",
|
||||||
"none": "不使用代理伺服器",
|
"none": "不使用代理伺服器",
|
||||||
|
|||||||
@ -3236,6 +3236,7 @@
|
|||||||
},
|
},
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"address": "Διεύθυνση διαμεσολάβησης",
|
"address": "Διεύθυνση διαμεσολάβησης",
|
||||||
|
"bypass": "Κανόνες Παράκαμψης",
|
||||||
"mode": {
|
"mode": {
|
||||||
"custom": "προσαρμοσμένη προξενική",
|
"custom": "προσαρμοσμένη προξενική",
|
||||||
"none": "χωρίς πρόξενο",
|
"none": "χωρίς πρόξενο",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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 }}>
|
||||||
|
|||||||
@ -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) => {
|
||||||
// 新字段默认支持
|
// 新字段默认支持
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user