feat(models): 增加模型显示名重命名 (#614)

* feat(models): add WUI model display aliases

Persist display-only model aliases in Web UI app config, surface them in the model selector/search, and keep canonical model IDs for Hermes calls.

* fix(models): improve WUI model alias editing

* fix(models): clarify unlisted model picker

* fix(models): scope aliases to providers
This commit is contained in:
Zhicheng Han
2026-05-11 16:18:13 +02:00
committed by GitHub
parent 7b781b4f42
commit b8be47d8d6
20 changed files with 898 additions and 57 deletions
+114 -6
View File
@@ -1,6 +1,17 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { checkHealth, fetchAvailableModels, updateDefaultModel, updateModelVisibility, triggerUpdate, type AvailableModelGroup, type AvailableModelsResponse, type ModelVisibility, type ModelVisibilityRule } from '@/api/hermes/system'
import {
checkHealth,
fetchAvailableModels,
updateDefaultModel,
updateModelVisibility,
triggerUpdate,
updateModelAlias,
type AvailableModelGroup,
type AvailableModelsResponse,
type ModelVisibility,
type ModelVisibilityRule,
} from '@/api/hermes/system'
const WEB_UI_VERSION = __APP_VERSION__
@@ -20,6 +31,7 @@ export const useAppStore = defineStore('app', () => {
const selectedModel = ref('')
const selectedProvider = ref('')
const customModels = ref<Record<string, string[]>>({})
const modelAliases = ref<Record<string, Record<string, string>>>({})
const modelVisibility = ref<ModelVisibility>({})
const healthPollTimer = ref<ReturnType<typeof setInterval>>()
const nodeVersion = ref('')
@@ -61,13 +73,55 @@ export const useAppStore = defineStore('app', () => {
function applyAvailableModelsResponse(res: AvailableModelsResponse) {
modelGroups.value = res.groups
modelAliases.value = res.model_aliases || {}
modelVisibility.value = res.model_visibility || {}
const defaultGroup = res.groups.find(g => g.provider === (res.default_provider || '') && g.models.includes(res.default))
const inferredGroup = res.groups.find(g => g.models.includes(res.default))
const defaultModel = res.default || ''
const defaultProvider = res.default_provider || ''
const explicitGroup = res.groups.find(g => g.provider === defaultProvider && g.models.includes(defaultModel))
const inferredGroup = res.groups.find(g => g.models.includes(defaultModel))
const fallbackGroup = res.groups.find(g => g.models.length > 0)
const selectedGroup = defaultGroup || inferredGroup || fallbackGroup
selectedModel.value = selectedGroup ? (defaultGroup || inferredGroup ? res.default : selectedGroup.models[0]) : ''
selectedProvider.value = selectedGroup?.provider || ''
const providerGroup = defaultProvider ? res.groups.find(g => g.provider === defaultProvider) : undefined
const allProvider = defaultProvider ? res.allProviders.find(g => g.provider === defaultProvider) : undefined
const providerCatalog = providerGroup?.available_models?.length
? providerGroup.available_models
: allProvider?.available_models?.length
? allProvider.available_models
: allProvider?.models || []
const visibilityRule = defaultProvider ? modelVisibility.value[defaultProvider] : undefined
const hiddenByVisibility = !!(
defaultModel &&
visibilityRule?.mode === 'include' &&
!visibilityRule.models.includes(defaultModel) &&
(providerCatalog.length === 0 || providerCatalog.includes(defaultModel))
)
const unlistedDefault = !!(
defaultModel &&
defaultProvider &&
providerGroup &&
!providerGroup.models.includes(defaultModel) &&
!hiddenByVisibility
)
if (explicitGroup || inferredGroup) {
const selectedGroup = explicitGroup || inferredGroup!
selectedModel.value = defaultModel
selectedProvider.value = selectedGroup.provider
customModels.value = {}
} else if (unlistedDefault) {
selectedModel.value = defaultModel
selectedProvider.value = defaultProvider
customModels.value = { [defaultProvider]: [defaultModel] }
} else if (fallbackGroup) {
selectedModel.value = fallbackGroup.models[0]
selectedProvider.value = fallbackGroup.provider
customModels.value = {}
} else {
selectedModel.value = ''
selectedProvider.value = ''
customModels.value = {}
}
}
async function loadModels() {
@@ -79,6 +133,34 @@ export const useAppStore = defineStore('app', () => {
}
}
function getModelAlias(modelId: string, provider?: string): string {
if (provider) return modelAliases.value[provider]?.[modelId] || ''
for (const aliases of Object.values(modelAliases.value)) {
if (aliases[modelId]) return aliases[modelId]
}
return ''
}
function displayModelName(modelId: string, provider?: string): string {
return getModelAlias(modelId, provider) || modelId
}
async function setModelAlias(modelId: string, provider: string, alias: string) {
const cleanAlias = alias.trim()
await updateModelAlias({ provider, model: modelId, alias: cleanAlias })
const next = { ...modelAliases.value }
const providerAliases = { ...(next[provider] || {}) }
if (cleanAlias) {
providerAliases[modelId] = cleanAlias
next[provider] = providerAliases
} else {
delete providerAliases[modelId]
if (Object.keys(providerAliases).length > 0) next[provider] = providerAliases
else delete next[provider]
}
modelAliases.value = next
}
async function switchModel(modelId: string, providerOverride?: string) {
try {
// Find the group containing this model to get provider info
@@ -99,6 +181,27 @@ export const useAppStore = defineStore('app', () => {
}
}
async function removeCustomModel(modelId: string, provider: string) {
const providerModels = customModels.value[provider] || []
if (!providerModels.includes(modelId)) return
const nextCustomModels = { ...customModels.value }
const remaining = providerModels.filter(m => m !== modelId)
if (remaining.length > 0) nextCustomModels[provider] = remaining
else delete nextCustomModels[provider]
customModels.value = nextCustomModels
if (selectedModel.value === modelId && selectedProvider.value === provider) {
const providerGroup = modelGroups.value.find(g => g.provider === provider && g.models.length > 0)
const fallbackGroup = providerGroup || modelGroups.value.find(g => g.models.length > 0)
if (fallbackGroup) {
await switchModel(fallbackGroup.models[0], fallbackGroup.provider)
} else {
selectedModel.value = ''
selectedProvider.value = ''
}
}
}
function getProviderVisibility(provider: string): ModelVisibilityRule {
return modelVisibility.value[provider] || { mode: 'all', models: [] }
@@ -160,6 +263,7 @@ export const useAppStore = defineStore('app', () => {
doUpdate,
modelGroups,
customModels,
modelAliases,
modelVisibility,
selectedModel,
selectedProvider,
@@ -170,6 +274,10 @@ export const useAppStore = defineStore('app', () => {
loadModels,
applyAvailableModelsResponse,
switchModel,
removeCustomModel,
getModelAlias,
displayModelName,
setModelAlias,
getProviderVisibility,
isModelVisible,
setModelVisibility,