mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-10-01 18:37:28 -05:00
ui : fold the model information dialog into the configuration tab
Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
@@ -1,369 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ActionIconCopyToClipboard, BadgesModality } from '$lib/components/app';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { modelsStore, serverStore } from '$lib/stores';
|
||||
import type { ApiLlamaCppServerProps } from '$lib/types';
|
||||
import { formatFileSize, formatNumber, formatParameters } from '$lib/utils';
|
||||
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
// when set, fetch props from the child process (router mode)
|
||||
modelId?: string | null;
|
||||
}
|
||||
|
||||
let { modelId = null, onOpenChange, open = $bindable() }: Props = $props();
|
||||
|
||||
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(modelsStore.models);
|
||||
let isLoadingModels = $derived(modelsStore.loading);
|
||||
|
||||
// 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(() => {
|
||||
if (!firstModel?.id) return [];
|
||||
|
||||
return modelsStore.props.getModelModalitiesArray(firstModel.id);
|
||||
});
|
||||
|
||||
// Ensure models are fetched when dialog opens
|
||||
$effect(() => {
|
||||
if (open && models.length === 0) {
|
||||
modelsStore.fetch();
|
||||
}
|
||||
});
|
||||
|
||||
// fetch per-model props from child process when dialog opens in router mode
|
||||
$effect(() => {
|
||||
if (open && isRouter && modelId) {
|
||||
isLoadingRouterProps = true;
|
||||
modelsStore.props
|
||||
.fetchModelProps(modelId)
|
||||
.then((props) => {
|
||||
routerModelProps = props;
|
||||
})
|
||||
.catch(() => {
|
||||
routerModelProps = null;
|
||||
})
|
||||
.finally(() => {
|
||||
isLoadingRouterProps = false;
|
||||
});
|
||||
}
|
||||
|
||||
if (!open) {
|
||||
routerModelProps = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open {onOpenChange}>
|
||||
<Dialog.Content
|
||||
class="z-9999 max-md:h-[100dvh]! max-md:w-screen! max-md:max-w-none! md:w-[calc(100vw-4rem)]! md:max-w-[60rem]! md:max-h-[80dvh]!"
|
||||
>
|
||||
<!-- sticky header holds only the close button; the title scrolls with the body -->
|
||||
<Dialog.Header />
|
||||
|
||||
<div class="min-w-0 space-y-6 md:py-4 -mt-4! md:mt-0 pb-4">
|
||||
<div class="min-w-0 space-y-2">
|
||||
<Dialog.Title>Model Information</Dialog.Title>
|
||||
|
||||
<Dialog.Description>Current model details and capabilities</Dialog.Description>
|
||||
</div>
|
||||
|
||||
{#if isLoadingModels || isLoadingRouterProps}
|
||||
<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}
|
||||
|
||||
{#if serverProps}
|
||||
<!-- Desktop: fixed-layout table, long values scroll inside their cell -->
|
||||
<Table.Root class="hidden table-fixed md:table">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head class="w-[10rem]">Model</Table.Head>
|
||||
|
||||
<Table.Head>
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<span class="min-w-0 flex-1 overflow-x-auto whitespace-nowrap">
|
||||
{modelName}
|
||||
</span>
|
||||
|
||||
<ActionIconCopyToClipboard
|
||||
ariaLabel="Copy model name to clipboard"
|
||||
canCopy={!!modelName}
|
||||
text={modelName || ''}
|
||||
/>
|
||||
</div>
|
||||
</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
|
||||
<Table.Body>
|
||||
<!-- Model Path -->
|
||||
<Table.Row>
|
||||
<Table.Cell class="h-10 align-middle font-medium">File Path</Table.Cell>
|
||||
|
||||
<Table.Cell class="h-10 align-middle font-mono text-xs">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<span class="min-w-0 flex-1 overflow-x-auto whitespace-nowrap">
|
||||
{serverProps.model_path}
|
||||
</span>
|
||||
|
||||
<ActionIconCopyToClipboard
|
||||
ariaLabel="Copy model path to clipboard"
|
||||
text={serverProps.model_path}
|
||||
/>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
|
||||
<!-- Context Size -->
|
||||
{#if serverProps?.default_generation_settings?.n_ctx}
|
||||
<Table.Row>
|
||||
<Table.Cell class="h-10 align-middle font-medium">Context Size</Table.Cell>
|
||||
|
||||
<Table.Cell
|
||||
>{formatNumber(serverProps.default_generation_settings.n_ctx)} tokens</Table.Cell
|
||||
>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell class="h-10 align-middle font-medium text-red-500"
|
||||
>Context Size</Table.Cell
|
||||
>
|
||||
|
||||
<Table.Cell class="text-red-500">Not available</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
|
||||
<!-- Training Context -->
|
||||
{#if modelMeta?.n_ctx_train}
|
||||
<Table.Row>
|
||||
<Table.Cell class="h-10 align-middle font-medium">Training Context</Table.Cell>
|
||||
|
||||
<Table.Cell>{formatNumber(modelMeta.n_ctx_train)} tokens</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
|
||||
<!-- Model Size -->
|
||||
{#if modelMeta?.size}
|
||||
<Table.Row>
|
||||
<Table.Cell class="h-10 align-middle font-medium">Model Size</Table.Cell>
|
||||
|
||||
<Table.Cell>{formatFileSize(modelMeta.size)}</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
|
||||
<!-- Parameters -->
|
||||
{#if modelMeta?.n_params}
|
||||
<Table.Row>
|
||||
<Table.Cell class="h-10 align-middle font-medium">Parameters</Table.Cell>
|
||||
|
||||
<Table.Cell>{formatParameters(modelMeta.n_params)}</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
|
||||
<!-- Embedding Size -->
|
||||
{#if modelMeta?.n_embd}
|
||||
<Table.Row>
|
||||
<Table.Cell class="align-middle font-medium">Embedding Size</Table.Cell>
|
||||
|
||||
<Table.Cell>{formatNumber(modelMeta.n_embd)}</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
|
||||
<!-- Vocabulary Size -->
|
||||
{#if modelMeta?.n_vocab}
|
||||
<Table.Row>
|
||||
<Table.Cell class="align-middle font-medium">Vocabulary Size</Table.Cell>
|
||||
|
||||
<Table.Cell>{formatNumber(modelMeta.n_vocab)} tokens</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
|
||||
<!-- Vocabulary Type -->
|
||||
{#if modelMeta?.vocab_type}
|
||||
<Table.Row>
|
||||
<Table.Cell class="align-middle font-medium">Vocabulary Type</Table.Cell>
|
||||
|
||||
<Table.Cell class="align-middle capitalize">{modelMeta.vocab_type}</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
|
||||
<!-- Total Slots -->
|
||||
<Table.Row>
|
||||
<Table.Cell class="align-middle font-medium">Parallel Slots</Table.Cell>
|
||||
|
||||
<Table.Cell>{serverProps.total_slots}</Table.Cell>
|
||||
</Table.Row>
|
||||
|
||||
<!-- Modalities -->
|
||||
{#if modalities.length > 0}
|
||||
<Table.Row>
|
||||
<Table.Cell class="align-middle font-medium">Modalities</Table.Cell>
|
||||
|
||||
<Table.Cell>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<BadgesModality {modalities} />
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
|
||||
<!-- Build Info -->
|
||||
<Table.Row>
|
||||
<Table.Cell class="align-middle font-medium">Build Info</Table.Cell>
|
||||
|
||||
<Table.Cell class="align-middle font-mono text-xs"
|
||||
>{serverProps.build_info}</Table.Cell
|
||||
>
|
||||
</Table.Row>
|
||||
|
||||
<!-- Chat Template -->
|
||||
{#if serverProps.chat_template}
|
||||
<Table.Row>
|
||||
<Table.Cell class="py-4" colspan={2}>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-medium">Chat Template</span>
|
||||
|
||||
<div class="overflow-x-auto rounded-md bg-muted p-4">
|
||||
<pre
|
||||
class="font-mono text-xs whitespace-pre">{serverProps.chat_template}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
|
||||
<!-- Mobile: stacked layout; long values wrap instead of scrolling the page -->
|
||||
<div class="flex min-w-0 flex-col gap-4 md:hidden">
|
||||
<div class="min-w-0 space-y-1">
|
||||
<div class="text-xs font-medium text-muted-foreground">Model</div>
|
||||
|
||||
<div class="flex min-w-0 items-start gap-2">
|
||||
<span class="min-w-0 flex-1 break-all font-mono text-xs">{modelName}</span>
|
||||
|
||||
<ActionIconCopyToClipboard
|
||||
ariaLabel="Copy model name to clipboard"
|
||||
canCopy={!!modelName}
|
||||
text={modelName || ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 space-y-1">
|
||||
<div class="text-xs font-medium text-muted-foreground">File Path</div>
|
||||
|
||||
<div class="flex min-w-0 items-start gap-2">
|
||||
<span class="min-w-0 flex-1 break-all font-mono text-xs"
|
||||
>{serverProps.model_path}</span
|
||||
>
|
||||
|
||||
<ActionIconCopyToClipboard
|
||||
ariaLabel="Copy model path to clipboard"
|
||||
text={serverProps.model_path}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if serverProps?.default_generation_settings?.n_ctx}
|
||||
{@render infoRow(
|
||||
'Context Size',
|
||||
`${formatNumber(serverProps.default_generation_settings.n_ctx)} tokens`
|
||||
)}
|
||||
{:else}
|
||||
{@render infoRow('Context Size', 'Not available', 'text-red-500')}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.n_ctx_train}
|
||||
{@render infoRow('Training Context', `${formatNumber(modelMeta.n_ctx_train)} tokens`)}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.size}
|
||||
{@render infoRow('Model Size', formatFileSize(modelMeta.size))}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.n_params}
|
||||
{@render infoRow('Parameters', formatParameters(modelMeta.n_params))}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.n_embd}
|
||||
{@render infoRow('Embedding Size', formatNumber(modelMeta.n_embd))}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.n_vocab}
|
||||
{@render infoRow('Vocabulary Size', `${formatNumber(modelMeta.n_vocab)} tokens`)}
|
||||
{/if}
|
||||
|
||||
{#if modelMeta?.vocab_type}
|
||||
{@render infoRow('Vocabulary Type', modelMeta.vocab_type, 'capitalize')}
|
||||
{/if}
|
||||
|
||||
{@render infoRow('Parallel Slots', `${serverProps.total_slots}`)}
|
||||
|
||||
{#if modalities.length > 0}
|
||||
<div class="min-w-0 space-y-1">
|
||||
<div class="text-xs font-medium text-muted-foreground">Modalities</div>
|
||||
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<BadgesModality {modalities} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="min-w-0 space-y-1">
|
||||
<div class="text-xs font-medium text-muted-foreground">Build Info</div>
|
||||
|
||||
<span class="block break-all font-mono text-xs">{serverProps.build_info}</span>
|
||||
</div>
|
||||
|
||||
{#if serverProps.chat_template}
|
||||
<div class="min-w-0 space-y-2">
|
||||
<div class="text-xs font-medium text-muted-foreground">Chat Template</div>
|
||||
|
||||
<div class="overflow-x-auto rounded-md bg-muted p-4">
|
||||
<pre class="font-mono text-xs whitespace-pre">{serverProps.chat_template}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{:else if !isLoadingModels}
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="text-sm text-muted-foreground">No model information available</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
{#snippet infoRow(label: string, value: string, valueClass: string = '')}
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span class="shrink-0 text-xs font-medium text-muted-foreground {valueClass}">{label}</span>
|
||||
|
||||
<span class="text-sm {valueClass}">{value}</span>
|
||||
</div>
|
||||
{/snippet}
|
||||
@@ -454,7 +454,6 @@ export { default as DialogConversationSelection } from './DialogConversationSele
|
||||
* <DialogModelInformation bind:open={showModelInfo} />
|
||||
* ```
|
||||
*/
|
||||
export { default as DialogModelInformation } from './DialogModelInformation.svelte';
|
||||
|
||||
/**
|
||||
* **DialogMcpResourcesBrowser** - MCP resources browser dialog
|
||||
|
||||
+98
-16
@@ -1,10 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { isLocalOption, modelQuantLabel, modelSizeLabel, resolveModelSize } from '../utils';
|
||||
import { CollapsibleSection } from '$lib/components/app';
|
||||
import {
|
||||
ActionIconCopyToClipboard,
|
||||
BadgesModality,
|
||||
CollapsibleSection
|
||||
} from '$lib/components/app';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { modelsStore } from '$lib/stores';
|
||||
import type { ApiLlamaCppServerProps } from '$lib/types/api';
|
||||
import type { ModelOption } from '$lib/types/models';
|
||||
import { formatParameters } from '$lib/utils/formatters';
|
||||
import { formatFileSize, formatNumber, formatParameters } from '$lib/utils/formatters';
|
||||
|
||||
interface Props {
|
||||
option: ModelOption;
|
||||
@@ -36,46 +41,123 @@
|
||||
};
|
||||
});
|
||||
|
||||
let meta = $derived(option.meta);
|
||||
let modalities = $derived(modelsStore.props.getModelModalitiesArray(option.id));
|
||||
let isMissingContext = $derived(!serverProps);
|
||||
let rows = $derived([
|
||||
{ label: 'Model', value: option.model },
|
||||
{ label: 'File Path', value: serverProps?.model_path ?? null },
|
||||
{ isCopyable: true, isMono: true, label: 'File Path', value: serverProps?.model_path ?? null },
|
||||
{
|
||||
label: 'Context Size',
|
||||
value: serverProps
|
||||
? `${formatParameters(serverProps.default_generation_settings.n_ctx)} tokens`
|
||||
? `${formatNumber(serverProps.default_generation_settings.n_ctx)} tokens`
|
||||
: null
|
||||
},
|
||||
{
|
||||
label: 'Training Context',
|
||||
value: option.contextLength ? `${formatParameters(option.contextLength)} tokens` : null
|
||||
value: meta?.n_ctx_train
|
||||
? `${formatNumber(meta.n_ctx_train)} tokens`
|
||||
: option.contextLength
|
||||
? `${formatParameters(option.contextLength)} tokens`
|
||||
: null
|
||||
},
|
||||
{ label: 'Model Size', value: resolvedSize ?? size },
|
||||
{ label: 'Parameters', value: option.parsedId?.params ?? null },
|
||||
{
|
||||
label: 'Model Size',
|
||||
value: meta?.size ? formatFileSize(meta.size) : (resolvedSize ?? size)
|
||||
},
|
||||
{
|
||||
label: 'Parameters',
|
||||
value: meta?.n_params ? formatParameters(meta.n_params) : (option.parsedId?.params ?? null)
|
||||
},
|
||||
{ label: 'Embedding Size', value: meta?.n_embd ? formatNumber(meta.n_embd) : null },
|
||||
{
|
||||
label: 'Vocabulary Size',
|
||||
value: meta?.n_vocab ? `${formatNumber(meta.n_vocab)} tokens` : null
|
||||
},
|
||||
{ isCapitalized: true, label: 'Vocabulary Type', value: (meta?.vocab_type as string) ?? null },
|
||||
{ isBadge: true, label: 'Quantization', value: quant },
|
||||
{ isBadge: true, label: 'Architecture', value: (option.meta?.architecture as string) ?? null },
|
||||
{ label: 'Format', value: isLocalOption(option) ? 'GGUF' : null },
|
||||
{ label: 'Parallel Slots', value: serverProps ? String(serverProps.total_slots) : null }
|
||||
] satisfies Array<{ isBadge?: boolean; label: string; value: string | null }>);
|
||||
{ label: 'Parallel Slots', value: serverProps ? String(serverProps.total_slots) : null },
|
||||
{ isMono: true, label: 'Build Info', value: serverProps?.build_info ?? null }
|
||||
] satisfies Array<{
|
||||
isBadge?: boolean;
|
||||
isCapitalized?: boolean;
|
||||
isCopyable?: boolean;
|
||||
isMono?: boolean;
|
||||
label: string;
|
||||
value: string | null;
|
||||
}>);
|
||||
|
||||
const sectionTrigger = 'flex w-full cursor-pointer items-center gap-2 py-2 text-left';
|
||||
</script>
|
||||
|
||||
<div class="space-y-0">
|
||||
<div class="flex items-center gap-3 border-b border-border/30 py-2.5">
|
||||
<span class="text-sm text-muted-foreground">Model</span>
|
||||
|
||||
<span class="ml-auto flex min-w-0 items-center gap-2">
|
||||
<span class="min-w-0 truncate font-mono text-xs">{option.model}</span>
|
||||
|
||||
<ActionIconCopyToClipboard
|
||||
ariaLabel="Copy model name to clipboard"
|
||||
canCopy
|
||||
text={option.model}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{#each rows as row (row.label)}
|
||||
<div class="flex items-center gap-3 border-b border-border/30 py-2.5 last:border-b-0">
|
||||
<span class="text-sm text-muted-foreground">{row.label}</span>
|
||||
<span
|
||||
class="text-sm text-muted-foreground {row.label === 'Context Size' && isMissingContext
|
||||
? 'text-destructive'
|
||||
: ''}">{row.label}</span
|
||||
>
|
||||
|
||||
<span class="ml-auto min-w-0 truncate text-sm">
|
||||
<span class="ml-auto flex min-w-0 items-center gap-2">
|
||||
{#if row.value === null}
|
||||
<span class="text-muted-foreground">—</span>
|
||||
{:else if row.isBadge}
|
||||
<Badge class="h-5 px-1.5 text-[10px]" variant="secondary">{row.value}</Badge>
|
||||
<span
|
||||
class={isMissingContext && row.label === 'Context Size'
|
||||
? 'text-destructive'
|
||||
: 'text-muted-foreground'}
|
||||
>
|
||||
{isMissingContext && row.label === 'Context Size' ? 'Not available' : '—'}
|
||||
</span>
|
||||
{:else}
|
||||
{row.value}
|
||||
<span
|
||||
class={[
|
||||
'min-w-0 truncate',
|
||||
row.isBadge ? '' : 'text-sm',
|
||||
row.isCapitalized ? 'capitalize' : '',
|
||||
row.isMono ? 'font-mono text-xs' : ''
|
||||
]}
|
||||
>
|
||||
{#if row.isBadge}
|
||||
<Badge class="h-5 px-1.5 text-[10px]" variant="secondary">{row.value}</Badge>
|
||||
{:else}
|
||||
{row.value}
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
{#if row.isCopyable}
|
||||
<ActionIconCopyToClipboard
|
||||
ariaLabel="Copy value to clipboard"
|
||||
canCopy
|
||||
text={row.value}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
{#if modalities.length > 0}
|
||||
<div class="flex items-center gap-3 border-b border-border/30 py-2.5 last:border-b-0">
|
||||
<span class="text-sm text-muted-foreground">Modalities</span>
|
||||
|
||||
<span class="ml-auto flex flex-wrap gap-1"><BadgesModality {modalities} /></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import ModelLoadHighlight from '../ModelLoadHighlight.svelte';
|
||||
import { ChevronDown, Lightbulb, Loader2 } from '@lucide/svelte';
|
||||
import {
|
||||
DialogModelInformation,
|
||||
DropdownMenuSearchable,
|
||||
ModelId,
|
||||
ModelsSelectorList,
|
||||
@@ -334,7 +333,6 @@
|
||||
{isFav}
|
||||
{isHighlighted}
|
||||
{isSelected}
|
||||
onInfoClick={ms.handleInfoClick}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === KeyboardKey.ENTER || event.key === KeyboardKey.SPACE) {
|
||||
event.preventDefault();
|
||||
@@ -354,7 +352,6 @@
|
||||
favorites={ms.favoriteItems}
|
||||
groups={ms.groupedFilteredOptions}
|
||||
loaded={ms.loadedItems}
|
||||
onInfoClick={ms.handleInfoClick}
|
||||
onProviderBack={ms.isProviderView ? ms.closeProvider : undefined}
|
||||
onProviderOpen={ms.openProvider}
|
||||
onSelect={ms.handleSelect}
|
||||
@@ -434,14 +431,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if ms.showModelDialog}
|
||||
<DialogModelInformation
|
||||
modelId={ms.infoModelId}
|
||||
onOpenChange={(v) => ms.setShowModelDialog(v)}
|
||||
open={ms.showModelDialog}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<DialogBackendForm
|
||||
bind:open={showAddBackend}
|
||||
onSaved={(backend) => void ms.showBackendModels(backend.id)}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
activeId: string | null;
|
||||
sectionHeaderClass?: string;
|
||||
onSelect: (modelId: string) => void;
|
||||
onInfoClick: (modelName: string) => void;
|
||||
renderOption?: import('svelte').Snippet<[ModelItem, boolean]>;
|
||||
/** Favorite models of every backend, listed in their own section. */
|
||||
favorites?: ModelItem[];
|
||||
@@ -40,7 +39,6 @@
|
||||
favorites = [],
|
||||
groups,
|
||||
loaded = [],
|
||||
onInfoClick,
|
||||
onProviderBack,
|
||||
onProviderOpen,
|
||||
onSelect,
|
||||
@@ -114,7 +112,6 @@
|
||||
{isFav}
|
||||
isHighlighted={false}
|
||||
{isSelected}
|
||||
{onInfoClick}
|
||||
onKeyDown={() => {}}
|
||||
onMouseEnter={() => {}}
|
||||
{onSelect}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
onSelect: (modelId: string) => void;
|
||||
onMouseEnter: () => void;
|
||||
onKeyDown: (e: KeyboardEvent) => void;
|
||||
onInfoClick?: (modelName: string) => void;
|
||||
/** Show the base model's org as the main avatar and the repo (quant) org as the corner badge; resolves the base org lazily via HF. */
|
||||
showBaseModelAvatar?: boolean;
|
||||
}
|
||||
@@ -27,7 +26,6 @@
|
||||
isFav,
|
||||
isHighlighted,
|
||||
isSelected,
|
||||
onInfoClick,
|
||||
onKeyDown,
|
||||
onMouseEnter,
|
||||
onSelect,
|
||||
@@ -103,7 +101,7 @@
|
||||
/>
|
||||
|
||||
<div class="flex shrink-0 items-center gap-1">
|
||||
<ModelRowActions {isFav} {isLoaded} onInfo={onInfoClick} {option} />
|
||||
<ModelRowActions {isFav} {isLoaded} {option} />
|
||||
|
||||
<ModelLoadControl
|
||||
{canLoad}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import ModelLoadHighlight from '../ModelLoadHighlight.svelte';
|
||||
import { ChevronDown, Lightbulb, Loader2, Package } from '@lucide/svelte';
|
||||
import {
|
||||
DialogModelInformation,
|
||||
ModelId,
|
||||
ModelsSelectorList,
|
||||
ModelsSelectorReasoningPanel,
|
||||
@@ -196,7 +195,6 @@
|
||||
favorites={ms.favoriteItems}
|
||||
groups={ms.groupedFilteredOptions}
|
||||
loaded={ms.loadedItems}
|
||||
onInfoClick={ms.handleInfoClick}
|
||||
onProviderBack={ms.isProviderView ? ms.closeProvider : undefined}
|
||||
onProviderOpen={ms.openProvider}
|
||||
onSelect={ms.handleSelect}
|
||||
@@ -244,14 +242,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if ms.showModelDialog}
|
||||
<DialogModelInformation
|
||||
modelId={ms.infoModelId}
|
||||
onOpenChange={(v) => ms.setShowModelDialog(v)}
|
||||
open={ms.showModelDialog}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<DialogBackendForm
|
||||
bind:open={showAddBackend}
|
||||
onSaved={(backend) => void ms.showBackendModels(backend.id)}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
LOCAL_BACKEND_ID,
|
||||
REMOTE_PROVIDER_MODEL_LIMIT
|
||||
} from '$lib/constants';
|
||||
import { backendsModelsStore, backendsStore, modelsStore, serverStore } from '$lib/stores';
|
||||
import { backendsModelsStore, backendsStore, modelsStore, serverStore, uiStore } from '$lib/stores';
|
||||
import type { ModelOption } from '$lib/types/models';
|
||||
import { getBackend } from '$lib/utils/api-base';
|
||||
import { getBackendCapabilities } from '$lib/utils/backend';
|
||||
@@ -53,14 +53,10 @@ export interface UseModelsSelectorReturn {
|
||||
readonly groupedFilteredOptions: ReturnType<typeof groupModelOptions>;
|
||||
readonly isLoadingModel: boolean;
|
||||
readonly searchTerm: string;
|
||||
readonly showModelDialog: boolean;
|
||||
readonly infoModelId: string | null;
|
||||
closeProvider(): void;
|
||||
openProvider(backendId: string): void;
|
||||
setSearchTerm(value: string): void;
|
||||
showBackendModels(backendId: string): Promise<void>;
|
||||
setShowModelDialog(value: boolean): void;
|
||||
handleInfoClick(modelName: string): void;
|
||||
handleSelect(modelId: string): Promise<void>;
|
||||
handleOpenChange(open: boolean): void;
|
||||
isFavorite(model: string): boolean;
|
||||
@@ -124,8 +120,6 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
|
||||
|
||||
let isLoadingModel = $state(false);
|
||||
let searchTerm = $state('');
|
||||
let showModelDialog = $state(false);
|
||||
let infoModelId = $state<string | null>(null);
|
||||
|
||||
const visibleOptions = $derived(allOptions.filter((option) => !modelsStore.isHidden(option.id)));
|
||||
const filteredOptions = $derived(filterModelOptions(options, searchTerm));
|
||||
@@ -196,11 +190,6 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
|
||||
);
|
||||
const emptyMessage = $derived(searchTerm ? 'No models found.' : 'No models yet.');
|
||||
|
||||
function handleInfoClick(modelName: string) {
|
||||
infoModelId = modelName;
|
||||
showModelDialog = true;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
modelsStore.fetch().catch((error) => {
|
||||
console.error('Unable to load models:', error);
|
||||
@@ -210,10 +199,10 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
|
||||
function handleOpenChange(open: boolean) {
|
||||
if (loading || updating) return;
|
||||
|
||||
// a single-model llama.cpp server with no other backend has no list to
|
||||
// show, so the trigger opens the model info dialog instead
|
||||
// a single-model llama.cpp server with no other backend has no list to show,
|
||||
// so the trigger opens the manager, which holds the one model's configuration
|
||||
if (!isMultiModel) {
|
||||
showModelDialog = open;
|
||||
if (open) uiStore.openModelsManager();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -362,16 +351,10 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
|
||||
return groupedFilteredOptions;
|
||||
},
|
||||
|
||||
handleInfoClick,
|
||||
|
||||
handleOpenChange,
|
||||
|
||||
handleSelect,
|
||||
|
||||
get infoModelId() {
|
||||
return infoModelId;
|
||||
},
|
||||
|
||||
get isCurrentModelInCache() {
|
||||
return isCurrentModelInCache;
|
||||
},
|
||||
@@ -430,16 +413,8 @@ export function useModelsSelector(opts: UseModelsSelectorOptions): UseModelsSele
|
||||
searchTerm = value;
|
||||
},
|
||||
|
||||
setShowModelDialog(value: boolean) {
|
||||
showModelDialog = value;
|
||||
},
|
||||
|
||||
showBackendModels,
|
||||
|
||||
get showModelDialog() {
|
||||
return showModelDialog;
|
||||
},
|
||||
|
||||
get updating() {
|
||||
return updating;
|
||||
}
|
||||
|
||||
@@ -126,7 +126,6 @@
|
||||
currentModel={selectedModel}
|
||||
favorites={favoriteModels}
|
||||
groups={groupedOptions}
|
||||
onInfoClick={(modelName) => console.log('Info clicked:', modelName)}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
</div>
|
||||
@@ -142,7 +141,6 @@
|
||||
loaded: [loadedModels[0]],
|
||||
providers: []
|
||||
}}
|
||||
onInfoClick={(modelName) => console.log('Info clicked:', modelName)}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
</div>
|
||||
@@ -159,7 +157,6 @@
|
||||
loaded: [],
|
||||
providers: []
|
||||
}}
|
||||
onInfoClick={(modelName) => console.log('Info clicked:', modelName)}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user