Models:支持在 Web UI 里管理可见模型 (#613)

* feat(models): add WUI model visibility filter

Store provider model visibility in Web UI app config and filter the WUI model picker/model page without rewriting Hermes CLI config or canonical model IDs.

* fix(models): sync sidebar after visibility changes
This commit is contained in:
Zhicheng Han
2026-05-11 15:24:45 +02:00
committed by GitHub
parent c6fb449a19
commit 3a1893d401
12 changed files with 710 additions and 12 deletions
+35 -4
View File
@@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { checkHealth, fetchAvailableModels, updateDefaultModel, triggerUpdate, type AvailableModelGroup } from '@/api/hermes/system'
import { checkHealth, fetchAvailableModels, updateDefaultModel, updateModelVisibility, triggerUpdate, type AvailableModelGroup, type AvailableModelsResponse, type ModelVisibility, type ModelVisibilityRule } from '@/api/hermes/system'
const WEB_UI_VERSION = __APP_VERSION__
@@ -20,6 +20,7 @@ export const useAppStore = defineStore('app', () => {
const selectedModel = ref('')
const selectedProvider = ref('')
const customModels = ref<Record<string, string[]>>({})
const modelVisibility = ref<ModelVisibility>({})
const healthPollTimer = ref<ReturnType<typeof setInterval>>()
const nodeVersion = ref('')
@@ -58,12 +59,21 @@ export const useAppStore = defineStore('app', () => {
}
}
function applyAvailableModelsResponse(res: AvailableModelsResponse) {
modelGroups.value = res.groups
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 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 || ''
}
async function loadModels() {
try {
const res = await fetchAvailableModels()
modelGroups.value = res.groups
selectedModel.value = res.default
selectedProvider.value = res.default_provider || ''
applyAvailableModelsResponse(res)
} catch {
// ignore
}
@@ -89,6 +99,22 @@ export const useAppStore = defineStore('app', () => {
}
}
function getProviderVisibility(provider: string): ModelVisibilityRule {
return modelVisibility.value[provider] || { mode: 'all', models: [] }
}
function isModelVisible(provider: string, model: string): boolean {
const rule = getProviderVisibility(provider)
return rule.mode !== 'include' || rule.models.includes(model)
}
async function setModelVisibility(provider: string, rule: ModelVisibilityRule) {
const res = await updateModelVisibility({ provider, mode: rule.mode, models: rule.models })
modelVisibility.value = res.model_visibility || {}
await loadModels()
}
function startHealthPolling(interval = 30000) {
stopHealthPolling()
checkConnection()
@@ -134,6 +160,7 @@ export const useAppStore = defineStore('app', () => {
doUpdate,
modelGroups,
customModels,
modelVisibility,
selectedModel,
selectedProvider,
streamEnabled,
@@ -141,7 +168,11 @@ export const useAppStore = defineStore('app', () => {
maxTokens,
checkConnection,
loadModels,
applyAvailableModelsResponse,
switchModel,
getProviderVisibility,
isModelVisible,
setModelVisibility,
startHealthPolling,
stopHealthPolling,
}
@@ -37,6 +37,8 @@ export const useModelsStore = defineStore('models', () => {
providers.value = res.groups
allProviders.value = res.allProviders
defaultModel.value = res.default
const appStore = useAppStore()
appStore.applyAvailableModelsResponse(res)
} catch (err) {
console.error('Failed to fetch providers:', err)
} finally {