feat: add smart core & one-click smart

This commit is contained in:
ezequielnick 2025-08-02 21:09:01 +08:00
parent 364578f210
commit 46aa654ee3
14 changed files with 658 additions and 63 deletions

View File

@ -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

View File

@ -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()

View File

@ -27,3 +27,9 @@ export {
setOverride,
updateOverrideItem
} from './override'
export {
createSmartOverride,
removeSmartOverride,
manageSmartOverride,
isSmartOverrideExists
} from './smartOverride'

View File

@ -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<void> {
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<void> {
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<void> {
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<boolean> {
try {
const override = await getOverrideItem(SMART_OVERRIDE_ID)
return !!override
} catch {
return false
}
}

View File

@ -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<Promise<void>[]> {
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<void> {
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}`)
}
}
}

View File

@ -168,6 +168,21 @@ export const mihomoUpgrade = async (): Promise<void> => {
return await instance.post('/upgrade')
}
// Smart 内核 API
export const mihomoSmartGroupWeights = async (groupName: string): Promise<Record<string, number>> => {
const instance = await getAxios()
return await instance.get(`/group/${encodeURIComponent(groupName)}/weights`)
}
export const mihomoSmartFlushCache = async (configName?: string): Promise<void> => {
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<void> => {
await mihomoTraffic()
}

View File

@ -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' : ''}`)
}

View File

@ -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()

View File

@ -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,

View File

@ -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",

View File

@ -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 端口",

View File

@ -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 && <InterfaceModal onClose={() => setLanOpen(false)} />}
<BasePage title={t('mihomo.title')}>
{/* Smart 内核设置 */}
<SettingCard>
<SettingItem
title={t('mihomo.coreVersion')}
actions={
<Button
<div className={`rounded-md border p-2 transition-all duration-200 ${
enableSmartCore
? 'border-blue-300 bg-blue-50/30 dark:border-blue-700 dark:bg-blue-950/20'
: 'border-gray-300 bg-gray-50/30 dark:border-gray-600 dark:bg-gray-800/20'
}`}>
<SettingItem
title={t('mihomo.enableSmartCore')}
divider
>
<Switch
size="sm"
isIconOnly
title={t('mihomo.upgradeCore')}
variant="light"
isLoading={upgrading}
onPress={async () => {
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')
}
}}
/>
</SettingItem>
{/* Smart 覆写开关 */}
{enableSmartCore && (
<SettingItem
title={
<div className="flex items-center gap-2">
<span>{t('mihomo.enableSmartOverride')}</span>
<Tooltip
content={t('mihomo.smartOverrideTooltip')}
placement="top"
className="max-w-xs"
>
<IoMdInformationCircleOutline className="text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 cursor-help" />
</Tooltip>
</div>
}
divider={core === 'mihomo-smart'}
>
<IoMdCloudDownload className="text-lg" />
</Button>
}
divider
>
<Select
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
className="w-[100px]"
size="sm"
aria-label={t('mihomo.selectCoreVersion')}
selectedKeys={new Set([core])}
disallowEmptySelection={true}
onSelectionChange={async (v) => {
handleConfigChangeWithRestart('core', v.currentKey as 'mihomo' | 'mihomo-alpha')
}}
<Switch
size="sm"
isSelected={enableSmartOverride}
color="primary"
onValueChange={async (v) => {
await patchAppConfig({ enableSmartOverride: v })
await restartCore()
}}
/>
</SettingItem>
)}
<SettingItem
title={t('mihomo.coreVersion')}
actions={
<Button
size="sm"
isIconOnly
title={t('mihomo.upgradeCore')}
variant="light"
isLoading={upgrading}
onPress={async () => {
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)
}
}}
>
<IoMdCloudDownload className="text-lg" />
</Button>
}
divider={enableSmartCore && core === 'mihomo-smart'}
>
<SelectItem key="mihomo">{t(CoreMap['mihomo'])}</SelectItem>
<SelectItem key="mihomo-alpha">{t(CoreMap['mihomo-alpha'])}</SelectItem>
</Select>
</SettingItem>
<Select
classNames={{
trigger: enableSmartCore
? 'data-[hover=true]:bg-blue-100 dark:data-[hover=true]:bg-blue-900/50'
: 'data-[hover=true]:bg-default-200'
}}
className="w-[100px]"
size="sm"
aria-label={t('mihomo.selectCoreVersion')}
selectedKeys={new Set([core])}
disallowEmptySelection={true}
onSelectionChange={async (v) => {
handleConfigChangeWithRestart('core', v.currentKey as 'mihomo' | 'mihomo-alpha' | 'mihomo-smart')
}}
>
{enableSmartCore ? (
<SelectItem key="mihomo-smart">{t(CoreMap['mihomo-smart'])}</SelectItem>
) : (
<>
<SelectItem key="mihomo">{t(CoreMap['mihomo'])}</SelectItem>
<SelectItem key="mihomo-alpha">{t(CoreMap['mihomo-alpha'])}</SelectItem>
</>
)}
</Select>
</SettingItem>
{/* Smart 内核配置项 */}
{enableSmartCore && core === 'mihomo-smart' && (
<>
<SettingItem
title={t('mihomo.smartCoreUseLightGBM')}
divider
>
<Switch
size="sm"
color="primary"
isSelected={smartCoreUseLightGBM}
onValueChange={async (v) => {
await patchAppConfig({ smartCoreUseLightGBM: v })
await restartCore()
}}
/>
</SettingItem>
<SettingItem
title={t('mihomo.smartCoreCollectData')}
divider
>
<Switch
size="sm"
color="primary"
isSelected={smartCoreCollectData}
onValueChange={async (v) => {
await patchAppConfig({ smartCoreCollectData: v })
await restartCore()
}}
/>
</SettingItem>
<SettingItem
title={t('mihomo.smartCoreStrategy')}
>
<Select
classNames={{ trigger: 'data-[hover=true]:bg-blue-100 dark:data-[hover=true]:bg-blue-900/50' }}
className="w-[150px]"
size="sm"
aria-label={t('mihomo.smartCoreStrategy')}
selectedKeys={new Set([smartCoreStrategy])}
disallowEmptySelection={true}
onSelectionChange={async (v) => {
const strategy = v.currentKey as 'sticky-sessions' | 'round-robin'
await patchAppConfig({ smartCoreStrategy: strategy })
await restartCore()
}}
>
<SelectItem key="sticky-sessions">{t('mihomo.smartCoreStrategyStickySession')}</SelectItem>
<SelectItem key="round-robin">{t('mihomo.smartCoreStrategyRoundRobin')}</SelectItem>
</Select>
</SettingItem>
</>
)}
</div>
</SettingCard>
{/* 常规内核设置 */}
<SettingCard>
<SettingItem title={t('mihomo.mixedPort')} divider>
<div className="flex">
{mixedPortInput !== mixedPort && (

View File

@ -79,6 +79,22 @@ export async function mihomoGroupDelay(group: string, url?: string): Promise<IMi
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoGroupDelay', group, url))
}
export async function mihomoSmartGroupWeights(groupName: string): Promise<Record<string, number>> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoSmartGroupWeights', groupName))
}
export async function mihomoSmartFlushCache(configName?: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoSmartFlushCache', configName))
}
export async function showDetailedError(title: string, message: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('showDetailedError', title, message))
}
export async function getSmartOverrideContent(): Promise<string | null> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getSmartOverrideContent'))
}
export async function patchMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('patchMihomoConfig', patch))
}

View File

@ -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