summaryrefslogtreecommitdiff
path: root/llama.cpp/tools/server/webui/src/lib/components/app/dialogs
diff options
context:
space:
mode:
Diffstat (limited to 'llama.cpp/tools/server/webui/src/lib/components/app/dialogs')
-rw-r--r--llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatAttachmentPreview.svelte67
-rw-r--r--llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatAttachmentsViewAll.svelte54
-rw-r--r--llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatError.svelte70
-rw-r--r--llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatSettings.svelte37
-rw-r--r--llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConfirmation.svelte72
-rw-r--r--llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConversationSelection.svelte68
-rw-r--r--llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConversationTitleUpdate.svelte46
-rw-r--r--llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogEmptyFileAlert.svelte61
-rw-r--r--llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogModelInformation.svelte211
-rw-r--r--llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogModelNotAvailable.svelte76
10 files changed, 762 insertions, 0 deletions
diff --git a/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatAttachmentPreview.svelte b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatAttachmentPreview.svelte
new file mode 100644
index 0000000..012ba00
--- /dev/null
+++ b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatAttachmentPreview.svelte
@@ -0,0 +1,67 @@
1<script lang="ts">
2 import * as Dialog from '$lib/components/ui/dialog';
3 import { ChatAttachmentPreview } from '$lib/components/app';
4 import { formatFileSize } from '$lib/utils';
5
6 interface Props {
7 open: boolean;
8 onOpenChange?: (open: boolean) => void;
9 // Either an uploaded file or a stored attachment
10 uploadedFile?: ChatUploadedFile;
11 attachment?: DatabaseMessageExtra;
12 // For uploaded files
13 preview?: string;
14 name?: string;
15 size?: number;
16 textContent?: string;
17 // For vision modality check
18 activeModelId?: string;
19 }
20
21 let {
22 open = $bindable(),
23 onOpenChange,
24 uploadedFile,
25 attachment,
26 preview,
27 name,
28 size,
29 textContent,
30 activeModelId
31 }: Props = $props();
32
33 let chatAttachmentPreviewRef: ChatAttachmentPreview | undefined = $state();
34
35 let displayName = $derived(uploadedFile?.name || attachment?.name || name || 'Unknown File');
36
37 let displaySize = $derived(uploadedFile?.size || size);
38
39 $effect(() => {
40 if (open && chatAttachmentPreviewRef) {
41 chatAttachmentPreviewRef.reset();
42 }
43 });
44</script>
45
46<Dialog.Root bind:open {onOpenChange}>
47 <Dialog.Content class="grid max-h-[90vh] max-w-5xl overflow-hidden sm:w-auto sm:max-w-6xl">
48 <Dialog.Header>
49 <Dialog.Title class="pr-8">{displayName}</Dialog.Title>
50 <Dialog.Description>
51 {#if displaySize}
52 {formatFileSize(displaySize)}
53 {/if}
54 </Dialog.Description>
55 </Dialog.Header>
56
57 <ChatAttachmentPreview
58 bind:this={chatAttachmentPreviewRef}
59 {uploadedFile}
60 {attachment}
61 {preview}
62 name={displayName}
63 {textContent}
64 {activeModelId}
65 />
66 </Dialog.Content>
67</Dialog.Root>
diff --git a/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatAttachmentsViewAll.svelte b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatAttachmentsViewAll.svelte
new file mode 100644
index 0000000..33ab0fe
--- /dev/null
+++ b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatAttachmentsViewAll.svelte
@@ -0,0 +1,54 @@
1<script lang="ts">
2 import * as Dialog from '$lib/components/ui/dialog';
3 import { ChatAttachmentsViewAll } from '$lib/components/app';
4
5 interface Props {
6 open?: boolean;
7 uploadedFiles?: ChatUploadedFile[];
8 attachments?: DatabaseMessageExtra[];
9 readonly?: boolean;
10 onFileRemove?: (fileId: string) => void;
11 imageHeight?: string;
12 imageWidth?: string;
13 imageClass?: string;
14 activeModelId?: string;
15 }
16
17 let {
18 open = $bindable(false),
19 uploadedFiles = [],
20 attachments = [],
21 readonly = false,
22 onFileRemove,
23 imageHeight = 'h-24',
24 imageWidth = 'w-auto',
25 imageClass = '',
26 activeModelId
27 }: Props = $props();
28
29 let totalCount = $derived(uploadedFiles.length + attachments.length);
30</script>
31
32<Dialog.Root bind:open>
33 <Dialog.Portal>
34 <Dialog.Overlay />
35
36 <Dialog.Content class="flex !max-h-[90vh] !max-w-6xl flex-col">
37 <Dialog.Header>
38 <Dialog.Title>All Attachments ({totalCount})</Dialog.Title>
39 <Dialog.Description>View and manage all attached files</Dialog.Description>
40 </Dialog.Header>
41
42 <ChatAttachmentsViewAll
43 {uploadedFiles}
44 {attachments}
45 {readonly}
46 {onFileRemove}
47 {imageHeight}
48 {imageWidth}
49 {imageClass}
50 {activeModelId}
51 />
52 </Dialog.Content>
53 </Dialog.Portal>
54</Dialog.Root>
diff --git a/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatError.svelte b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatError.svelte
new file mode 100644
index 0000000..b4340e8
--- /dev/null
+++ b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatError.svelte
@@ -0,0 +1,70 @@
1<script lang="ts">
2 import * as AlertDialog from '$lib/components/ui/alert-dialog';
3 import { AlertTriangle, TimerOff } from '@lucide/svelte';
4
5 interface Props {
6 open: boolean;
7 type: 'timeout' | 'server';
8 message: string;
9 contextInfo?: { n_prompt_tokens: number; n_ctx: number };
10 onOpenChange?: (open: boolean) => void;
11 }
12
13 let { open = $bindable(), type, message, contextInfo, onOpenChange }: Props = $props();
14
15 const isTimeout = $derived(type === 'timeout');
16 const title = $derived(isTimeout ? 'TCP Timeout' : 'Server Error');
17 const description = $derived(
18 isTimeout
19 ? 'The request did not receive a response from the server before timing out.'
20 : 'The server responded with an error message. Review the details below.'
21 );
22 const iconClass = $derived(isTimeout ? 'text-destructive' : 'text-amber-500');
23 const badgeClass = $derived(
24 isTimeout
25 ? 'border-destructive/40 bg-destructive/10 text-destructive'
26 : 'border-amber-500/40 bg-amber-500/10 text-amber-600 dark:text-amber-400'
27 );
28
29 function handleOpenChange(newOpen: boolean) {
30 open = newOpen;
31 onOpenChange?.(newOpen);
32 }
33</script>
34
35<AlertDialog.Root {open} onOpenChange={handleOpenChange}>
36 <AlertDialog.Content>
37 <AlertDialog.Header>
38 <AlertDialog.Title class="flex items-center gap-2">
39 {#if isTimeout}
40 <TimerOff class={`h-5 w-5 ${iconClass}`} />
41 {:else}
42 <AlertTriangle class={`h-5 w-5 ${iconClass}`} />
43 {/if}
44
45 {title}
46 </AlertDialog.Title>
47
48 <AlertDialog.Description>
49 {description}
50 </AlertDialog.Description>
51 </AlertDialog.Header>
52
53 <div class={`rounded-lg border px-4 py-3 text-sm ${badgeClass}`}>
54 <p class="font-medium">{message}</p>
55 {#if contextInfo}
56 <div class="mt-2 space-y-1 text-xs opacity-80">
57 <p>
58 <span class="font-medium">Prompt tokens:</span>
59 {contextInfo.n_prompt_tokens.toLocaleString()}
60 </p>
61 <p><span class="font-medium">Context size:</span> {contextInfo.n_ctx.toLocaleString()}</p>
62 </div>
63 {/if}
64 </div>
65
66 <AlertDialog.Footer>
67 <AlertDialog.Action onclick={() => handleOpenChange(false)}>Close</AlertDialog.Action>
68 </AlertDialog.Footer>
69 </AlertDialog.Content>
70</AlertDialog.Root>
diff --git a/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatSettings.svelte b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatSettings.svelte
new file mode 100644
index 0000000..e9aaa10
--- /dev/null
+++ b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogChatSettings.svelte
@@ -0,0 +1,37 @@
1<script lang="ts">
2 import * as Dialog from '$lib/components/ui/dialog';
3 import { ChatSettings } from '$lib/components/app';
4
5 interface Props {
6 onOpenChange?: (open: boolean) => void;
7 open?: boolean;
8 }
9
10 let { onOpenChange, open = false }: Props = $props();
11
12 let chatSettingsRef: ChatSettings | undefined = $state();
13
14 function handleClose() {
15 onOpenChange?.(false);
16 }
17
18 function handleSave() {
19 onOpenChange?.(false);
20 }
21
22 $effect(() => {
23 if (open && chatSettingsRef) {
24 chatSettingsRef.reset();
25 }
26 });
27</script>
28
29<Dialog.Root {open} onOpenChange={handleClose}>
30 <Dialog.Content
31 class="z-999999 flex h-[100dvh] max-h-[100dvh] min-h-[100dvh] flex-col gap-0 rounded-none p-0
32 md:h-[64vh] md:max-h-[64vh] md:min-h-0 md:rounded-lg"
33 style="max-width: 48rem;"
34 >
35 <ChatSettings bind:this={chatSettingsRef} onSave={handleSave} />
36 </Dialog.Content>
37</Dialog.Root>
diff --git a/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConfirmation.svelte b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConfirmation.svelte
new file mode 100644
index 0000000..b5175a9
--- /dev/null
+++ b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConfirmation.svelte
@@ -0,0 +1,72 @@
1<script lang="ts">
2 import * as AlertDialog from '$lib/components/ui/alert-dialog';
3 import type { Component } from 'svelte';
4
5 interface Props {
6 open: boolean;
7 title: string;
8 description: string;
9 confirmText?: string;
10 cancelText?: string;
11 variant?: 'default' | 'destructive';
12 icon?: Component;
13 onConfirm: () => void;
14 onCancel: () => void;
15 onKeydown?: (event: KeyboardEvent) => void;
16 }
17
18 let {
19 open = $bindable(),
20 title,
21 description,
22 confirmText = 'Confirm',
23 cancelText = 'Cancel',
24 variant = 'default',
25 icon,
26 onConfirm,
27 onCancel,
28 onKeydown
29 }: Props = $props();
30
31 function handleKeydown(event: KeyboardEvent) {
32 if (event.key === 'Enter') {
33 event.preventDefault();
34 onConfirm();
35 }
36 onKeydown?.(event);
37 }
38
39 function handleOpenChange(newOpen: boolean) {
40 if (!newOpen) {
41 onCancel();
42 }
43 }
44</script>
45
46<AlertDialog.Root {open} onOpenChange={handleOpenChange}>
47 <AlertDialog.Content onkeydown={handleKeydown}>
48 <AlertDialog.Header>
49 <AlertDialog.Title class="flex items-center gap-2">
50 {#if icon}
51 {@const IconComponent = icon}
52 <IconComponent class="h-5 w-5 {variant === 'destructive' ? 'text-destructive' : ''}" />
53 {/if}
54 {title}
55 </AlertDialog.Title>
56
57 <AlertDialog.Description>
58 {description}
59 </AlertDialog.Description>
60 </AlertDialog.Header>
61
62 <AlertDialog.Footer>
63 <AlertDialog.Cancel onclick={onCancel}>{cancelText}</AlertDialog.Cancel>
64 <AlertDialog.Action
65 onclick={onConfirm}
66 class={variant === 'destructive' ? 'bg-destructive text-white hover:bg-destructive/80' : ''}
67 >
68 {confirmText}
69 </AlertDialog.Action>
70 </AlertDialog.Footer>
71 </AlertDialog.Content>
72</AlertDialog.Root>
diff --git a/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConversationSelection.svelte b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConversationSelection.svelte
new file mode 100644
index 0000000..1f8ea64
--- /dev/null
+++ b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConversationSelection.svelte
@@ -0,0 +1,68 @@
1<script lang="ts">
2 import * as Dialog from '$lib/components/ui/dialog';
3 import { ConversationSelection } from '$lib/components/app';
4
5 interface Props {
6 conversations: DatabaseConversation[];
7 messageCountMap?: Map<string, number>;
8 mode: 'export' | 'import';
9 onCancel: () => void;
10 onConfirm: (selectedConversations: DatabaseConversation[]) => void;
11 open?: boolean;
12 }
13
14 let {
15 conversations,
16 messageCountMap = new Map(),
17 mode,
18 onCancel,
19 onConfirm,
20 open = $bindable(false)
21 }: Props = $props();
22
23 let conversationSelectionRef: ConversationSelection | undefined = $state();
24
25 let previousOpen = $state(false);
26
27 $effect(() => {
28 if (open && !previousOpen && conversationSelectionRef) {
29 conversationSelectionRef.reset();
30 } else if (!open && previousOpen) {
31 onCancel();
32 }
33
34 previousOpen = open;
35 });
36</script>
37
38<Dialog.Root bind:open>
39 <Dialog.Portal>
40 <Dialog.Overlay class="z-[1000000]" />
41
42 <Dialog.Content class="z-[1000001] max-w-2xl">
43 <Dialog.Header>
44 <Dialog.Title>
45 Select Conversations to {mode === 'export' ? 'Export' : 'Import'}
46 </Dialog.Title>
47 <Dialog.Description>
48 {#if mode === 'export'}
49 Choose which conversations you want to export. Selected conversations will be downloaded
50 as a JSON file.
51 {:else}
52 Choose which conversations you want to import. Selected conversations will be merged
53 with your existing conversations.
54 {/if}
55 </Dialog.Description>
56 </Dialog.Header>
57
58 <ConversationSelection
59 bind:this={conversationSelectionRef}
60 {conversations}
61 {messageCountMap}
62 {mode}
63 {onCancel}
64 {onConfirm}
65 />
66 </Dialog.Content>
67 </Dialog.Portal>
68</Dialog.Root>
diff --git a/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConversationTitleUpdate.svelte b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConversationTitleUpdate.svelte
new file mode 100644
index 0000000..4a9ecce
--- /dev/null
+++ b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogConversationTitleUpdate.svelte
@@ -0,0 +1,46 @@
1<script lang="ts">
2 import * as AlertDialog from '$lib/components/ui/alert-dialog';
3 import { Button } from '$lib/components/ui/button';
4
5 interface Props {
6 open: boolean;
7 currentTitle: string;
8 newTitle: string;
9 onConfirm: () => void;
10 onCancel: () => void;
11 }
12
13 let { open = $bindable(), currentTitle, newTitle, onConfirm, onCancel }: Props = $props();
14</script>
15
16<AlertDialog.Root bind:open>
17 <AlertDialog.Content>
18 <AlertDialog.Header>
19 <AlertDialog.Title>Update Conversation Title?</AlertDialog.Title>
20
21 <AlertDialog.Description>
22 Do you want to update the conversation title to match the first message content?
23 </AlertDialog.Description>
24 </AlertDialog.Header>
25
26 <div class="space-y-4 pt-2 pb-6">
27 <div class="space-y-2">
28 <p class="text-sm font-medium text-muted-foreground">Current title:</p>
29
30 <p class="rounded-md bg-muted/50 p-3 text-sm font-medium">{currentTitle}</p>
31 </div>
32
33 <div class="space-y-2">
34 <p class="text-sm font-medium text-muted-foreground">New title would be:</p>
35
36 <p class="rounded-md bg-muted/50 p-3 text-sm font-medium">{newTitle}</p>
37 </div>
38 </div>
39
40 <AlertDialog.Footer>
41 <Button variant="outline" onclick={onCancel}>Keep Current Title</Button>
42
43 <Button onclick={onConfirm}>Update Title</Button>
44 </AlertDialog.Footer>
45 </AlertDialog.Content>
46</AlertDialog.Root>
diff --git a/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogEmptyFileAlert.svelte b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogEmptyFileAlert.svelte
new file mode 100644
index 0000000..f875b0a
--- /dev/null
+++ b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogEmptyFileAlert.svelte
@@ -0,0 +1,61 @@
1<script lang="ts">
2 import * as AlertDialog from '$lib/components/ui/alert-dialog';
3 import { FileX } from '@lucide/svelte';
4
5 interface Props {
6 open: boolean;
7 emptyFiles: string[];
8 onOpenChange?: (open: boolean) => void;
9 }
10
11 let { open = $bindable(), emptyFiles, onOpenChange }: Props = $props();
12
13 function handleOpenChange(newOpen: boolean) {
14 open = newOpen;
15 onOpenChange?.(newOpen);
16 }
17</script>
18
19<AlertDialog.Root {open} onOpenChange={handleOpenChange}>
20 <AlertDialog.Content>
21 <AlertDialog.Header>
22 <AlertDialog.Title class="flex items-center gap-2">
23 <FileX class="h-5 w-5 text-destructive" />
24
25 Empty Files Detected
26 </AlertDialog.Title>
27
28 <AlertDialog.Description>
29 The following files are empty and have been removed from your attachments:
30 </AlertDialog.Description>
31 </AlertDialog.Header>
32
33 <div class="space-y-3 text-sm">
34 <div class="rounded-lg bg-muted p-3">
35 <div class="mb-2 font-medium">Empty Files:</div>
36
37 <ul class="list-inside list-disc space-y-1 text-muted-foreground">
38 {#each emptyFiles as fileName (fileName)}
39 <li class="font-mono text-sm">{fileName}</li>
40 {/each}
41 </ul>
42 </div>
43
44 <div>
45 <div class="mb-2 font-medium">What happened:</div>
46
47 <ul class="list-inside list-disc space-y-1 text-muted-foreground">
48 <li>Empty files cannot be processed or sent to the AI model</li>
49
50 <li>These files have been automatically removed from your attachments</li>
51
52 <li>You can try uploading files with content instead</li>
53 </ul>
54 </div>
55 </div>
56
57 <AlertDialog.Footer>
58 <AlertDialog.Action onclick={() => handleOpenChange(false)}>Got it</AlertDialog.Action>
59 </AlertDialog.Footer>
60 </AlertDialog.Content>
61</AlertDialog.Root>
diff --git a/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogModelInformation.svelte b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogModelInformation.svelte
new file mode 100644
index 0000000..dfea47c
--- /dev/null
+++ b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogModelInformation.svelte
@@ -0,0 +1,211 @@
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>
diff --git a/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogModelNotAvailable.svelte b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogModelNotAvailable.svelte
new file mode 100644
index 0000000..a6c2029
--- /dev/null
+++ b/llama.cpp/tools/server/webui/src/lib/components/app/dialogs/DialogModelNotAvailable.svelte
@@ -0,0 +1,76 @@
1<script lang="ts">
2 import * as AlertDialog from '$lib/components/ui/alert-dialog';
3 import { AlertTriangle, ArrowRight } from '@lucide/svelte';
4 import { goto } from '$app/navigation';
5 import { page } from '$app/state';
6
7 interface Props {
8 open: boolean;
9 modelName: string;
10 availableModels?: string[];
11 onOpenChange?: (open: boolean) => void;
12 }
13
14 let { open = $bindable(), modelName, availableModels = [], onOpenChange }: Props = $props();
15
16 function handleOpenChange(newOpen: boolean) {
17 open = newOpen;
18 onOpenChange?.(newOpen);
19 }
20
21 function handleSelectModel(model: string) {
22 // Build URL with selected model, preserving other params
23 const url = new URL(page.url);
24 url.searchParams.set('model', model);
25
26 handleOpenChange(false);
27 goto(url.toString());
28 }
29</script>
30
31<AlertDialog.Root {open} onOpenChange={handleOpenChange}>
32 <AlertDialog.Content class="max-w-lg">
33 <AlertDialog.Header>
34 <AlertDialog.Title class="flex items-center gap-2">
35 <AlertTriangle class="h-5 w-5 text-amber-500" />
36 Model Not Available
37 </AlertDialog.Title>
38
39 <AlertDialog.Description>
40 The requested model could not be found. Select an available model to continue.
41 </AlertDialog.Description>
42 </AlertDialog.Header>
43
44 <div class="space-y-3">
45 <div class="rounded-lg border border-amber-500/40 bg-amber-500/10 px-4 py-3 text-sm">
46 <p class="font-medium text-amber-600 dark:text-amber-400">
47 Requested: <code class="rounded bg-amber-500/20 px-1.5 py-0.5">{modelName}</code>
48 </p>
49 </div>
50
51 {#if availableModels.length > 0}
52 <div class="text-sm">
53 <p class="mb-2 font-medium text-muted-foreground">Select an available model:</p>
54 <div class="max-h-48 space-y-1 overflow-y-auto rounded-md border p-1">
55 {#each availableModels as model (model)}
56 <button
57 type="button"
58 class="group flex w-full items-center justify-between gap-2 rounded-sm px-3 py-2 text-left text-sm transition-colors hover:bg-accent hover:text-accent-foreground"
59 onclick={() => handleSelectModel(model)}
60 >
61 <span class="min-w-0 truncate font-mono text-xs">{model}</span>
62 <ArrowRight
63 class="h-4 w-4 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100"
64 />
65 </button>
66 {/each}
67 </div>
68 </div>
69 {/if}
70 </div>
71
72 <AlertDialog.Footer>
73 <AlertDialog.Action onclick={() => handleOpenChange(false)}>Cancel</AlertDialog.Action>
74 </AlertDialog.Footer>
75 </AlertDialog.Content>
76</AlertDialog.Root>