2026-04-11 15:59:14 +08:00
|
|
|
import { defineStore } from 'pinia'
|
|
|
|
|
import { ref } from 'vue'
|
2026-04-24 23:30:34 +08:00
|
|
|
import { checkHealth, fetchAvailableModels, updateDefaultModel, triggerUpdate, type AvailableModelGroup } from '@/api/hermes/system'
|
2026-04-16 13:51:42 +08:00
|
|
|
|
|
|
|
|
const WEB_UI_VERSION = __APP_VERSION__
|
2026-04-11 15:59:14 +08:00
|
|
|
|
2026-04-18 00:00:24 +08:00
|
|
|
const SIDEBAR_COLLAPSED_KEY = 'hermes_sidebar_collapsed'
|
|
|
|
|
|
2026-04-11 15:59:14 +08:00
|
|
|
export const useAppStore = defineStore('app', () => {
|
2026-04-15 09:12:54 +08:00
|
|
|
const sidebarOpen = ref(false)
|
2026-04-18 00:00:24 +08:00
|
|
|
// Desktop-only collapsed state (icon-rail mode). Persisted to localStorage.
|
|
|
|
|
const sidebarCollapsed = ref(localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === '1')
|
2026-04-15 09:12:54 +08:00
|
|
|
|
2026-04-11 15:59:14 +08:00
|
|
|
const connected = ref(false)
|
2026-04-16 13:51:42 +08:00
|
|
|
const serverVersion = ref(WEB_UI_VERSION)
|
|
|
|
|
const latestVersion = ref('')
|
|
|
|
|
const updateAvailable = ref(false)
|
|
|
|
|
const updating = ref(false)
|
2026-04-12 23:23:50 +08:00
|
|
|
const modelGroups = ref<AvailableModelGroup[]>([])
|
|
|
|
|
const selectedModel = ref('')
|
2026-04-19 15:05:05 +08:00
|
|
|
const selectedProvider = ref('')
|
2026-04-24 08:49:45 +08:00
|
|
|
const customModels = ref<Record<string, string[]>>({})
|
2026-04-11 15:59:14 +08:00
|
|
|
const healthPollTimer = ref<ReturnType<typeof setInterval>>()
|
2026-04-23 12:57:42 +08:00
|
|
|
const nodeVersion = ref('')
|
2026-04-11 15:59:14 +08:00
|
|
|
|
|
|
|
|
// Settings
|
|
|
|
|
const streamEnabled = ref(true)
|
|
|
|
|
const sessionPersistence = ref(true)
|
|
|
|
|
const maxTokens = ref(4096)
|
|
|
|
|
|
2026-04-16 13:51:42 +08:00
|
|
|
async function doUpdate(): Promise<boolean> {
|
|
|
|
|
updating.value = true
|
|
|
|
|
try {
|
|
|
|
|
const res = await triggerUpdate()
|
|
|
|
|
if (res.success) {
|
|
|
|
|
updateAvailable.value = false
|
|
|
|
|
await checkConnection()
|
|
|
|
|
}
|
|
|
|
|
return res.success
|
|
|
|
|
} finally {
|
|
|
|
|
updating.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 15:59:14 +08:00
|
|
|
async function checkConnection() {
|
|
|
|
|
try {
|
|
|
|
|
const res = await checkHealth()
|
2026-04-13 20:08:32 +08:00
|
|
|
connected.value = res.status === 'ok'
|
2026-04-16 13:51:42 +08:00
|
|
|
if (res.webui_version) serverVersion.value = res.webui_version
|
|
|
|
|
if (res.webui_latest) latestVersion.value = res.webui_latest
|
|
|
|
|
updateAvailable.value = !!res.webui_update_available
|
2026-04-23 12:57:42 +08:00
|
|
|
if (res.node_version) nodeVersion.value = res.node_version
|
2026-04-11 15:59:14 +08:00
|
|
|
} catch {
|
|
|
|
|
connected.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadModels() {
|
|
|
|
|
try {
|
2026-04-24 23:30:34 +08:00
|
|
|
const res = await fetchAvailableModels()
|
|
|
|
|
modelGroups.value = res.groups
|
2026-04-12 23:23:50 +08:00
|
|
|
selectedModel.value = res.default
|
2026-04-24 23:30:34 +08:00
|
|
|
selectedProvider.value = res.default_provider || ''
|
2026-04-11 15:59:14 +08:00
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 23:23:50 +08:00
|
|
|
async function switchModel(modelId: string, providerOverride?: string) {
|
|
|
|
|
try {
|
|
|
|
|
// Find the group containing this model to get provider info
|
|
|
|
|
const group = modelGroups.value.find(g => g.models.includes(modelId))
|
|
|
|
|
const provider = providerOverride || group?.provider || ''
|
|
|
|
|
await updateDefaultModel({ default: modelId, provider })
|
|
|
|
|
selectedModel.value = modelId
|
2026-04-19 15:05:05 +08:00
|
|
|
selectedProvider.value = provider || ''
|
2026-04-24 08:49:45 +08:00
|
|
|
// Track as custom if not already in the server-fetched list
|
|
|
|
|
if (provider && !modelGroups.value.find(g => g.provider === provider)?.models.includes(modelId)) {
|
|
|
|
|
if (!customModels.value[provider]) customModels.value[provider] = []
|
|
|
|
|
if (!customModels.value[provider].includes(modelId)) {
|
|
|
|
|
customModels.value[provider] = [...customModels.value[provider], modelId]
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-12 23:23:50 +08:00
|
|
|
} catch (err: any) {
|
|
|
|
|
console.error('Failed to switch model:', err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 15:59:14 +08:00
|
|
|
function startHealthPolling(interval = 30000) {
|
|
|
|
|
stopHealthPolling()
|
|
|
|
|
checkConnection()
|
|
|
|
|
healthPollTimer.value = setInterval(checkConnection, interval)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function stopHealthPolling() {
|
|
|
|
|
if (healthPollTimer.value) {
|
|
|
|
|
clearInterval(healthPollTimer.value)
|
|
|
|
|
healthPollTimer.value = undefined
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 09:12:54 +08:00
|
|
|
function toggleSidebar() {
|
|
|
|
|
sidebarOpen.value = !sidebarOpen.value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function closeSidebar() {
|
|
|
|
|
sidebarOpen.value = false
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 00:00:24 +08:00
|
|
|
function toggleSidebarCollapsed() {
|
|
|
|
|
sidebarCollapsed.value = !sidebarCollapsed.value
|
|
|
|
|
try {
|
|
|
|
|
localStorage.setItem(SIDEBAR_COLLAPSED_KEY, sidebarCollapsed.value ? '1' : '0')
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore quota errors — fallback to in-memory only
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 15:59:14 +08:00
|
|
|
return {
|
2026-04-15 09:12:54 +08:00
|
|
|
sidebarOpen,
|
2026-04-18 00:00:24 +08:00
|
|
|
sidebarCollapsed,
|
2026-04-15 09:12:54 +08:00
|
|
|
toggleSidebar,
|
|
|
|
|
closeSidebar,
|
2026-04-18 00:00:24 +08:00
|
|
|
toggleSidebarCollapsed,
|
2026-04-11 15:59:14 +08:00
|
|
|
connected,
|
|
|
|
|
serverVersion,
|
2026-04-16 13:51:42 +08:00
|
|
|
latestVersion,
|
2026-04-23 12:57:42 +08:00
|
|
|
nodeVersion,
|
2026-04-16 13:51:42 +08:00
|
|
|
updateAvailable,
|
|
|
|
|
updating,
|
|
|
|
|
doUpdate,
|
2026-04-12 23:23:50 +08:00
|
|
|
modelGroups,
|
2026-04-24 08:49:45 +08:00
|
|
|
customModels,
|
2026-04-12 23:23:50 +08:00
|
|
|
selectedModel,
|
2026-04-19 15:05:05 +08:00
|
|
|
selectedProvider,
|
2026-04-11 15:59:14 +08:00
|
|
|
streamEnabled,
|
|
|
|
|
sessionPersistence,
|
|
|
|
|
maxTokens,
|
|
|
|
|
checkConnection,
|
|
|
|
|
loadModels,
|
2026-04-12 23:23:50 +08:00
|
|
|
switchModel,
|
2026-04-11 15:59:14 +08:00
|
|
|
startHealthPolling,
|
|
|
|
|
stopHealthPolling,
|
|
|
|
|
}
|
|
|
|
|
})
|