Add session-level bridge model settings (#811)

This commit is contained in:
ekko
2026-05-17 12:20:53 +08:00
committed by GitHub
parent fa035f348e
commit 5e8f8bd4a1
35 changed files with 697 additions and 60 deletions
+24 -8
View File
@@ -17,6 +17,7 @@ import { hasApiKey } from '@/api/client'
const WEB_UI_VERSION = __APP_VERSION__
const SIDEBAR_COLLAPSED_KEY = 'hermes_sidebar_collapsed'
const MODELS_CACHE_TTL_MS = 30000
export const useAppStore = defineStore('app', () => {
const sidebarOpen = ref(false)
@@ -42,6 +43,8 @@ export const useAppStore = defineStore('app', () => {
const streamEnabled = ref(true)
const sessionPersistence = ref(true)
const maxTokens = ref(4096)
let modelsLoadPromise: Promise<void> | null = null
let modelsLoadedAt = 0
async function doUpdate(): Promise<boolean> {
updating.value = true
@@ -128,14 +131,26 @@ export const useAppStore = defineStore('app', () => {
}
}
async function loadModels() {
async function loadModels(force = false) {
if (!hasApiKey()) return
try {
const res = await fetchAvailableModels()
applyAvailableModelsResponse(res)
} catch {
// ignore
}
if (!force && modelsLoadPromise) return modelsLoadPromise
if (!force && modelGroups.value.length > 0 && Date.now() - modelsLoadedAt < MODELS_CACHE_TTL_MS) return
modelsLoadPromise = (async () => {
try {
const res = await fetchAvailableModels()
applyAvailableModelsResponse(res)
modelsLoadedAt = Date.now()
} catch {
// ignore
} finally {
modelsLoadPromise = null
}
})()
return modelsLoadPromise
}
async function reloadModels() {
return loadModels(true)
}
function getModelAlias(modelId: string, provider?: string): string {
@@ -220,7 +235,7 @@ export const useAppStore = defineStore('app', () => {
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()
await reloadModels()
}
function startHealthPolling(interval = 30000) {
@@ -285,6 +300,7 @@ export const useAppStore = defineStore('app', () => {
maxTokens,
checkConnection,
loadModels,
reloadModels,
applyAvailableModelsResponse,
switchModel,
removeCustomModel,
+27 -12
View File
@@ -1,5 +1,5 @@
import { startRunViaSocket, resumeSession, registerSessionHandlers, unregisterSessionHandlers, getChatRunSocket, respondToolApproval, type RunEvent, type ContentBlock as ContentBlockImport } from '@/api/hermes/chat'
import { deleteSession as deleteSessionApi, fetchSession, fetchSessions, type HermesMessage, type SessionSummary } from '@/api/hermes/sessions'
import { deleteSession as deleteSessionApi, fetchSession, fetchSessions, setSessionModel, type HermesMessage, type SessionSummary } from '@/api/hermes/sessions'
import { getApiKey } from '@/api/client'
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
@@ -238,7 +238,7 @@ function mapHermesSession(s: SessionSummary): Session {
createdAt: Math.round(s.started_at * 1000),
updatedAt: Math.round((s.last_active || s.ended_at || s.started_at) * 1000),
model: s.model,
provider: (s as any).billing_provider || '',
provider: s.provider || (s as any).billing_provider || '',
messageCount: s.message_count,
endedAt: s.ended_at != null ? Math.round(s.ended_at * 1000) : null,
lastActiveAt: s.last_active != null ? Math.round(s.last_active * 1000) : undefined,
@@ -611,18 +611,25 @@ export const useChatStore = defineStore('chat', () => {
// Inherit current global model
const appStore = useAppStore()
session.model = appStore.selectedModel || undefined
session.provider = appStore.selectedProvider || ''
switchSession(session.id)
}
async function switchSessionModel(modelId: string, provider?: string) {
if (!activeSession.value) return
activeSession.value.model = modelId
activeSession.value.provider = provider || ''
// If provider changed, update global config too (Hermes requires it)
if (provider) {
const { useAppStore } = await import('./app')
await useAppStore().switchModel(modelId, provider)
async function switchSessionModel(modelId: string, provider?: string, sessionId?: string): Promise<boolean> {
const targetId = sessionId || activeSession.value?.id
if (!targetId) return false
const ok = await setSessionModel(targetId, modelId, provider || '')
if (!ok) return false
const target = sessions.value.find(s => s.id === targetId)
if (target) {
target.model = modelId
target.provider = provider || ''
}
if (activeSession.value?.id === targetId) {
activeSession.value.model = modelId
activeSession.value.provider = provider || ''
}
return true
}
async function deleteSession(sessionId: string) {
@@ -903,13 +910,21 @@ export const useChatStore = defineStore('chat', () => {
}
const appStore = useAppStore()
await appStore.loadModels()
const sessionModel = activeSession.value?.model || appStore.selectedModel
const isBridgeSource = activeSession.value?.source === 'cli'
const sessionProvider = activeSession.value?.provider || appStore.selectedProvider
const runPayload = {
input,
session_id: sid,
model: sessionModel || undefined,
model: isBridgeSource ? undefined : sessionModel || undefined,
provider: isBridgeSource ? undefined : sessionProvider || undefined,
model_groups: appStore.modelGroups.map(group => ({
provider: group.provider,
models: group.models,
})),
queue_id: userMsg.id,
source: (activeSession.value?.source === 'cli' ? 'cli' : 'api_server') as 'cli' | 'api_server',
source: (isBridgeSource ? 'cli' : 'api_server') as 'cli' | 'api_server',
}
if (shouldQueue) {
+1 -5
View File
@@ -52,21 +52,17 @@ export const useModelsStore = defineStore('models', () => {
await systemApi.updateDefaultModel({ default: modelId, provider })
defaultModel.value = modelId
const appStore = useAppStore()
appStore.loadModels()
appStore.reloadModels()
}
async function addProvider(data: CustomProvider) {
await systemApi.addCustomProvider(data)
await fetchProviders()
const appStore = useAppStore()
appStore.loadModels()
}
async function removeProvider(name: string) {
await systemApi.removeCustomProvider(name)
await fetchProviders()
const appStore = useAppStore()
appStore.loadModels()
}
return {