diff --git a/build/pkg-scripts/postinstall b/build/pkg-scripts/postinstall index c9b3bf5..a557e09 100644 --- a/build/pkg-scripts/postinstall +++ b/build/pkg-scripts/postinstall @@ -51,6 +51,14 @@ else log "Warning: mihomo-alpha binary not found at $APP_PATH/Contents/Resources/sidecar/mihomo-alpha" fi +if [ -f "$APP_PATH/Contents/Resources/sidecar/mihomo-smart" ]; then + chown root:admin "$APP_PATH/Contents/Resources/sidecar/mihomo-smart" + chmod +s "$APP_PATH/Contents/Resources/sidecar/mihomo-smart" + log "Set permissions for mihomo-smart" +else + log "Warning: mihomo-smart binary not found at $APP_PATH/Contents/Resources/sidecar/mihomo-smart" +fi + # 复制 helper 工具 log "Installing helper tool..." if [ -f "$APP_PATH/Contents/Resources/files/party.mihomo.helper" ]; then diff --git a/scripts/prepare.mjs b/scripts/prepare.mjs index e159b8d..caafa9d 100644 --- a/scripts/prepare.mjs +++ b/scripts/prepare.mjs @@ -45,6 +45,31 @@ async function getLatestAlphaVersion() { } } +/* ======= mihomo smart ======= */ +const MIHOMO_SMART_URL_PREFIX = `https://github.com/vernesong/mihomo/releases/download/Prerelease-Alpha` +let MIHOMO_SMART_VERSION + +const MIHOMO_SMART_MAP = { + 'win32-x64': 'mihomo-windows-amd64-v1-go120-alpha-smart', + 'win32-ia32': 'mihomo-windows-386-go120-alpha-smart', + 'win32-arm64': 'mihomo-windows-arm64-alpha-smart', + 'darwin-x64': 'mihomo-darwin-amd64-v1-go120-alpha-smart', + 'darwin-arm64': 'mihomo-darwin-arm64-alpha-smart', + 'linux-x64': 'mihomo-linux-amd64-v1-go120-alpha-smart', + 'linux-arm64': 'mihomo-linux-arm64-alpha-smart' +} + +async function getLatestSmartVersion() { + try { + // Smart 内核版本 + MIHOMO_SMART_VERSION = 'ca46a10' + console.log(`Using smart version: ${MIHOMO_SMART_VERSION}`) + } catch (error) { + console.error('Error with smart version:', error.message) + throw error + } +} + /* ======= mihomo release ======= */ const MIHOMO_VERSION_URL = 'https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt' @@ -87,6 +112,10 @@ if (!MIHOMO_ALPHA_MAP[`${platform}-${arch}`]) { throw new Error(`unsupported platform "${platform}-${arch}"`) } +if (!MIHOMO_SMART_MAP[`${platform}-${arch}`]) { + throw new Error(`unsupported platform "${platform}-${arch}"`) +} + /** * core info */ @@ -123,13 +152,33 @@ function mihomo() { downloadURL } } + +function mihomoSmart() { + const name = MIHOMO_SMART_MAP[`${platform}-${arch}`] + const isWin = platform === 'win32' + const urlExt = isWin ? 'zip' : 'gz' + const downloadURL = `${MIHOMO_SMART_URL_PREFIX}/${name}-${MIHOMO_SMART_VERSION}.${urlExt}` + const exeFile = `${name}${isWin ? '.exe' : ''}` + const zipFile = `${name}-${MIHOMO_SMART_VERSION}.${urlExt}` + + return { + name: 'mihomo-smart', + targetFile: `mihomo-smart${isWin ? '.exe' : ''}`, + exeFile, + zipFile, + downloadURL, + useProjectRoot: true // 标记需要使用项目根目录 + } +} /** * download sidecar and rename */ async function resolveSidecar(binInfo) { - const { name, targetFile, zipFile, exeFile, downloadURL } = binInfo + const { name, targetFile, zipFile, exeFile, downloadURL, useProjectRoot } = binInfo - const sidecarDir = path.join(cwd, 'extra', 'sidecar') + // 对于 Smart 内核,使用项目根目录而不是 scripts 目录 + const baseDir = useProjectRoot ? path.dirname(cwd) : cwd + const sidecarDir = path.join(baseDir, 'extra', 'sidecar') const sidecarPath = path.join(sidecarDir, targetFile) fs.mkdirSync(sidecarDir, { recursive: true }) @@ -360,6 +409,11 @@ const tasks = [ func: () => getLatestReleaseVersion().then(() => resolveSidecar(mihomo())), retry: 5 }, + { + name: 'mihomo-smart', + func: () => getLatestSmartVersion().then(() => resolveSidecar(mihomoSmart())), + retry: 5 + }, { name: 'mmdb', func: resolveMmdb, retry: 5 }, { name: 'metadb', func: resolveMetadb, retry: 5 }, { name: 'geosite', func: resolveGeosite, retry: 5 }, @@ -432,7 +486,14 @@ async function runTask() { break } catch (err) { console.error(`[ERROR]: task::${task.name} try ${i} ==`, err.message) - if (i === task.retry - 1) throw err + if (i === task.retry - 1) { + if (task.optional) { + console.log(`[WARN]: Optional task::${task.name} failed, skipping...`) + break + } else { + throw err + } + } } } return runTask() diff --git a/src/main/config/index.ts b/src/main/config/index.ts index c51114f..ef76486 100644 --- a/src/main/config/index.ts +++ b/src/main/config/index.ts @@ -27,3 +27,9 @@ export { setOverride, updateOverrideItem } from './override' +export { + createSmartOverride, + removeSmartOverride, + manageSmartOverride, + isSmartOverrideExists +} from './smartOverride' diff --git a/src/main/config/smartOverride.ts b/src/main/config/smartOverride.ts new file mode 100644 index 0000000..458347b --- /dev/null +++ b/src/main/config/smartOverride.ts @@ -0,0 +1,275 @@ +import { getAppConfig } from './app' +import { addOverrideItem, removeOverrideItem, getOverrideItem } from './override' + +const SMART_OVERRIDE_ID = 'smart-core-override' + +/** + * Smart 内核的覆写配置模板 + */ +function generateSmartOverrideTemplate(useLightGBM: boolean, collectData: boolean, strategy: string): string { + return ` +// 配置会在启用 Smart 内核时自动应用 + +function main(config) { + try { + // 确保配置对象存在 + if (!config || typeof config !== 'object') { + console.log('[Smart Override] Invalid config object') + return config + } + + // 确保代理组配置存在 + if (!config['proxy-groups']) { + config['proxy-groups'] = [] + } + + // 确保代理组是数组 + if (!Array.isArray(config['proxy-groups'])) { + console.log('[Smart Override] proxy-groups is not an array, converting...') + config['proxy-groups'] = [] + } + + // 查找现有的 Smart 代理组并更新 + let smartGroupExists = false + for (let i = 0; i < config['proxy-groups'].length; i++) { + const group = config['proxy-groups'][i] + if (group && group.type === 'smart') { + smartGroupExists = true + console.log('[Smart Override] Found existing smart group:', group.name) + + if (!group['policy-priority']) { + group['policy-priority'] = 'Premium:0.9;SG:1.3' + } + group.uselightgbm = ${useLightGBM} + group.collectdata = ${collectData} + group.strategy = '${strategy}' + break + } + } + + // 如果没有 Smart 组且有可用代理,创建示例组 + if (!smartGroupExists && config.proxies && Array.isArray(config.proxies) && config.proxies.length > 0) { + console.log('[Smart Override] Creating new smart group with', config.proxies.length, 'proxies') + + // 获取所有代理的名称 + const proxyNames = config.proxies + .filter(proxy => proxy && typeof proxy === 'object' && proxy.name) + .map(proxy => proxy.name) + + if (proxyNames.length > 0) { + const smartGroup = { + name: 'Smart Group', + type: 'smart', + 'policy-priority': 'Premium:0.9;SG:1.3', + uselightgbm: ${useLightGBM}, + collectdata: ${collectData}, + strategy: '${strategy}', + proxies: proxyNames + } + config['proxy-groups'].unshift(smartGroup) + console.log('[Smart Override] Created smart group at first position with proxies:', proxyNames) + } else { + console.log('[Smart Override] No valid proxies found, skipping smart group creation') + } + } else if (!smartGroupExists) { + console.log('[Smart Override] No proxies available, skipping smart group creation') + } + + // 处理规则替换 + if (config.rules && Array.isArray(config.rules)) { + console.log('[Smart Override] Processing rules, original count:', config.rules.length) + + // 收集所有代理组名称 + const proxyGroupNames = new Set() + if (config['proxy-groups'] && Array.isArray(config['proxy-groups'])) { + config['proxy-groups'].forEach(group => { + if (group && group.name) { + proxyGroupNames.add(group.name) + } + }) + } + + // 添加常见的内置目标 + const builtinTargets = new Set([ + 'DIRECT', + 'REJECT', + 'REJECT-DROP', + 'PASS', + 'COMPATIBLE' + ]) + + // 添加常见的规则参数,不应该替换 + const ruleParams = new Set(['no-resolve', 'force-remote-dns', 'prefer-ipv6']) + + console.log('[Smart Override] Found', proxyGroupNames.size, 'proxy groups:', Array.from(proxyGroupNames)) + + let replacedCount = 0 + config.rules = config.rules.map(rule => { + if (typeof rule === 'string') { + // 检查是否是复杂规则格式(包含括号的嵌套规则) + if (rule.includes('((') || rule.includes('))')) { + console.log('[Smart Override] Skipping complex nested rule:', rule) + return rule + } + + // 处理字符串格式的规则 + const parts = rule.split(',').map(part => part.trim()) + if (parts.length >= 3) { + let targetIndex = -1 + let targetValue = '' + + for (let i = 2; i < parts.length; i++) { + const part = parts[i] + if (!ruleParams.has(part)) { + targetIndex = i + targetValue = part + break + } + } + + if (targetIndex !== -1 && targetValue) { + // 检查是否应该替换 + const shouldReplace = !builtinTargets.has(targetValue) && + (proxyGroupNames.has(targetValue) || + !ruleParams.has(targetValue)) + + if (shouldReplace) { + parts[targetIndex] = 'Smart Group' + replacedCount++ + console.log('[Smart Override] Replaced rule target:', targetValue, '→ Smart Group') + return parts.join(',') + } + } + } + } else if (typeof rule === 'object' && rule !== null) { + // 处理对象格式 + let targetField = '' + let targetValue = '' + + if (rule.target) { + targetField = 'target' + targetValue = rule.target + } else if (rule.proxy) { + targetField = 'proxy' + targetValue = rule.proxy + } + + if (targetField && targetValue) { + const shouldReplace = !builtinTargets.has(targetValue) && + (proxyGroupNames.has(targetValue) || + !ruleParams.has(targetValue)) + + if (shouldReplace) { + rule[targetField] = 'Smart Group' + replacedCount++ + console.log('[Smart Override] Replaced rule target:', targetValue, '→ Smart Group') + } + } + } + return rule + }) + + console.log('[Smart Override] Rules processed, replaced', replacedCount, 'non-DIRECT rules with Smart Group') + } else { + console.log('[Smart Override] No rules found or rules is not an array') + } + + console.log('[Smart Override] Configuration processed successfully') + return config + } catch (error) { + console.error('[Smart Override] Error processing config:', error) + // 发生错误时返回原始配置,避免破坏整个配置 + return config + } +} +` +} + +/** + * 创建或更新 Smart 内核覆写配置 + */ +export async function createSmartOverride(): Promise { + try { + // 获取应用配置 + const { + smartCoreUseLightGBM = false, + smartCoreCollectData = false, + smartCoreStrategy = 'sticky-sessions' + } = await getAppConfig() + + // 生成覆写模板 + const template = generateSmartOverrideTemplate( + smartCoreUseLightGBM, + smartCoreCollectData, + smartCoreStrategy + ) + + // 检查是否已存在 Smart 覆写配置 + const existingOverride = await getOverrideItem(SMART_OVERRIDE_ID) + + if (existingOverride) { + // 如果已存在,更新配置 + await addOverrideItem({ + id: SMART_OVERRIDE_ID, + name: 'Smart Core Override', + type: 'local', + ext: 'js', + global: true, + file: template + }) + } else { + // 如果不存在,创建新的覆写配置 + await addOverrideItem({ + id: SMART_OVERRIDE_ID, + name: 'Smart Core Override', + type: 'local', + ext: 'js', + global: true, + file: template + }) + } + } catch (error) { + console.error('Failed to create Smart override:', error) + throw error + } +} + +/** + * 删除 Smart 内核覆写配置 + */ +export async function removeSmartOverride(): Promise { + try { + const existingOverride = await getOverrideItem(SMART_OVERRIDE_ID) + if (existingOverride) { + await removeOverrideItem(SMART_OVERRIDE_ID) + } + } catch (error) { + console.error('Failed to remove Smart override:', error) + throw error + } +} + +/** + * 根据应用配置管理 Smart 覆写 + */ +export async function manageSmartOverride(): Promise { + const { enableSmartCore = true, enableSmartOverride = true, core } = await getAppConfig() + + if (enableSmartCore && enableSmartOverride && core === 'mihomo-smart') { + await createSmartOverride() + } else { + await removeSmartOverride() + } +} + +/** + * 检查 Smart 覆写是否存在 + */ +export async function isSmartOverrideExists(): Promise { + try { + const override = await getOverrideItem(SMART_OVERRIDE_ID) + return !!override + } catch { + return false + } +} diff --git a/src/main/core/manager.ts b/src/main/core/manager.ts index e6df217..158d7bd 100644 --- a/src/main/core/manager.ts +++ b/src/main/core/manager.ts @@ -15,7 +15,8 @@ import { getControledMihomoConfig, getProfileConfig, patchAppConfig, - patchControledMihomoConfig + patchControledMihomoConfig, + manageSmartOverride } from '../config' import { app, dialog, ipcMain, net } from 'electron' import { @@ -83,6 +84,10 @@ export async function startCore(detached = false): Promise[]> { const { current } = await getProfileConfig() const { tun } = await getControledMihomoConfig() const corePath = mihomoCorePath(core) + + // 管理 Smart 内核覆写配置 + await manageSmartOverride() + await generateProfile() await checkProfile() await stopCore() @@ -249,15 +254,32 @@ async function checkProfile(): Promise { mihomoTestDir() ], { env }) } catch (error) { + console.error('Profile check failed:', error) + if (error instanceof Error && 'stdout' in error) { - const { stdout } = error as { stdout: string } + const { stdout, stderr } = error as { stdout: string; stderr?: string } + console.log('Profile check stdout:', stdout) + console.log('Profile check stderr:', stderr) + const errorLines = stdout .split('\n') - .filter((line) => line.includes('level=error')) - .map((line) => line.split('level=error')[1]) - throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}:\n${errorLines.join('\n')}`) + .filter((line) => line.includes('level=error') || line.includes('error')) + .map((line) => { + if (line.includes('level=error')) { + return line.split('level=error')[1]?.trim() || line + } + return line.trim() + }) + .filter(line => line.length > 0) + + if (errorLines.length === 0) { + const allLines = stdout.split('\n').filter(line => line.trim().length > 0) + throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}:\n${allLines.join('\n')}`) + } else { + throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}:\n${errorLines.join('\n')}`) + } } else { - throw error + throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}: ${error}`) } } } diff --git a/src/main/core/mihomoApi.ts b/src/main/core/mihomoApi.ts index 81dba3b..9227da5 100644 --- a/src/main/core/mihomoApi.ts +++ b/src/main/core/mihomoApi.ts @@ -168,6 +168,21 @@ export const mihomoUpgrade = async (): Promise => { return await instance.post('/upgrade') } +// Smart 内核 API +export const mihomoSmartGroupWeights = async (groupName: string): Promise> => { + const instance = await getAxios() + return await instance.get(`/group/${encodeURIComponent(groupName)}/weights`) +} + +export const mihomoSmartFlushCache = async (configName?: string): Promise => { + const instance = await getAxios() + if (configName) { + return await instance.post(`/cache/smart/flush/${encodeURIComponent(configName)}`) + } else { + return await instance.post('/cache/smart/flush') + } +} + export const startMihomoTraffic = async (): Promise => { await mihomoTraffic() } diff --git a/src/main/utils/dirs.ts b/src/main/utils/dirs.ts index 738f8fb..13a219f 100644 --- a/src/main/utils/dirs.ts +++ b/src/main/utils/dirs.ts @@ -69,6 +69,10 @@ export function mihomoCoreDir(): string { export function mihomoCorePath(core: string): string { const isWin = process.platform === 'win32' + // 处理 Smart 内核 + if (core === 'mihomo-smart') { + return path.join(mihomoCoreDir(), `mihomo-smart${isWin ? '.exe' : ''}`) + } return path.join(mihomoCoreDir(), `${core}${isWin ? '.exe' : ''}`) } diff --git a/src/main/utils/ipc.ts b/src/main/utils/ipc.ts index 53b2750..ce1c7ec 100644 --- a/src/main/utils/ipc.ts +++ b/src/main/utils/ipc.ts @@ -16,7 +16,9 @@ import { mihomoUpgrade, mihomoUpgradeGeo, mihomoVersion, - patchMihomoConfig + patchMihomoConfig, + mihomoSmartGroupWeights, + mihomoSmartFlushCache } from '../core/mihomoApi' import { checkAutoRun, disableAutoRun, enableAutoRun } from '../sys/autoRun' import { @@ -141,6 +143,13 @@ export function registerIpcMainHandlers(): void { ipcErrorWrapper(mihomoGroupDelay)(group, url) ) ipcMain.handle('patchMihomoConfig', (_e, patch) => ipcErrorWrapper(patchMihomoConfig)(patch)) + // Smart 内核 API + ipcMain.handle('mihomoSmartGroupWeights', (_e, groupName) => + ipcErrorWrapper(mihomoSmartGroupWeights)(groupName) + ) + ipcMain.handle('mihomoSmartFlushCache', (_e, configName) => + ipcErrorWrapper(mihomoSmartFlushCache)(configName) + ) ipcMain.handle('checkAutoRun', ipcErrorWrapper(checkAutoRun)) ipcMain.handle('enableAutoRun', ipcErrorWrapper(enableAutoRun)) ipcMain.handle('disableAutoRun', ipcErrorWrapper(disableAutoRun)) @@ -251,6 +260,18 @@ export function registerIpcMainHandlers(): void { ipcMain.handle('alert', (_e, msg) => { dialog.showErrorBox('Mihomo Party', msg) }) + ipcMain.handle('showDetailedError', (_e, title, message) => { + dialog.showErrorBox(title, message) + }) + ipcMain.handle('getSmartOverrideContent', async () => { + const { getOverrideItem } = await import('../config') + try { + const override = await getOverrideItem('smart-core-override') + return override?.file || null + } catch (error) { + return null + } + }) ipcMain.handle('resetAppConfig', resetAppConfig) ipcMain.handle('relaunchApp', () => { app.relaunch() diff --git a/src/main/utils/template.ts b/src/main/utils/template.ts index 1d7669e..d9b86f2 100644 --- a/src/main/utils/template.ts +++ b/src/main/utils/template.ts @@ -1,5 +1,10 @@ export const defaultConfig: IAppConfig = { core: 'mihomo', + enableSmartCore: true, + enableSmartOverride: true, + smartCoreUseLightGBM: false, + smartCoreCollectData: false, + smartCoreStrategy: 'sticky-sessions', silentStart: false, appTheme: 'system', useWindowFrame: false, diff --git a/src/renderer/src/locales/en-US.json b/src/renderer/src/locales/en-US.json index ef961da..d6960de 100644 --- a/src/renderer/src/locales/en-US.json +++ b/src/renderer/src/locales/en-US.json @@ -114,6 +114,15 @@ "mihomo.selectCoreVersion": "Select Core Version", "mihomo.stableVersion": "Stable", "mihomo.alphaVersion": "Alpha", + "mihomo.smartVersion": "Smart", + "mihomo.enableSmartCore": "Enable Smart Core", + "mihomo.enableSmartOverride": "Use Auto Smart Rule Override", + "mihomo.smartOverrideTooltip": "Use Party's built-in smart override script to extract all nodes from subscriptions and replace default rules. Perfect for users who don't want to tinker, one-click functionality; if using global mode, please select the node named 'Smart Group'", + "mihomo.smartCoreUseLightGBM": "Use LightGBM", + "mihomo.smartCoreCollectData": "Collect Data", + "mihomo.smartCoreStrategy": "Strategy Mode", + "mihomo.smartCoreStrategyStickySession": "Sticky Sessions", + "mihomo.smartCoreStrategyRoundRobin": "Round Robin", "mihomo.mixedPort": "Mixed Port", "mihomo.confirm": "Confirm", "mihomo.socksPort": "Socks Port", diff --git a/src/renderer/src/locales/zh-CN.json b/src/renderer/src/locales/zh-CN.json index 490a98d..88c7e0a 100644 --- a/src/renderer/src/locales/zh-CN.json +++ b/src/renderer/src/locales/zh-CN.json @@ -114,6 +114,15 @@ "mihomo.selectCoreVersion": "选择内核版本", "mihomo.stableVersion": "稳定版", "mihomo.alphaVersion": "预览版", + "mihomo.smartVersion": "Smart 版", + "mihomo.enableSmartCore": "启用 Smart 内核", + "mihomo.enableSmartOverride": "使用自动 Smart 规则覆写", + "mihomo.smartOverrideTooltip": "使用 Party 自带的智能覆写脚本,提取订阅中的所有节点,并替换默认规则,适合不想折腾的用户,功能一键生效;如果使用全局模式,请选择名称为“Smart Group 的节点", + "mihomo.smartCoreUseLightGBM": "使用 LightGBM", + "mihomo.smartCoreCollectData": "收集数据", + "mihomo.smartCoreStrategy": "策略模式", + "mihomo.smartCoreStrategyStickySession": "粘性会话", + "mihomo.smartCoreStrategyRoundRobin": "轮询", "mihomo.mixedPort": "混合端口", "mihomo.confirm": "确认", "mihomo.socksPort": "Socks 端口", diff --git a/src/renderer/src/pages/mihomo.tsx b/src/renderer/src/pages/mihomo.tsx index 20f45a7..cbe6f42 100644 --- a/src/renderer/src/pages/mihomo.tsx +++ b/src/renderer/src/pages/mihomo.tsx @@ -1,4 +1,4 @@ -import { Button, Divider, Input, Select, SelectItem, Switch } from '@heroui/react' +import { Button, Divider, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react' import BasePage from '@renderer/components/base/base-page' import SettingCard from '@renderer/components/base/base-setting-card' import SettingItem from '@renderer/components/base/base-setting-item' @@ -6,13 +6,14 @@ import { useAppConfig } from '@renderer/hooks/use-app-config' import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config' import { platform } from '@renderer/utils/init' import { FaNetworkWired } from 'react-icons/fa' -import { IoMdCloudDownload } from 'react-icons/io' +import { IoMdCloudDownload, IoMdInformationCircleOutline } from 'react-icons/io' import PubSub from 'pubsub-js' import { mihomoUpgrade, restartCore, startSubStoreBackendServer, - triggerSysProxy + triggerSysProxy, + showDetailedError } from '@renderer/utils/ipc' import React, { useState } from 'react' import InterfaceModal from '@renderer/components/mihomo/interface-modal' @@ -21,7 +22,8 @@ import { useTranslation } from 'react-i18next' const CoreMap = { mihomo: 'mihomo.stableVersion', - 'mihomo-alpha': 'mihomo.alphaVersion' + 'mihomo-alpha': 'mihomo.alphaVersion', + 'mihomo-smart': 'mihomo.smartVersion' } const Mihomo: React.FC = () => { @@ -29,6 +31,11 @@ const Mihomo: React.FC = () => { const { appConfig, patchAppConfig } = useAppConfig() const { core = 'mihomo', + enableSmartCore = true, + enableSmartOverride = true, + smartCoreUseLightGBM = false, + smartCoreCollectData = false, + smartCoreStrategy = 'sticky-sessions', maxLogDays = 7, sysProxy, disableLoopbackDetector, @@ -82,7 +89,14 @@ const Mihomo: React.FC = () => { await patchAppConfig({ [key]: value }) await restartCore() } catch (e) { - alert(e) + const errorMessage = e instanceof Error ? e.message : String(e) + console.error('Core restart failed:', errorMessage) + + if (errorMessage.includes('配置检查失败') || errorMessage.includes('Profile Check Failed')) { + await showDetailedError(t('mihomo.error.profileCheckFailed'), errorMessage) + } else { + alert(errorMessage) + } } finally { PubSub.publish('mihomo-core-changed') } @@ -92,59 +106,184 @@ const Mihomo: React.FC = () => { <> {lanOpen && setLanOpen(false)} />} + {/* Smart 内核设置 */} - + + { - try { - setUpgrading(true) - await mihomoUpgrade() - setTimeout(() => { - PubSub.publish('mihomo-core-changed') - }, 2000) - if (platform !== 'win32') { - new Notification(t('mihomo.coreAuthLost'), { - body: t('mihomo.coreUpgradeSuccess') - }) - } - } catch (e) { - if (typeof e === 'string' && e.includes('already using latest version')) { - new Notification(t('mihomo.alreadyLatestVersion')) - } else { - alert(e) - } - } finally { - setUpgrading(false) + isSelected={enableSmartCore} + color={enableSmartCore ? 'primary' : 'default'} + onValueChange={async (v) => { + await patchAppConfig({ enableSmartCore: v }) + if (v && core !== 'mihomo-smart') { + await handleConfigChangeWithRestart('core', 'mihomo-smart') + } else if (!v && core === 'mihomo-smart') { + await handleConfigChangeWithRestart('core', 'mihomo') } }} + /> + + + {/* Smart 覆写开关 */} + {enableSmartCore && ( + + {t('mihomo.enableSmartOverride')} + + + + + } + divider={core === 'mihomo-smart'} > - - - } - divider - > - - + + + + {/* Smart 内核配置项 */} + {enableSmartCore && core === 'mihomo-smart' && ( + <> + + { + await patchAppConfig({ smartCoreUseLightGBM: v }) + await restartCore() + }} + /> + + + + { + await patchAppConfig({ smartCoreCollectData: v }) + await restartCore() + }} + /> + + + + + + + )} + + + + {/* 常规内核设置 */} +
{mixedPortInput !== mixedPort && ( diff --git a/src/renderer/src/utils/ipc.ts b/src/renderer/src/utils/ipc.ts index 4dc35b1..8900eee 100644 --- a/src/renderer/src/utils/ipc.ts +++ b/src/renderer/src/utils/ipc.ts @@ -79,6 +79,22 @@ export async function mihomoGroupDelay(group: string, url?: string): Promise> { + return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoSmartGroupWeights', groupName)) +} + +export async function mihomoSmartFlushCache(configName?: string): Promise { + return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoSmartFlushCache', configName)) +} + +export async function showDetailedError(title: string, message: string): Promise { + return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showDetailedError', title, message)) +} + +export async function getSmartOverrideContent(): Promise { + return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getSmartOverrideContent')) +} + export async function patchMihomoConfig(patch: Partial): Promise { return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('patchMihomoConfig', patch)) } diff --git a/src/shared/types.d.ts b/src/shared/types.d.ts index 8e37812..46a1e9a 100644 --- a/src/shared/types.d.ts +++ b/src/shared/types.d.ts @@ -216,7 +216,12 @@ interface ISysProxyConfig { } interface IAppConfig { - core: 'mihomo' | 'mihomo-alpha' + core: 'mihomo' | 'mihomo-alpha' | 'mihomo-smart' + enableSmartCore: boolean + enableSmartOverride: boolean + smartCoreUseLightGBM: boolean + smartCoreCollectData: boolean + smartCoreStrategy: 'sticky-sessions' | 'round-robin' disableLoopbackDetector: boolean disableEmbedCA: boolean disableSystemCA: boolean