Compare commits

...

2 Commits

Author SHA1 Message Date
ezequielnick
b21381062f fix: ENOENT: no such file or directory(config.yaml) 2025-09-10 19:21:47 +08:00
Memory
ecd92417e4
feat: add kernel version selection
* feat: add kernel version selection

* full

* update translations
2025-09-10 19:19:06 +08:00
13 changed files with 558 additions and 59 deletions

View File

@ -6,8 +6,8 @@
<h3 align="center">Another <a href="https://github.com/MetaCubeX/mihomo">Mihomo</a> GUI</h3>
<p align="center">
<a href="https://github.com/mihomo-party-org/mihomo-party/releases">
<img src="https://img.shields.io/github/release/mihomo-party-org/mihomo-party/all.svg">
<a href="https://github.com/mihomo-party-org/clash-party/releases">
<img src="https://img.shields.io/github/release/mihomo-party-org/clash-party/all.svg">
</a>
<a href="https://t.me/mihomo_party_group">
<img src="https://img.shields.io/badge/Telegram-Group-blue?logo=telegram">
@ -30,7 +30,7 @@
### 特性
- [x] 一键 Smart Core 规则覆写,基于 AI 模型自动选择最优节点 详细介绍请看 [这里](https://mihomo.party/docs/guide/smart-core)
- [x] 一键 Smart Core 规则覆写,基于 AI 模型自动选择最优节点 详细介绍请看 [这里](https://clashparty.org/docs/guide/smart-core)
- [x] 开箱即用,无需服务模式的 Tun
- [x] 多种配色主题可选UI 焕然一新
- [x] 支持大部分 Mihomo(Clash Meta) 常用配置修改
@ -39,4 +39,4 @@
- [x] 强大的覆写功能,任意修订配置文件
- [x] 深度集成 Sub-Store轻松管理订阅
### 安装/使用指南见 [官方文档](https://mihomo.party)
### 安装/使用指南见 [官方文档](https://clashparty.org)

View File

@ -1,3 +1,9 @@
## 1.8.8
### 修复 (Fix)
- MacOS 首次启动时的 ENOENT: no such file or directory(config.yaml)
- 自动更新获取老的文件名称
## 1.8.7
### 新功能 (Feat)

View File

@ -1,6 +1,6 @@
{
"name": "mihomo-party",
"version": "1.8.7",
"version": "1.8.8",
"description": "Clash Party",
"main": "./out/main/index.js",
"author": "mihomo-party-org",

View File

@ -221,6 +221,8 @@ app.whenReady().then(async () => {
// Set app user model id for windows
electronApp.setAppUserModelId('party.mihomo.app')
await initBasic()
await checkHighPrivilegeCoreEarly()
await initAdminStatus()

232
src/main/utils/github.ts Normal file
View File

@ -0,0 +1,232 @@
import axios from 'axios'
import { createWriteStream, createReadStream } from 'fs'
import { mihomoCoreDir } from './dirs'
import AdmZip from 'adm-zip'
import { execSync } from 'child_process'
import { platform } from 'os'
import { join } from 'path'
import { existsSync, rmSync } from 'fs'
import { createGunzip } from 'zlib'
import { stopCore } from '../core/manager'
export interface GitHubTag {
name: string
zipball_url: string
tarball_url: string
}
interface VersionCache {
data: GitHubTag[]
timestamp: number
}
const CACHE_EXPIRY = 5 * 60 * 1000
const GITHUB_API_CONFIG = {
BASE_URL: 'https://api.github.com',
API_VERSION: '2022-11-28',
TAGS_PER_PAGE: 100
}
const PLATFORM_MAP: Record<string, string> = {
'win32-x64': 'mihomo-windows-amd64-compatible',
'win32-ia32': 'mihomo-windows-386',
'win32-arm64': 'mihomo-windows-arm64',
'darwin-x64': 'mihomo-darwin-amd64-compatible',
'darwin-arm64': 'mihomo-darwin-arm64',
'linux-x64': 'mihomo-linux-amd64-compatible',
'linux-arm64': 'mihomo-linux-arm64'
}
const versionCache = new Map<string, VersionCache>()
/**
* GitHub仓库的标签列表
* @param owner
* @param repo
* @param forceRefresh
* @returns
*/
export async function getGitHubTags(owner: string, repo: string, forceRefresh = false): Promise<GitHubTag[]> {
const cacheKey = `${owner}/${repo}`
// 检查缓存
if (!forceRefresh && versionCache.has(cacheKey)) {
const cache = versionCache.get(cacheKey)!
// 检查缓存是否过期
if (Date.now() - cache.timestamp < CACHE_EXPIRY) {
console.log(`[GitHub] Returning cached tags for ${owner}/${repo}`)
return cache.data
}
}
try {
console.log(`[GitHub] Fetching tags for ${owner}/${repo}`)
const response = await axios.get<GitHubTag[]>(
`${GITHUB_API_CONFIG.BASE_URL}/repos/${owner}/${repo}/tags?per_page=${GITHUB_API_CONFIG.TAGS_PER_PAGE}`,
{
headers: {
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': GITHUB_API_CONFIG.API_VERSION
},
timeout: 10000
}
)
// 更新缓存
versionCache.set(cacheKey, {
data: response.data,
timestamp: Date.now()
})
console.log(`[GitHub] Successfully fetched ${response.data.length} tags for ${owner}/${repo}`)
return response.data
} catch (error) {
console.error(`[GitHub] Failed to fetch tags for ${owner}/${repo}:`, error)
if (axios.isAxiosError(error)) {
throw new Error(`GitHub API error: ${error.response?.status} - ${error.response?.statusText}`)
}
throw new Error('Failed to fetch version list')
}
}
/**
*
* @param owner
* @param repo
*/
export function clearVersionCache(owner: string, repo: string): void {
const cacheKey = `${owner}/${repo}`
const hasCache = versionCache.has(cacheKey)
versionCache.delete(cacheKey)
console.log(`[GitHub] Cache ${hasCache ? 'cleared' : 'not found'} for ${owner}/${repo}`)
}
/**
* GitHub Release资产
* @param url URL
* @param outputPath
*/
async function downloadGitHubAsset(url: string, outputPath: string): Promise<void> {
try {
console.log(`[GitHub] Downloading asset from ${url}`)
const writer = createWriteStream(outputPath)
const response = await axios({
url,
method: 'GET',
responseType: 'stream',
timeout: 30000
})
response.data.pipe(writer)
return new Promise((resolve, reject) => {
writer.on('finish', () => {
console.log(`[GitHub] Successfully downloaded asset to ${outputPath}`)
resolve()
})
writer.on('error', (error) => {
console.error(`[GitHub] Failed to write asset to ${outputPath}:`, error)
reject(new Error(`Failed to download core file: ${error.message}`))
})
})
} catch (error) {
console.error(`[GitHub] Failed to download asset from ${url}:`, error)
if (axios.isAxiosError(error)) {
throw new Error(`Download error: ${error.response?.status} - ${error.response?.statusText}`)
}
throw new Error('Failed to download core file')
}
}
/**
* mihomo核心
* @param version
*/
export async function installMihomoCore(version: string): Promise<void> {
try {
console.log(`[GitHub] Installing mihomo core version ${version}`)
const plat = platform()
let arch = process.arch
// 映射平台和架构到GitHub Release文件名
const key = `${plat}-${arch}`
const name = PLATFORM_MAP[key]
if (!name) {
throw new Error(`Unsupported platform "${plat}-${arch}"`)
}
const isWin = plat === 'win32'
const urlExt = isWin ? 'zip' : 'gz'
const downloadURL = `https://github.com/MetaCubeX/mihomo/releases/download/${version}/${name}-${version}.${urlExt}`
const coreDir = mihomoCoreDir()
const tempZip = join(coreDir, `temp-core.${urlExt}`)
const exeFile = `${name}${isWin ? '.exe' : ''}`
const targetFile = `mihomo-specific${isWin ? '.exe' : ''}`
const targetPath = join(coreDir, targetFile)
// 如果目标文件已存在,先停止核心
if (existsSync(targetPath)) {
console.log('[GitHub] Stopping core before extracting new core file')
// 先停止核心
await stopCore(true)
}
// 下载文件
await downloadGitHubAsset(downloadURL, tempZip)
// 解压文件
if (urlExt === 'zip') {
console.log(`[GitHub] Extracting ZIP file ${tempZip}`)
const zip = new AdmZip(tempZip)
const entries = zip.getEntries()
const entry = entries.find(e => e.entryName.includes(exeFile))
if (entry) {
zip.extractEntryTo(entry, coreDir, false, true, false, targetFile)
console.log(`[GitHub] Successfully extracted ${exeFile} to ${targetPath}`)
} else {
throw new Error(`Executable file not found in zip: ${exeFile}`)
}
} else {
// 处理.gz文件
console.log(`[GitHub] Extracting GZ file ${tempZip}`)
const readStream = createReadStream(tempZip)
const writeStream = createWriteStream(targetPath)
await new Promise<void>((resolve, reject) => {
const onError = (error: Error) => {
console.error('[GitHub] Gzip decompression failed:', error.message)
reject(new Error(`Gzip decompression failed: ${error.message}`))
}
readStream
.pipe(createGunzip().on('error', onError))
.pipe(writeStream)
.on('finish', () => {
console.log('[GitHub] Gunzip finished')
try {
execSync(`chmod 755 ${targetPath}`)
console.log('[GitHub] Chmod binary finished')
} catch (chmodError) {
console.warn('[GitHub] Failed to chmod binary:', chmodError)
}
resolve()
})
.on('error', onError)
})
}
// 清理临时文件
console.log(`[GitHub] Cleaning up temporary file ${tempZip}`)
rmSync(tempZip)
console.log(`[GitHub] Successfully installed mihomo core version ${version}`)
} catch (error) {
console.error('[GitHub] Failed to install mihomo core:', error)
throw new Error(`Failed to install core: ${error instanceof Error ? error.message : String(error)}`)
}
}

View File

@ -21,6 +21,7 @@ import {
mihomoSmartFlushCache
} from '../core/mihomoApi'
import { checkAutoRun, disableAutoRun, enableAutoRun } from '../sys/autoRun'
import { installMihomoCore, getGitHubTags, clearVersionCache } from './github'
import {
getAppConfig,
patchAppConfig,
@ -128,6 +129,22 @@ function ipcErrorWrapper<T>( // eslint-disable-next-line @typescript-eslint/no-e
}
}
}
// GitHub版本管理相关IPC处理程序
export async function fetchMihomoTags(forceRefresh = false): Promise<{name: string, zipball_url: string, tarball_url: string}[]> {
return await getGitHubTags('MetaCubeX', 'mihomo', forceRefresh)
}
export async function installSpecificMihomoCore(version: string): Promise<void> {
// 安装完成后清除缓存,以便下次获取最新的标签列表
clearVersionCache('MetaCubeX', 'mihomo')
return await installMihomoCore(version)
}
export async function clearMihomoVersionCache(): Promise<void> {
clearVersionCache('MetaCubeX', 'mihomo')
}
export function registerIpcMainHandlers(): void {
ipcMain.handle('mihomoVersion', ipcErrorWrapper(mihomoVersion))
ipcMain.handle('mihomoCloseConnection', (_e, id) => ipcErrorWrapper(mihomoCloseConnection)(id))
@ -314,4 +331,13 @@ export function registerIpcMainHandlers(): void {
// 触发托盘菜单更新
ipcMain.emit('updateTrayMenu')
})
}
// 注册获取Mihomo标签的IPC处理程序
ipcMain.handle('fetchMihomoTags', (_e, forceRefresh) => ipcErrorWrapper(fetchMihomoTags)(forceRefresh))
// 注册安装特定版本Mihomo核心的IPC处理程序
ipcMain.handle('installSpecificMihomoCore', (_e, version) => ipcErrorWrapper(installSpecificMihomoCore)(version))
// 注册清除版本缓存的IPC处理程序
ipcMain.handle('clearMihomoVersionCache', () => ipcErrorWrapper(clearMihomoVersionCache)())
}

View File

@ -43,6 +43,7 @@
"common.updater.versionReady": "v{{version}} Version Ready",
"common.updater.goToDownload": "Go to Download",
"common.updater.update": "Update",
"common.refresh": "Refresh",
"settings.general": "General Settings",
"settings.mihomo": "Mihomo Settings",
"settings.language": "Language",
@ -128,6 +129,13 @@
"mihomo.stableVersion": "Stable",
"mihomo.alphaVersion": "Alpha",
"mihomo.smartVersion": "Smart",
"mihomo.specificVersion": "Specific Version",
"mihomo.selectSpecificVersion": "Select Specific Version",
"mihomo.searchVersion": "Search version...",
"mihomo.installVersion": "Install Version",
"mihomo.noVersionsFound": "No versions found",
"mihomo.error.fetchTagsFailed": "Failed to fetch version list",
"mihomo.error.installCoreFailed": "Failed to install core",
"mihomo.enableSmartCore": "Enable Smart Core",
"mihomo.enableSmartOverride": "Use Auto Smart Rule Override",
"mihomo.smartOverrideTooltip": "Use the built-in smart override script in Party to replace url-test and load-balance with the Smart rule group; if the above rule groups are not present, extract all nodes from the subscription and replace the default rules, suitable for users who dont want to hassle, with the feature taking effect with one click; if using global mode, please select the node named 'Smart Group.'",

View File

@ -125,6 +125,13 @@
"mihomo.stableVersion": "نسخه پایدار",
"mihomo.alphaVersion": "نسخه آلفا",
"mihomo.smartVersion": "Smart",
"mihomo.specificVersion": "نسخه خاص",
"mihomo.selectSpecificVersion": "انتخاب نسخه خاص",
"mihomo.searchVersion": "جستجوی نسخه...",
"mihomo.installVersion": "نصب نسخه",
"mihomo.noVersionsFound": "نسخه‌ای یافت نشد",
"mihomo.error.fetchTagsFailed": "دریافت لیست نسخه‌ها با شکست مواجه شد",
"mihomo.error.installCoreFailed": "نصب هسته با شکست مواجه شد",
"mihomo.enableSmartCore": "فعال‌سازی Smart Core",
"mihomo.enableSmartOverride": "استفاده از بازنویسی خودکار Smart",
"mihomo.smartOverrideTooltip": "از اسکریپت بازنویسی هوشمند داخلی Party برای جایگزینی url-test و load-balance با گروه قواعد Smart استفاده کنید؛ اگر گروه‌های قواعد فوق وجود ندارند، تمام گره‌ها را از اشتراک استخراج کرده و قواعد پیش‌فرض را جایگزین کنید، برای کاربرانی که نمی‌خواهند دردسر باشند مناسب است، قابلیت با یک کلیک فعال می‌شود؛ اگر از حالت جهانی استفاده می‌کنید، لطفاً گره با نام 'Smart Group' را انتخاب کنید",

View File

@ -125,6 +125,13 @@
"mihomo.stableVersion": "Стабильная",
"mihomo.alphaVersion": "Альфа",
"mihomo.smartVersion": "Smart",
"mihomo.specificVersion": "Определённая версия",
"mihomo.selectSpecificVersion": "Выбрать определённую версию",
"mihomo.searchVersion": "Поиск версии...",
"mihomo.installVersion": "Установить версию",
"mihomo.noVersionsFound": "Версии не найдены",
"mihomo.error.fetchTagsFailed": "Не удалось получить список версий",
"mihomo.error.installCoreFailed": "Не удалось установить ядро",
"mihomo.enableSmartCore": "Включить Smart ядро",
"mihomo.enableSmartOverride": "Использовать автоматическое Smart переопределение правил",
"mihomo.smartOverrideTooltip": "Использовать встроенный скрипт умного переопределения в Party для замены url-test и load-balance на Smart группы правил; если указанные группы правил отсутствуют, извлечь все узлы из подписки и заменить правила по умолчанию, подходит для пользователей, которые не хотят заморачиваться, функция действует одним кликом; если используется глобальный режим, выберите узел с именем 'Smart Group'",

View File

@ -43,6 +43,7 @@
"common.updater.versionReady": "v{{version}} 版本就绪",
"common.updater.goToDownload": "前往下载",
"common.updater.update": "更新",
"common.refresh": "刷新",
"settings.general": "通用设置",
"settings.mihomo": "Mihomo 设置",
"settings.language": "语言",
@ -128,6 +129,13 @@
"mihomo.stableVersion": "稳定版",
"mihomo.alphaVersion": "预览版",
"mihomo.smartVersion": "Smart 版",
"mihomo.specificVersion": "特定版本",
"mihomo.selectSpecificVersion": "选择特定版本",
"mihomo.searchVersion": "搜索版本...",
"mihomo.installVersion": "安装版本",
"mihomo.noVersionsFound": "未找到版本",
"mihomo.error.fetchTagsFailed": "获取版本列表失败",
"mihomo.error.installCoreFailed": "安装内核失败",
"mihomo.enableSmartCore": "启用 Smart 内核",
"mihomo.enableSmartOverride": "使用自动 Smart 规则覆写",
"mihomo.smartOverrideTooltip": "使用 Party 自带的智能覆写脚本,替换 url-test 和 load-balance 为 Smart 规则组如果没有上述规则组则提取订阅中的所有节点并替换默认规则适合不想折腾的用户功能一键生效如果使用全局模式请选择名称为“Smart Group 的节点",

View File

@ -1,4 +1,4 @@
import { Button, Divider, Input, Select, SelectItem, Switch, Tooltip } from '@heroui/react'
import { Button, Divider, Input, Select, SelectItem, Switch, Tooltip, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, useDisclosure, Spinner, Chip } 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,16 +6,19 @@ 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, IoMdInformationCircleOutline } from 'react-icons/io'
import { IoMdCloudDownload, IoMdInformationCircleOutline, IoMdRefresh } from 'react-icons/io'
import PubSub from 'pubsub-js'
import {
mihomoUpgrade,
restartCore,
startSubStoreBackendServer,
triggerSysProxy,
showDetailedError
showDetailedError,
fetchMihomoTags,
installSpecificMihomoCore,
clearMihomoVersionCache
} from '@renderer/utils/ipc'
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import InterfaceModal from '@renderer/components/mihomo/interface-modal'
import { MdDeleteForever } from 'react-icons/md'
import { useTranslation } from 'react-i18next'
@ -23,7 +26,8 @@ import { useTranslation } from 'react-i18next'
const CoreMap = {
mihomo: 'mihomo.stableVersion',
'mihomo-alpha': 'mihomo.alphaVersion',
'mihomo-smart': 'mihomo.smartVersion'
'mihomo-smart': 'mihomo.smartVersion',
'mihomo-specific': 'mihomo.specificVersion'
}
const Mihomo: React.FC = () => {
@ -31,6 +35,7 @@ const Mihomo: React.FC = () => {
const { appConfig, patchAppConfig } = useAppConfig()
const {
core = 'mihomo',
specificVersion,
enableSmartCore = true,
enableSmartOverride = true,
smartCoreUseLightGBM = false,
@ -78,7 +83,14 @@ const Mihomo: React.FC = () => {
const [skipAuthPrefixesInput, setSkipAuthPrefixesInput] = useState(skipAuthPrefixes)
const [upgrading, setUpgrading] = useState(false)
const [lanOpen, setLanOpen] = useState(false)
const { isOpen, onOpen, onClose } = useDisclosure()
const [tags, setTags] = useState<{name: string, zipball_url: string, tarball_url: string}[]>([])
const [loadingTags, setLoadingTags] = useState(false)
const [selectedTag, setSelectedTag] = useState(specificVersion || '')
const [installing, setInstalling] = useState(false)
const [searchTerm, setSearchTerm] = useState('')
const [refreshing, setRefreshing] = useState(false)
const onChangeNeedRestart = async (patch: Partial<IMihomoConfig>): Promise<void> => {
await patchControledMihomoConfig(patch)
await restartCore()
@ -101,7 +113,90 @@ const Mihomo: React.FC = () => {
PubSub.publish('mihomo-core-changed')
}
}
// 获取GitHub标签列表带缓存
const fetchTags = async (forceRefresh = false) => {
setLoadingTags(true)
try {
const data = await fetchMihomoTags(forceRefresh)
setTags(data)
} catch (error) {
console.error('Failed to fetch tags:', error)
alert(t('mihomo.error.fetchTagsFailed'))
} finally {
setLoadingTags(false)
}
}
// 安装特定版本的核心
const installSpecificCore = async () => {
if (!selectedTag) return
setInstalling(true)
try {
// 下载并安装特定版本的核心
await installSpecificMihomoCore(selectedTag)
// 更新应用配置
await patchAppConfig({
core: 'mihomo-specific',
specificVersion: selectedTag
})
// 重启核心
await restartCore()
// 关闭模态框
onClose()
// 通知用户
new Notification(t('mihomo.coreUpgradeSuccess'))
} catch (error) {
console.error('Failed to install specific core:', error)
alert(t('mihomo.error.installCoreFailed'))
} finally {
setInstalling(false)
}
}
// 刷新标签列表
const refreshTags = async () => {
setRefreshing(true)
try {
// 清除缓存并强制刷新
await clearMihomoVersionCache()
await fetchTags(true)
} finally {
setRefreshing(false)
}
}
// 打开模态框时获取标签
const handleOpenModal = async () => {
onOpen()
// 先显示缓存的标签(如果有)
if (tags.length === 0) {
await fetchTags(false) // 使用缓存
}
// 在后台检查更新
setTimeout(() => {
fetchTags(true) // 强制刷新
}, 100)
}
// 过滤标签
const filteredTags = tags.filter(tag =>
tag.name.toLowerCase().includes(searchTerm.toLowerCase())
)
// 当模态框打开时,确保选中当前版本
useEffect(() => {
if (isOpen && specificVersion) {
setSelectedTag(specificVersion)
}
}, [isOpen, specificVersion])
return (
<>
{lanOpen && <InterfaceModal onClose={() => setLanOpen(false)} />}
@ -162,39 +257,57 @@ const Mihomo: React.FC = () => {
)}
<SettingItem
title={t('mihomo.coreVersion')}
title={
<div className="flex items-center gap-2">
<span>{t('mihomo.coreVersion')}</span>
{core === 'mihomo-specific' && specificVersion && (
<Chip size="sm" variant="flat" color="primary">
{specificVersion}
</Chip>
)}
</div>
}
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')
})
<div className="flex gap-2">
<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)
}
} 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>
}}
>
<IoMdCloudDownload className="text-lg" />
</Button>
<Button
size="sm"
variant="light"
onPress={handleOpenModal}
>
{t('mihomo.selectSpecificVersion')}
</Button>
</div>
}
divider={enableSmartCore && core === 'mihomo-smart'}
>
@ -204,27 +317,29 @@ const Mihomo: React.FC = () => {
? 'data-[hover=true]:bg-blue-100 dark:data-[hover=true]:bg-blue-900/50'
: 'data-[hover=true]:bg-default-200'
}}
className="w-[100px]"
className="w-[150px]"
size="sm"
aria-label={t('mihomo.selectCoreVersion')}
selectedKeys={new Set([
enableSmartCore
? 'mihomo-smart'
: (core === 'mihomo-smart' ? 'mihomo' : core)
core
])}
disallowEmptySelection={true}
onSelectionChange={async (v) => {
handleConfigChangeWithRestart('core', v.currentKey as 'mihomo' | 'mihomo-alpha' | 'mihomo-smart')
const selectedCore = v.currentKey as 'mihomo' | 'mihomo-alpha' | 'mihomo-smart' | 'mihomo-specific'
// 如果切换到特定版本但没有设置specificVersion则打开选择模态框
if (selectedCore === 'mihomo-specific' && !specificVersion) {
handleOpenModal()
} else {
handleConfigChangeWithRestart('core', selectedCore)
}
}}
>
<SelectItem key="mihomo">{t(CoreMap['mihomo'])}</SelectItem>
<SelectItem key="mihomo-alpha">{t(CoreMap['mihomo-alpha'])}</SelectItem>
{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>
</>
)}
) : null}
<SelectItem key="mihomo-specific">{t(CoreMap['mihomo-specific'])}</SelectItem>
</Select>
</SettingItem>
@ -899,6 +1014,82 @@ const Mihomo: React.FC = () => {
</SettingItem>
</SettingCard>
</BasePage>
{/* 自定义版本选择模态框 */}
<Modal
isOpen={isOpen}
onClose={onClose}
size="5xl"
backdrop="blur"
classNames={{ backdrop: 'top-[48px]' }}
hideCloseButton
scrollBehavior="inside"
>
<ModalContent className="h-full w-[calc(100%-100px)]">
<ModalHeader className="flex app-drag">{t('mihomo.selectSpecificVersion')}</ModalHeader>
<ModalBody>
<div className="flex flex-col gap-4">
<div className="flex gap-2">
<Input
placeholder={t('mihomo.searchVersion')}
value={searchTerm}
onValueChange={setSearchTerm}
className="flex-1"
/>
<Button
isIconOnly
variant="light"
onPress={refreshTags}
isLoading={refreshing}
title={t('common.refresh')}
>
<IoMdRefresh className="text-lg" />
</Button>
</div>
{loadingTags ? (
<div className="flex justify-center items-center h-40">
<Spinner size="lg" />
</div>
) : (
<div className="h-full overflow-y-auto">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{filteredTags.map((tag) => (
<div
key={tag.name}
className={`p-3 rounded-lg cursor-pointer transition-colors ${
selectedTag === tag.name
? 'bg-primary/20 border-2 border-primary'
: 'bg-default-100 hover:bg-default-200'
}`}
onClick={() => setSelectedTag(tag.name)}
>
<div className="font-medium">{tag.name}</div>
</div>
))}
</div>
{filteredTags.length === 0 && (
<div className="text-center py-8 text-default-500">
{t('mihomo.noVersionsFound')}
</div>
)}
</div>
)}
</div>
</ModalBody>
<ModalFooter>
<Button variant="light" onPress={onClose}>
{t('common.cancel')}
</Button>
<Button
color="primary"
isLoading={installing}
isDisabled={!selectedTag || installing}
onPress={installSpecificCore}
>
{t('mihomo.installVersion')}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
)
}

View File

@ -9,6 +9,19 @@ function ipcErrorWrapper(response: any): any {
}
}
// GitHub版本管理相关IPC调用
export async function fetchMihomoTags(forceRefresh = false): Promise<{name: string, zipball_url: string, tarball_url: string}[]> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('fetchMihomoTags', forceRefresh))
}
export async function installSpecificMihomoCore(version: string): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('installSpecificMihomoCore', version))
}
export async function clearMihomoVersionCache(): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('clearMihomoVersionCache'))
}
export async function mihomoVersion(): Promise<IMihomoVersion> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('mihomoVersion'))
}
@ -231,8 +244,6 @@ export async function triggerSysProxy(enable: boolean): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('triggerSysProxy', enable))
}
export async function checkTunPermissions(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkTunPermissions'))
}
@ -519,4 +530,4 @@ async function alert<T>(msg: T): Promise<void> {
return await window.electron.ipcRenderer.invoke('alert', msgStr)
}
window.alert = alert
window.alert = alert

View File

@ -216,7 +216,8 @@ interface ISysProxyConfig {
}
interface IAppConfig {
core: 'mihomo' | 'mihomo-alpha' | 'mihomo-smart'
core: 'mihomo' | 'mihomo-alpha' | 'mihomo-smart' | 'mihomo-specific'
specificVersion?: string
enableSmartCore: boolean
enableSmartOverride: boolean
smartCoreUseLightGBM: boolean