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>