diff --git a/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionFileAttachments.svelte b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionFileAttachments.svelte index 186786af48..b99b352b66 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionFileAttachments.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionFileAttachments.svelte @@ -4,7 +4,7 @@ import * as DropdownMenu from '$lib/components/ui/dropdown-menu'; import * as Tooltip from '$lib/components/ui/tooltip'; import { TOOLTIP_DELAY_DURATION } from '$lib/constants/tooltip-config'; - import { FILE_TYPE_ICONS } from '$lib/constants/modality-icons'; + import { FILE_TYPE_ICONS } from '$lib/constants/icons'; import { FileTypeCategory } from '$lib/enums'; interface Props { diff --git a/tools/server/webui/src/lib/components/app/misc/BadgeModality.svelte b/tools/server/webui/src/lib/components/app/misc/BadgeModality.svelte index 3610197fdd..a0d5e863c2 100644 --- a/tools/server/webui/src/lib/components/app/misc/BadgeModality.svelte +++ b/tools/server/webui/src/lib/components/app/misc/BadgeModality.svelte @@ -1,8 +1,10 @@ -{#each modalities as modality, index (index)} - {@const IconComponent = getModalityIcon(modality)} +{#each displayableModalities as modality, index (index)} + {@const IconComponent = MODALITY_ICONS[modality]} + {@const label = MODALITY_LABELS[modality]} {/if} - {getModalityLabel(modality)} + {label} {/each} diff --git a/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte b/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte index e2eecadf50..0d5fa62e6c 100644 --- a/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte +++ b/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte @@ -10,12 +10,14 @@ modelsUpdating, selectModel, selectedModelId, - modelsStore, - unloadModel + unloadModel, + routerModels, + loadingModelIds, + loadModel } from '$lib/stores/models.svelte'; + import { ServerModelStatus } from '$lib/enums'; import { isRouterMode, propsStore } from '$lib/stores/props.svelte'; import { DialogModelInformation } from '$lib/components/app'; - import { MODALITY_ICONS } from '$lib/constants/modality-icons'; import type { ModelOption } from '$lib/types/models'; interface Props { @@ -44,6 +46,22 @@ let isRouter = $derived(isRouterMode()); let serverModel = $derived(propsStore.modelName); + // Reactive router models state - needed for proper reactivity of isModelLoaded checks + let currentRouterModels = $derived(routerModels()); + let currentLoadingModelIds = $derived(loadingModelIds()); + + // Helper functions that create reactive dependencies + function checkIsModelLoaded(modelId: string): boolean { + // Access currentRouterModels to establish reactive dependency + const model = currentRouterModels.find((m) => m.name === modelId); + return model?.status?.value === ServerModelStatus.LOADED || false; + } + + function checkIsModelOperationInProgress(modelId: string): boolean { + // Access currentLoadingModelIds to establish reactive dependency + return currentLoadingModelIds.includes(modelId); + } + let isHighlightedCurrentModelActive = $derived( !isRouter || !currentModel ? false @@ -240,16 +258,28 @@ }; } - function handleSelect(modelId: string) { + async function handleSelect(modelId: string) { const option = options.find((opt) => opt.id === modelId); - if (option && onModelChange) { + if (!option) return; + + closeMenu(); + + if (onModelChange) { // If callback provided, use it (for regenerate functionality) onModelChange(option.id, option.model); - } else if (option) { - // Otherwise, just update the global selection (for form selector) - selectModel(option.id).catch(console.error); + } else { + // Update global selection + await selectModel(option.id); + } + + // Load the model if not already loaded (router mode) + if (isRouter && !checkIsModelLoaded(option.model)) { + try { + await loadModel(option.model); + } catch (error) { + console.error('Failed to load model:', error); + } } - closeMenu(); } function getDisplayOption(): ModelOption | undefined { @@ -382,10 +412,8 @@
{/if} {#each options as option (option.id)} - {@const isLoaded = modelsStore.isModelLoaded(option.model)} - {@const isUnloading = modelsStore.isModelOperationInProgress(option.model)} - {@const hasVision = option.modalities?.vision ?? false} - {@const hasAudio = option.modalities?.audio ?? false} + {@const isLoaded = checkIsModelLoaded(option.model)} + {@const isUnloading = checkIsModelOperationInProgress(option.model)} {@const isSelected = currentModel === option.model || activeId === option.id}
{option.model} - - - - - {#if isUnloading} {:else if isLoaded} {:else} - + {/if}
{/each} diff --git a/tools/server/webui/src/lib/constants/modality-icons.ts b/tools/server/webui/src/lib/constants/icons.ts similarity index 64% rename from tools/server/webui/src/lib/constants/modality-icons.ts rename to tools/server/webui/src/lib/constants/icons.ts index 81eb97a5b9..1e88ab5b3a 100644 --- a/tools/server/webui/src/lib/constants/modality-icons.ts +++ b/tools/server/webui/src/lib/constants/icons.ts @@ -7,9 +7,10 @@ import { File as FileIcon, FileText as FileTextIcon, Image as ImageIcon, - Volume2 as AudioIcon + Eye as VisionIcon, + Mic as AudioIcon } from '@lucide/svelte'; -import { FileTypeCategory } from '$lib/enums'; +import { FileTypeCategory, ModelModality } from '$lib/enums'; export const FILE_TYPE_ICONS = { [FileTypeCategory.IMAGE]: ImageIcon, @@ -20,9 +21,12 @@ export const FILE_TYPE_ICONS = { export const DEFAULT_FILE_ICON = FileIcon; -export type ModelModality = 'vision' | 'audio'; - export const MODALITY_ICONS = { - vision: ImageIcon, - audio: AudioIcon + [ModelModality.VISION]: VisionIcon, + [ModelModality.AUDIO]: AudioIcon +} as const; + +export const MODALITY_LABELS = { + [ModelModality.VISION]: 'Vision', + [ModelModality.AUDIO]: 'Audio' } as const;