Merge 2161962ae0 into b91d7dfe5b
This commit is contained in:
commit
b066b9f822
Binary file not shown.
|
|
@ -5,21 +5,38 @@
|
|||
import { serverStore } from '$lib/stores/server.svelte';
|
||||
import { modelsStore, modelOptions, modelsLoading } from '$lib/stores/models.svelte';
|
||||
import { formatFileSize, formatParameters, formatNumber } from '$lib/utils';
|
||||
import type { ApiLlamaCppServerProps } from '$lib/types';
|
||||
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
// when set, fetch props from the child process (router mode)
|
||||
modelId?: string | null;
|
||||
}
|
||||
|
||||
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 isRouter = $derived(serverStore.isRouterMode);
|
||||
|
||||
// per-model props fetched from the child process
|
||||
let routerModelProps = $state<ApiLlamaCppServerProps | null>(null);
|
||||
let isLoadingRouterProps = $state(false);
|
||||
|
||||
// in router mode use per-model props, otherwise use global props
|
||||
let serverProps = $derived(isRouter && modelId ? routerModelProps : serverStore.props);
|
||||
|
||||
let modelName = $derived(isRouter && modelId ? modelId : 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);
|
||||
// in router mode, find the model option matching modelId
|
||||
// in single mode, use the first model as before
|
||||
let firstModel = $derived.by(() => {
|
||||
if (isRouter && modelId) {
|
||||
return models.find((m) => m.model === modelId) ?? null;
|
||||
}
|
||||
return models[0] ?? null;
|
||||
});
|
||||
|
||||
// Get modalities from modelStore using the model ID from the first model
|
||||
let modalities = $derived.by(() => {
|
||||
|
|
@ -33,6 +50,27 @@
|
|||
modelsStore.fetch();
|
||||
}
|
||||
});
|
||||
|
||||
// fetch per-model props from child process when dialog opens in router mode
|
||||
$effect(() => {
|
||||
if (open && isRouter && modelId) {
|
||||
isLoadingRouterProps = true;
|
||||
modelsStore
|
||||
.fetchModelProps(modelId)
|
||||
.then((props) => {
|
||||
routerModelProps = props;
|
||||
})
|
||||
.catch(() => {
|
||||
routerModelProps = null;
|
||||
})
|
||||
.finally(() => {
|
||||
isLoadingRouterProps = false;
|
||||
});
|
||||
}
|
||||
if (!open) {
|
||||
routerModelProps = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open {onOpenChange}>
|
||||
|
|
@ -52,7 +90,7 @@
|
|||
</Dialog.Header>
|
||||
|
||||
<div class="space-y-6 py-4">
|
||||
{#if isLoadingModels}
|
||||
{#if isLoadingModels || isLoadingRouterProps}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="text-sm text-muted-foreground">Loading model information...</div>
|
||||
</div>
|
||||
|
|
@ -212,7 +250,7 @@
|
|||
<Table.Cell class="align-middle font-medium">Chat Template</Table.Cell>
|
||||
|
||||
<Table.Cell class="py-10">
|
||||
<div class="max-h-120 overflow-y-auto rounded-md bg-muted p-4">
|
||||
<div class="rounded-md bg-muted p-4">
|
||||
<pre
|
||||
class="font-mono text-xs whitespace-pre-wrap">{serverProps.chat_template}</pre>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -164,6 +164,19 @@
|
|||
|
||||
let isOpen = $state(false);
|
||||
let showModelDialog = $state(false);
|
||||
let infoModelId = $state<string | null>(null);
|
||||
|
||||
// key of the first "available" (non-loaded, non-favourite) group
|
||||
// used to render the "Available models" separator exactly once
|
||||
let firstAvailableOrgKey = $derived.by(() => {
|
||||
const g = groupedFilteredOptions.find((g) => !g.isLoadedGroup && !g.isFavouritesGroup);
|
||||
return g ? (g.orgName ?? '') : null;
|
||||
});
|
||||
|
||||
function handleInfoClick(modelName: string) {
|
||||
infoModelId = modelName;
|
||||
showModelDialog = true;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
modelsStore.fetch().catch((error) => {
|
||||
|
|
@ -427,12 +440,19 @@
|
|||
<p class="px-2 py-2 text-xs font-semibold text-muted-foreground/60 select-none">
|
||||
Favourite models
|
||||
</p>
|
||||
{:else if group.orgName}
|
||||
<p
|
||||
class="px-2 py-2 text-xs font-semibold text-muted-foreground/60 select-none [&:not(:first-child)]:mt-2"
|
||||
>
|
||||
{group.orgName}
|
||||
</p>
|
||||
{:else}
|
||||
{#if (group.orgName ?? '') === firstAvailableOrgKey}
|
||||
<p class="px-2 py-2 text-xs font-semibold text-muted-foreground/60 select-none">
|
||||
Available models
|
||||
</p>
|
||||
{/if}
|
||||
{#if group.orgName}
|
||||
<p
|
||||
class="px-2 py-2 text-xs font-semibold text-muted-foreground/60 select-none [&:not(:first-child)]:mt-2"
|
||||
>
|
||||
{group.orgName}
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#each group.items as { option, flatIndex } (group.isLoadedGroup ? `loaded-${option.id}` : group.isFavouritesGroup ? `fav-${option.id}` : option.id)}
|
||||
|
|
@ -447,6 +467,7 @@
|
|||
{isFav}
|
||||
showOrgName={group.isFavouritesGroup || group.isLoadedGroup}
|
||||
onSelect={handleSelect}
|
||||
onInfoClick={handleInfoClick}
|
||||
onMouseEnter={() => (highlightedIndex = flatIndex)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === KeyboardKey.ENTER || e.key === KeyboardKey.SPACE) {
|
||||
|
|
@ -500,6 +521,6 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
{#if showModelDialog && !isRouter}
|
||||
<DialogModelInformation bind:open={showModelDialog} />
|
||||
{#if showModelDialog}
|
||||
<DialogModelInformation bind:open={showModelDialog} modelId={infoModelId} />
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { CircleAlert, Heart, HeartOff, Loader2, Power, PowerOff, RotateCw } from '@lucide/svelte';
|
||||
import {
|
||||
CircleAlert,
|
||||
Heart,
|
||||
HeartOff,
|
||||
Info,
|
||||
Loader2,
|
||||
Power,
|
||||
PowerOff,
|
||||
RotateCw
|
||||
} 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 +24,7 @@
|
|||
onSelect: (modelId: string) => void;
|
||||
onMouseEnter: () => void;
|
||||
onKeyDown: (e: KeyboardEvent) => void;
|
||||
onInfoClick?: (modelName: string) => void;
|
||||
}
|
||||
|
||||
let {
|
||||
|
|
@ -25,7 +35,8 @@
|
|||
showOrgName = false,
|
||||
onSelect,
|
||||
onMouseEnter,
|
||||
onKeyDown
|
||||
onKeyDown,
|
||||
onInfoClick
|
||||
}: Props = $props();
|
||||
|
||||
let currentRouterModels = $derived(routerModels());
|
||||
|
|
@ -88,6 +99,23 @@
|
|||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- info button: only shown when model is loaded and callback is provided -->
|
||||
{#if isLoaded && onInfoClick}
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div
|
||||
class="pointer-events-none flex w-4 items-center justify-center opacity-0 group-hover:pointer-events-auto group-hover:opacity-100"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<ActionIcon
|
||||
iconSize="h-2.5 w-2.5"
|
||||
icon={Info}
|
||||
tooltip="Model information"
|
||||
class="h-3 w-3 hover:text-foreground"
|
||||
onclick={() => onInfoClick(option.model)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if isLoading}
|
||||
<Loader2 class="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
{:else if isFailed}
|
||||
|
|
|
|||
|
|
@ -152,6 +152,19 @@
|
|||
|
||||
let sheetOpen = $state(false);
|
||||
let showModelDialog = $state(false);
|
||||
let infoModelId = $state<string | null>(null);
|
||||
|
||||
// key of the first "available" (non-loaded, non-favourite) group
|
||||
// used to render the "Available models" separator exactly once
|
||||
let firstAvailableOrgKey = $derived.by(() => {
|
||||
const g = groupedFilteredOptions.find((g) => !g.isLoadedGroup && !g.isFavouritesGroup);
|
||||
return g ? (g.orgName ?? '') : null;
|
||||
});
|
||||
|
||||
function handleInfoClick(modelName: string) {
|
||||
infoModelId = modelName;
|
||||
showModelDialog = true;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
modelsStore.fetch().catch((error) => {
|
||||
|
|
@ -348,12 +361,19 @@
|
|||
<p class="px-2 py-2 text-xs font-semibold text-muted-foreground/60 select-none">
|
||||
Favourite models
|
||||
</p>
|
||||
{:else if group.orgName}
|
||||
<p
|
||||
class="px-2 py-2 text-xs font-semibold text-muted-foreground/60 select-none [&:not(:first-child)]:mt-2"
|
||||
>
|
||||
{group.orgName}
|
||||
</p>
|
||||
{:else}
|
||||
{#if (group.orgName ?? '') === firstAvailableOrgKey}
|
||||
<p class="px-2 py-2 text-xs font-semibold text-muted-foreground/60 select-none">
|
||||
Available models
|
||||
</p>
|
||||
{/if}
|
||||
{#if group.orgName}
|
||||
<p
|
||||
class="px-2 py-2 text-xs font-semibold text-muted-foreground/60 select-none [&:not(:first-child)]:mt-2"
|
||||
>
|
||||
{group.orgName}
|
||||
</p>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#each group.items as { option } (group.isLoadedGroup ? `loaded-${option.id}` : group.isFavouritesGroup ? `fav-${option.id}` : option.id)}
|
||||
|
|
@ -366,6 +386,7 @@
|
|||
{isFav}
|
||||
showOrgName={group.isFavouritesGroup || group.isLoadedGroup}
|
||||
onSelect={handleSelect}
|
||||
onInfoClick={handleInfoClick}
|
||||
onMouseEnter={() => {}}
|
||||
onKeyDown={() => {}}
|
||||
/>
|
||||
|
|
@ -403,6 +424,6 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
{#if showModelDialog && !isRouter}
|
||||
<DialogModelInformation bind:open={showModelDialog} />
|
||||
{#if showModelDialog}
|
||||
<DialogModelInformation bind:open={showModelDialog} modelId={infoModelId} />
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Reference in New Issue