fix: apply pending correctness fixes

Cherry-pick the still-relevant fixes from recent draft PRs without pulling in stale release/docs changes:

- serialize dashboard data loads to avoid concurrent config self-heal writes
- preserve valid per-model default blocks during dashboard model self-heal
- pass structured humanizeError results directly to toast for model import scan failures
- align frontend kernel isLatest with suffix-aware recommended version ordering

Verification:
- node --test tests/*.test.js
- npm run build
This commit is contained in:
晴天
2026-05-21 19:06:38 +08:00
parent dbdf239430
commit 9d2dc8438e
5 changed files with 86 additions and 16 deletions

View File

@@ -60,6 +60,48 @@ export function versionGte(a, b) {
return true
}
function baseVersionStr(v) {
const s = String(v || '')
const i = s.indexOf('-')
return i === -1 ? s : s.slice(0, i)
}
function hasVersionSuffix(v) {
return String(v || '').includes('-')
}
function allNumericParts(ver) {
return String(ver || '')
.split(/[^\d]+/)
.filter(Boolean)
.map(n => parseInt(n, 10))
.filter(n => !Number.isNaN(n))
}
function compareLex(a, b) {
if (!a?.length || !b?.length) return 0
const len = Math.max(a.length, b.length)
for (let i = 0; i < len; i++) {
const ai = a[i] || 0
const bi = b[i] || 0
if (ai < bi) return -1
if (ai > bi) return 1
}
return 0
}
export function recommendedIsNewer(recommended, current) {
const r = parseVersion(baseVersionStr(recommended))
const c = parseVersion(baseVersionStr(current))
if (!r || !c) return false
const baseCmp = compareLex(r, c)
if (baseCmp !== 0) return baseCmp > 0
if (hasVersionSuffix(recommended) && hasVersionSuffix(current)) {
return compareLex(allNumericParts(recommended), allNumericParts(current)) > 0
}
return false
}
/**
* 检测版本是否属于汉化版
*/
@@ -100,7 +142,7 @@ export function buildSnapshot(engineId, version) {
target,
floor,
aboveFloor: !!version && versionGte(version, floor),
isLatest: !!version && !!target && versionGte(version, target),
isLatest: !!version && !!target && !recommendedIsNewer(target, version),
features,
versionLabel: version
? `${versionBase}${variant === 'chinese' ? ' 汉化' : ''}`

View File

@@ -13,7 +13,7 @@ import { attachCliConflictBanner } from '../components/cli-conflict-banner.js'
import { icon } from '../lib/icons.js'
let _unsubGw = null
let _loadInFlight = false
let _dashboardLoadChain = Promise.resolve()
let _lastGwChangeLoad = 0
let _detachCliConflict = null
@@ -193,16 +193,20 @@ function normalizeDefaultModelConfig(config) {
for (const fallback of modelConfig.fallbacks) {
nextMap[fallback] = currentMap[fallback] && typeof currentMap[fallback] === 'object' && !Array.isArray(currentMap[fallback]) ? currentMap[fallback] : {}
}
for (const [key, value] of Object.entries(currentMap)) {
if (validModels.has(key) && !nextMap[key]) {
nextMap[key] = value && typeof value === 'object' && !Array.isArray(value) ? value : {}
}
}
config.agents.defaults.models = nextMap
return modelConfig.primary
}
async function loadDashboardData(page, fullRefresh = false) {
// 并发保护如果上一次加载仍在进行跳过本次fullRefresh 除外)
if (_loadInFlight && !fullRefresh) return
const loadSeq = ++_dashboardLoadSeq
_loadInFlight = true
try { await _loadDashboardDataInner(page, fullRefresh, loadSeq) } finally { if (loadSeq === _dashboardLoadSeq) _loadInFlight = false }
_dashboardLoadChain = _dashboardLoadChain.catch(() => {}).then(() => _loadDashboardDataInner(page, fullRefresh, loadSeq))
return _dashboardLoadChain
}
async function _loadDashboardDataInner(page, fullRefresh, loadSeq) {

View File

@@ -1308,7 +1308,7 @@ async function importClientConfigs(page, state) {
const result = await api.scanModelClientConfigs()
candidates = Array.isArray(result?.candidates) ? result.candidates : []
} catch (e) {
toast(`${t('models.importScanFailed')}: ${humanizeError(e)}`, 'error')
toast(humanizeError(e, t('models.importScanFailed')), 'error')
return
} finally {
if (btn) { btn.disabled = false; btn.textContent = oldText || t('models.importClientConfigs') }