mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 05:00:30 +08:00
feat: add smart core & one-click smart
This commit is contained in:
parent
364578f210
commit
46aa654ee3
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -27,3 +27,9 @@ export {
|
||||
setOverride,
|
||||
updateOverrideItem
|
||||
} from './override'
|
||||
export {
|
||||
createSmartOverride,
|
||||
removeSmartOverride,
|
||||
manageSmartOverride,
|
||||
isSmartOverrideExists
|
||||
} from './smartOverride'
|
||||
|
||||
275
src/main/config/smartOverride.ts
Normal file
275
src/main/config/smartOverride.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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' : ''}`)
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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 端口",
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
7
src/shared/types.d.ts
vendored
7
src/shared/types.d.ts
vendored
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user