1<script lang="ts">
  2	import * as Dialog from '$lib/components/ui/dialog';
  3	import * as Table from '$lib/components/ui/table';
  4	import { BadgeModality, CopyToClipboardIcon } from '$lib/components/app';
  5	import { serverStore } from '$lib/stores/server.svelte';
  6	import { modelsStore, modelOptions, modelsLoading } from '$lib/stores/models.svelte';
  7	import { formatFileSize, formatParameters, formatNumber } from '$lib/utils';
  8
  9	interface Props {
 10		open?: boolean;
 11		onOpenChange?: (open: boolean) => void;
 12	}
 13
 14	let { open = $bindable(), onOpenChange }: Props = $props();
 15
 16	let serverProps = $derived(serverStore.props);
 17	let modelName = $derived(modelsStore.singleModelName);
 18	let models = $derived(modelOptions());
 19	let isLoadingModels = $derived(modelsLoading());
 20
 21	// Get the first model for single-model mode display
 22	let firstModel = $derived(models[0] ?? null);
 23
 24	// Get modalities from modelStore using the model ID from the first model
 25	let modalities = $derived.by(() => {
 26		if (!firstModel?.id) return [];
 27		return modelsStore.getModelModalitiesArray(firstModel.id);
 28	});
 29
 30	// Ensure models are fetched when dialog opens
 31	$effect(() => {
 32		if (open && models.length === 0) {
 33			modelsStore.fetch();
 34		}
 35	});
 36</script>
 37
 38<Dialog.Root bind:open {onOpenChange}>
 39	<Dialog.Content class="@container z-9999 !max-w-[60rem] max-w-full">
 40		<style>
 41			@container (max-width: 56rem) {
 42				.resizable-text-container {
 43					max-width: calc(100vw - var(--threshold));
 44				}
 45			}
 46		</style>
 47
 48		<Dialog.Header>
 49			<Dialog.Title>Model Information</Dialog.Title>
 50			<Dialog.Description>Current model details and capabilities</Dialog.Description>
 51		</Dialog.Header>
 52
 53		<div class="space-y-6 py-4">
 54			{#if isLoadingModels}
 55				<div class="flex items-center justify-center py-8">
 56					<div class="text-sm text-muted-foreground">Loading model information...</div>
 57				</div>
 58			{:else if firstModel}
 59				{@const modelMeta = firstModel.meta}
 60
 61				{#if serverProps}
 62					<Table.Root>
 63						<Table.Header>
 64							<Table.Row>
 65								<Table.Head class="w-[10rem]">Model</Table.Head>
 66
 67								<Table.Head>
 68									<div class="inline-flex items-center gap-2">
 69										<span
 70											class="resizable-text-container min-w-0 flex-1 truncate"
 71											style:--threshold="12rem"
 72										>
 73											{modelName}
 74										</span>
 75
 76										<CopyToClipboardIcon
 77											text={modelName || ''}
 78											canCopy={!!modelName}
 79											ariaLabel="Copy model name to clipboard"
 80										/>
 81									</div>
 82								</Table.Head>
 83							</Table.Row>
 84						</Table.Header>
 85						<Table.Body>
 86							<!-- Model Path -->
 87							<Table.Row>
 88								<Table.Cell class="h-10 align-middle font-medium">File Path</Table.Cell>
 89
 90								<Table.Cell
 91									class="inline-flex h-10 items-center gap-2 align-middle font-mono text-xs"
 92								>
 93									<span
 94										class="resizable-text-container min-w-0 flex-1 truncate"
 95										style:--threshold="14rem"
 96									>
 97										{serverProps.model_path}
 98									</span>
 99
100									<CopyToClipboardIcon
101										text={serverProps.model_path}
102										ariaLabel="Copy model path to clipboard"
103									/>
104								</Table.Cell>
105							</Table.Row>
106
107							<!-- Context Size -->
108							<Table.Row>
109								<Table.Cell class="h-10 align-middle font-medium">Context Size</Table.Cell>
110								<Table.Cell
111									>{formatNumber(serverProps.default_generation_settings.n_ctx)} tokens</Table.Cell
112								>
113							</Table.Row>
114
115							<!-- Training Context -->
116							{#if modelMeta?.n_ctx_train}
117								<Table.Row>
118									<Table.Cell class="h-10 align-middle font-medium">Training Context</Table.Cell>
119									<Table.Cell>{formatNumber(modelMeta.n_ctx_train)} tokens</Table.Cell>
120								</Table.Row>
121							{/if}
122
123							<!-- Model Size -->
124							{#if modelMeta?.size}
125								<Table.Row>
126									<Table.Cell class="h-10 align-middle font-medium">Model Size</Table.Cell>
127									<Table.Cell>{formatFileSize(modelMeta.size)}</Table.Cell>
128								</Table.Row>
129							{/if}
130
131							<!-- Parameters -->
132							{#if modelMeta?.n_params}
133								<Table.Row>
134									<Table.Cell class="h-10 align-middle font-medium">Parameters</Table.Cell>
135									<Table.Cell>{formatParameters(modelMeta.n_params)}</Table.Cell>
136								</Table.Row>
137							{/if}
138
139							<!-- Embedding Size -->
140							{#if modelMeta?.n_embd}
141								<Table.Row>
142									<Table.Cell class="align-middle font-medium">Embedding Size</Table.Cell>
143									<Table.Cell>{formatNumber(modelMeta.n_embd)}</Table.Cell>
144								</Table.Row>
145							{/if}
146
147							<!-- Vocabulary Size -->
148							{#if modelMeta?.n_vocab}
149								<Table.Row>
150									<Table.Cell class="align-middle font-medium">Vocabulary Size</Table.Cell>
151									<Table.Cell>{formatNumber(modelMeta.n_vocab)} tokens</Table.Cell>
152								</Table.Row>
153							{/if}
154
155							<!-- Vocabulary Type -->
156							{#if modelMeta?.vocab_type}
157								<Table.Row>
158									<Table.Cell class="align-middle font-medium">Vocabulary Type</Table.Cell>
159									<Table.Cell class="align-middle capitalize">{modelMeta.vocab_type}</Table.Cell>
160								</Table.Row>
161							{/if}
162
163							<!-- Total Slots -->
164							<Table.Row>
165								<Table.Cell class="align-middle font-medium">Parallel Slots</Table.Cell>
166								<Table.Cell>{serverProps.total_slots}</Table.Cell>
167							</Table.Row>
168
169							<!-- Modalities -->
170							{#if modalities.length > 0}
171								<Table.Row>
172									<Table.Cell class="align-middle font-medium">Modalities</Table.Cell>
173									<Table.Cell>
174										<div class="flex flex-wrap gap-1">
175											<BadgeModality {modalities} />
176										</div>
177									</Table.Cell>
178								</Table.Row>
179							{/if}
180
181							<!-- Build Info -->
182							<Table.Row>
183								<Table.Cell class="align-middle font-medium">Build Info</Table.Cell>
184								<Table.Cell class="align-middle font-mono text-xs"
185									>{serverProps.build_info}</Table.Cell
186								>
187							</Table.Row>
188
189							<!-- Chat Template -->
190							{#if serverProps.chat_template}
191								<Table.Row>
192									<Table.Cell class="align-middle font-medium">Chat Template</Table.Cell>
193									<Table.Cell class="py-10">
194										<div class="max-h-120 overflow-y-auto rounded-md bg-muted p-4">
195											<pre
196												class="font-mono text-xs whitespace-pre-wrap">{serverProps.chat_template}</pre>
197										</div>
198									</Table.Cell>
199								</Table.Row>
200							{/if}
201						</Table.Body>
202					</Table.Root>
203				{/if}
204			{:else if !isLoadingModels}
205				<div class="flex items-center justify-center py-8">
206					<div class="text-sm text-muted-foreground">No model information available</div>
207				</div>
208			{/if}
209		</div>
210	</Dialog.Content>
211</Dialog.Root>