fix: apply Smart core settings on hot reload and stop re-downloading the model

* fix: apply Smart core settings on hot reload

The Smart core switches (LightGBM, data collection, strategy, collector
size) are delivered to the kernel through a generated global override
script, but that script was only regenerated inside prepareCore(). The
settings page persists the change and then hot reloads, so the reload
re-applied the previous script and the new values silently did not reach
the kernel until the core happened to be restarted.

Regenerate the override before generating the profile on hot reload, and
skip the write when the generated content is unchanged so routine hot
reloads no longer churn the override file.

* fix: reuse the downloaded Smart model when checking a profile

The Smart core downloads Model.bin when it is missing from its working
directory. Profile checks run the core with `-d <test dir>`, which never
receives the model, so every check starts a fresh download.

That download runs after the previous core has already been stopped and
the system proxy torn down, so it times out instead of completing, and a
failed download leaves nothing behind - the next restart repeats it. On a
restart here it cost 20s, against 0.5s once the model is in place.

Copy the model over from the working directory before running the check,
mirroring how the geo databases are already shared with the test dir.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MOMO0302-02
2026-08-31 02:04:14 -07:00
committed by GitHub
parent 911e090537
commit 3b296bf54b
4 changed files with 48 additions and 2 deletions

View File

@@ -1,6 +1,6 @@
import { overrideLogger } from '../utils/logger'
import { getAppConfig } from './app'
import { addOverrideItem, removeOverrideItem, getOverrideItem } from './override'
import { addOverrideItem, removeOverrideItem, getOverrideItem, getOverride } from './override'
const SMART_OVERRIDE_ID = 'smart-core-override'
@@ -377,6 +377,13 @@ export async function createSmartOverride(): Promise<void> {
smartCollectorSize
)
// 热重载每次生效前都会重新生成覆写,内容未变时跳过写盘,避免无谓的时间戳刷新
const existing = await getOverrideItem(SMART_OVERRIDE_ID)
if (existing) {
const current = await getOverride(SMART_OVERRIDE_ID, 'js')
if (current === template) return
}
await addOverrideItem({
id: SMART_OVERRIDE_ID,
name: 'Smart Core Override',

View File

@@ -46,6 +46,7 @@ import {
getAxios
} from './mihomoApi'
import { generateProfile } from './factory'
import { syncSmartModelToTestDir } from './smartModel'
import {
checkAdminRestartForTun as checkAdminRestartForTunWithRestart,
getSessionAdminStatus,
@@ -967,6 +968,7 @@ export async function checkProfileConfig(
ageSecretKey?: string
): Promise<void> {
const corePath = mihomoCorePath(core)
await syncSmartModelToTestDir()
try {
await execFilePromise(corePath, ['-t', '-f', configPath, '-d', mihomoTestDir()], {

View File

@@ -2,7 +2,7 @@ import { createConnection } from 'net'
import axios, { AxiosInstance } from 'axios'
import WebSocket from 'ws'
import { app } from 'electron'
import { getAppConfig, getControledMihomoConfig } from '../config'
import { getAppConfig, getControledMihomoConfig, manageSmartOverride } from '../config'
import { mainWindow } from '../window'
import { tray } from '../resolve/tray'
import { calcTraffic } from '../utils/calc'
@@ -434,6 +434,9 @@ export const mihomoHotReloadConfig = async (): Promise<void> => {
await restartCore()
return
}
// Smart 覆写脚本由应用配置生成,必须先同步再生成配置,
// 否则界面上改动的 Smart 选项会沿用旧脚本,要等到下次重启内核才生效
await manageSmartOverride()
const current = await generateProfile()
const { diffWorkDir = false } = await getAppConfig()
const configPath = diffWorkDir ? mihomoWorkConfigPath(current) : mihomoWorkConfigPath('work')

View File

@@ -0,0 +1,34 @@
import { copyFile, stat } from 'fs/promises'
import { existsSync } from 'fs'
import path from 'path'
import { mihomoWorkDir, mihomoTestDir } from '../utils/dirs'
import { managerLogger } from '../utils/logger'
const MODEL_FILE = 'Model.bin'
async function isSourceNewer(sourcePath: string, targetPath: string): Promise<boolean> {
try {
const [sourceStats, targetStats] = await Promise.all([stat(sourcePath), stat(targetPath)])
return sourceStats.mtime > targetStats.mtime
} catch {
return true
}
}
// Smart 内核在工作目录找不到 Model.bin 时会联网下载模型。而配置检查mihomo -t跑在独立的
// test 目录里,该目录从不包含模型;这次检查又发生在旧内核已经停止、系统代理已经撤下之后,
// 于是下载必然超时失败,每次重启白白多等约 20 秒,且失败不留文件,下次重启重演一遍。
// 正式工作目录已有模型时先同步过去,跳过这次注定失败的下载。
export async function syncSmartModelToTestDir(): Promise<void> {
const source = path.join(mihomoWorkDir(), MODEL_FILE)
if (!existsSync(source)) return
const target = path.join(mihomoTestDir(), MODEL_FILE)
if (existsSync(target) && !(await isSourceNewer(source, target))) return
try {
await copyFile(source, target)
} catch (error) {
managerLogger.warn('Failed to sync Model.bin into test dir', error)
}
}