webui: add model information dialog to model selector in router mode

This commit is contained in:
maddes8cht 2026-03-15 03:27:00 +01:00
parent d23355afc3
commit 2d0566d38f
3 changed files with 49 additions and 27 deletions

View File

@ -2,32 +2,41 @@
import * as Dialog from '$lib/components/ui/dialog';
import * as Table from '$lib/components/ui/table';
import { BadgeModality, ActionIconCopyToClipboard } from '$lib/components/app';
import { serverStore } from '$lib/stores/server.svelte';
import { modelsStore, modelOptions, modelsLoading } from '$lib/stores/models.svelte';
import { formatFileSize, formatParameters, formatNumber } from '$lib/utils';
interface Props {
open?: boolean;
onOpenChange?: (open: boolean) => void;
modelId?: string | null; // Neue Prop für das gewählte Modell
}
let { open = $bindable(), onOpenChange }: Props = $props();
let { open = $bindable(), onOpenChange, modelId = null }: Props = $props();
let serverProps = $derived(serverStore.props);
let modelName = $derived(modelsStore.singleModelName);
let models = $derived(modelOptions());
let isLoadingModels = $derived(modelsLoading());
// Get the first model for single-model mode display
let firstModel = $derived(models[0] ?? null);
// Get modalities from modelStore using the model ID from the first model
let modalities = $derived.by(() => {
if (!firstModel?.id) return [];
return modelsStore.getModelModalitiesArray(firstModel.id);
// Finde das spezifische Modell anhand der ID oder nimm das erste als Fallback
let selectedModel = $derived.by(() => {
if (modelId) {
return models.find(m => m.id === modelId) || models[0];
}
return models[0] ?? null;
});
// Hol die spezifischen Server-Props für dieses Modell aus dem modelStore
let serverProps = $derived.by(() => {
if (!selectedModel) return null;
return modelsStore.getModelProps(selectedModel.model);
});
let modelName = $derived(selectedModel?.model ?? '');
let modalities = $derived.by(() => {
if (!selectedModel?.id) return [];
return modelsStore.getModelModalitiesArray(selectedModel.id);
});
// Ensure models are fetched when dialog opens
$effect(() => {
if (open && models.length === 0) {
modelsStore.fetch();
@ -56,8 +65,8 @@
<div class="flex items-center justify-center py-8">
<div class="text-sm text-muted-foreground">Loading model information...</div>
</div>
{:else if firstModel}
{@const modelMeta = firstModel.meta}
{:else if selectedModel}
{@const modelMeta = selectedModel.meta}
{#if serverProps}
<Table.Root>

View File

@ -53,6 +53,7 @@
let activeId = $derived(selectedModelId());
let isRouter = $derived(isRouterMode());
let serverModel = $derived(singleModelName());
let infoModelId = $state<string | null>(null);
let isHighlightedCurrentModelActive = $derived.by(() => {
if (!isRouter || !currentModel) return false;
@ -95,7 +96,6 @@
items: { option: ModelOption; flatIndex: number }[];
}[] = [];
// Loaded models group (top)
const loadedItems: { option: ModelOption; flatIndex: number }[] = [];
for (let i = 0; i < filteredOptions.length; i++) {
if (modelsStore.isModelLoaded(filteredOptions[i].model)) {
@ -112,7 +112,6 @@
});
}
// Favourites group
const loadedModelIds = new Set(loadedItems.map((item) => item.option.model));
const favItems: { option: ModelOption; flatIndex: number }[] = [];
for (let i = 0; i < filteredOptions.length; i++) {
@ -130,7 +129,6 @@
});
}
// Org groups (excluding loaded and favourites)
const orgGroups = new SvelteMap<string, { option: ModelOption; flatIndex: number }[]>();
for (let i = 0; i < filteredOptions.length; i++) {
const option = filteredOptions[i];
@ -399,7 +397,6 @@
>
<div class="models-list">
{#if !isCurrentModelInCache && currentModel}
<!-- Show unavailable model as first option (disabled) -->
<button
type="button"
class="flex w-full cursor-not-allowed items-center bg-red-400/10 p-2 text-left text-sm text-red-400"
@ -454,6 +451,11 @@
handleSelect(option.id);
}
}}
onShowInfo={async () => {
infoModelId = option.id; // ID merken
handleOpenChange(false);
showModelDialog = true;
}}
/>
{/each}
{/each}
@ -500,6 +502,9 @@
{/if}
</div>
{#if showModelDialog && !isRouter}
<DialogModelInformation bind:open={showModelDialog} />
{#if showModelDialog}
<DialogModelInformation
bind:open={showModelDialog}
modelId={infoModelId}
/>
{/if}

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { CircleAlert, Heart, HeartOff, Loader2, Power, PowerOff, RotateCw } from '@lucide/svelte';
import { CircleAlert, Heart, HeartOff, Loader2, Power, PowerOff, RotateCw, BadgeInfo } from '@lucide/svelte';
import { cn } from '$lib/components/ui/utils';
import { ActionIcon, ModelId } from '$lib/components/app';
import type { ModelOption } from '$lib/types/models';
@ -15,6 +15,7 @@
onSelect: (modelId: string) => void;
onMouseEnter: () => void;
onKeyDown: (e: KeyboardEvent) => void;
onShowInfo: () => void;
}
let {
@ -25,7 +26,8 @@
showOrgName = false,
onSelect,
onMouseEnter,
onKeyDown
onKeyDown,
onShowInfo
}: Props = $props();
let currentRouterModels = $derived(routerModels());
@ -33,6 +35,7 @@
const model = currentRouterModels.find((m) => m.id === option.model);
return (model?.status?.value as ServerModelStatus) ?? null;
});
let isOperationInProgress = $derived(modelsStore.isModelOperationInProgress(option.model));
let isFailed = $derived(serverStatus === ServerModelStatus.FAILED);
let isLoaded = $derived(serverStatus === ServerModelStatus.LOADED && !isOperationInProgress);
@ -107,12 +110,17 @@
</div>
</div>
{:else if isLoaded}
<div class="flex w-4 items-center justify-center">
<span class="h-2 w-2 rounded-full bg-green-500 group-hover:hidden"></span>
<div class="flex w-fit items-center justify-center gap-1"> <span class="h-2 w-2 rounded-full bg-green-500 group-hover:hidden"></span>
<div class="hidden group-hover:flex items-center gap-1" onclick={(e) => e.stopPropagation()}>
<ActionIcon
iconSize="h-2.5 w-2.5"
icon={BadgeInfo}
tooltip="Model information"
class="h-3 w-3 hover:text-primary"
onclick={() => onShowInfo()}
/>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="hidden group-hover:flex" onclick={(e) => e.stopPropagation()}>
<ActionIcon
iconSize="h-2.5 w-2.5"
icon={PowerOff}