]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/commitdiff
Pre-MCP UI and architecture cleanup (#19685)
authorAleksander Grygier <redacted>
Tue, 17 Feb 2026 12:47:45 +0000 (13:47 +0100)
committerGitHub <redacted>
Tue, 17 Feb 2026 12:47:45 +0000 (13:47 +0100)
* webui: extract non-MCP changes from mcp-mvp review split

* webui: extract additional pre-MCP UI and architecture cleanup

* chore: update webui build output

13 files changed:
tools/server/public/index.html.gz
tools/server/webui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentThumbnailFile.svelte
tools/server/webui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentThumbnailImage.svelte
tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormTextarea.svelte
tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreenHeader.svelte
tools/server/webui/src/lib/components/app/chat/ChatScreen/ChatScreenProcessingInfo.svelte
tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsImportExportTab.svelte
tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebar.svelte
tools/server/webui/src/lib/components/app/chat/ChatSidebar/ChatSidebarConversationItem.svelte
tools/server/webui/src/lib/components/app/content/MarkdownContent.svelte
tools/server/webui/src/lib/stores/server.svelte.ts
tools/server/webui/src/lib/stores/settings.svelte.ts
tools/server/webui/tests/stories/ChatScreenForm.stories.svelte

index 75fc856f5452ceb57268e4237a2bc9987e76df05..05dfd9f17ac38b8db895851d6b2d57985ba6ce12 100644 (file)
Binary files a/tools/server/public/index.html.gz and b/tools/server/public/index.html.gz differ
index 908db5894bbfa6a1b6e03e41019aa6fa442ff4fc..9d32ea0721a00c92c9c3840ea09a88b65cd0143e 100644 (file)
@@ -1,5 +1,5 @@
 <script lang="ts">
-       import { RemoveButton } from '$lib/components/app';
+       import { ActionIconRemove } from '$lib/components/app';
        import { formatFileSize, getFileTypeLabel, getPreviewText, isTextFile } from '$lib/utils';
        import { AttachmentType } from '$lib/enums';
 
                        onclick={onClick}
                >
                        <div class="absolute top-2 right-2 opacity-0 transition-opacity group-hover:opacity-100">
-                               <RemoveButton {id} {onRemove} />
+                               <ActionIconRemove {id} {onRemove} />
                        </div>
 
                        <div class="pr-8">
 
                {#if !readonly}
                        <div class="absolute top-2 right-2 opacity-0 transition-opacity group-hover:opacity-100">
-                               <RemoveButton {id} {onRemove} />
+                               <ActionIconRemove {id} {onRemove} />
                        </div>
                {/if}
        </button>
index ba711a9d7b174cf651938807ed40bbde0bfaf112..d0419db30c48a2e92a1943d200bec29e1f2cef99 100644 (file)
@@ -1,5 +1,5 @@
 <script lang="ts">
-       import { RemoveButton } from '$lib/components/app';
+       import { ActionIconRemove } from '$lib/components/app';
 
        interface Props {
                id: string;
@@ -58,7 +58,7 @@
                <div
                        class="absolute top-1 right-1 flex items-center justify-center opacity-0 transition-opacity group-hover:opacity-100"
                >
-                       <RemoveButton {id} {onRemove} class="text-white" />
+                       <ActionIconRemove {id} {onRemove} class="text-white" />
                </div>
        {/if}
 </div>
index 19b763f55ef42c05af902fea266fce5b5586ffe1..f0855b9dbe5df32b492160175a67a732f3277458 100644 (file)
@@ -5,6 +5,7 @@
        interface Props {
                class?: string;
                disabled?: boolean;
+               onInput?: () => void;
                onKeydown?: (event: KeyboardEvent) => void;
                onPaste?: (event: ClipboardEvent) => void;
                placeholder?: string;
@@ -14,6 +15,7 @@
        let {
                class: className = '',
                disabled = false,
+               onInput,
                onKeydown,
                onPaste,
                placeholder = 'Ask anything...',
                class:cursor-not-allowed={disabled}
                {disabled}
                onkeydown={onKeydown}
-               oninput={(event) => autoResizeTextarea(event.currentTarget)}
+               oninput={(event) => {
+                       autoResizeTextarea(event.currentTarget);
+                       onInput?.();
+               }}
                onpaste={onPaste}
                {placeholder}
        ></textarea>
index 874140feecc0e4682e8a2cce8e2f51d179a56735..4909d6045739c358d4b8f6a82cb2b5273971d9f4 100644 (file)
 </script>
 
 <header
-       class="md:background-transparent pointer-events-none fixed top-0 right-0 left-0 z-50 flex items-center justify-end bg-background/40 p-4 backdrop-blur-xl duration-200 ease-linear {sidebar.open
+       class="pointer-events-none fixed top-0 right-0 left-0 z-50 flex items-center justify-end p-4 duration-200 ease-linear {sidebar.open
                ? 'md:left-[var(--sidebar-width)]'
                : ''}"
 >
        <div class="pointer-events-auto flex items-center space-x-2">
-               <Button variant="ghost" size="sm" onclick={toggleSettings}>
+               <Button
+                       variant="ghost"
+                       size="icon"
+                       onclick={toggleSettings}
+                       class="rounded-full backdrop-blur-lg"
+               >
                        <Settings class="h-4 w-4" />
                </Button>
        </div>
index a60ae9e9af3077192bb95d2ef182e0034b07a413..cc7b22cfd8d2910c30f2ca6fcafa7be516be09e7 100644 (file)
@@ -11,7 +11,7 @@
        let isCurrentConversationLoading = $derived(isLoading());
        let isStreaming = $derived(isChatStreaming());
        let hasProcessingData = $derived(processingState.processingState !== null);
-       let processingDetails = $derived(processingState.getProcessingDetails());
+       let processingDetails = $derived(processingState.getTechnicalDetails());
 
        let showProcessingInfo = $derived(
                isCurrentConversationLoading || isStreaming || config().keepStatsVisible || hasProcessingData
@@ -63,7 +63,7 @@
 <div class="chat-processing-info-container pointer-events-none" class:visible={showProcessingInfo}>
        <div class="chat-processing-info-content">
                {#each processingDetails as detail (detail)}
-                       <span class="chat-processing-info-detail pointer-events-auto">{detail}</span>
+                       <span class="chat-processing-info-detail pointer-events-auto backdrop-blur-sm">{detail}</span>
                {/each}
        </div>
 </div>
@@ -73,7 +73,7 @@
                position: sticky;
                top: 0;
                z-index: 10;
-               padding: 1.5rem 1rem;
+               padding: 0 1rem 0.75rem;
                opacity: 0;
                transform: translateY(50%);
                transition:
                color: var(--muted-foreground);
                font-size: 0.75rem;
                padding: 0.25rem 0.75rem;
-               background: var(--muted);
                border-radius: 0.375rem;
                font-family:
                        ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
index 1c8b41102a87da3d53b86088d2620cf76ec4a76e..68839438f6dd9862e99395df5aee67db05fca228 100644 (file)
@@ -1,11 +1,10 @@
 <script lang="ts">
        import { Download, Upload, Trash2 } from '@lucide/svelte';
        import { Button } from '$lib/components/ui/button';
-       import { DialogConversationSelection } from '$lib/components/app';
+       import { DialogConversationSelection, DialogConfirmation } from '$lib/components/app';
        import { createMessageCountMap } from '$lib/utils';
        import { conversationsStore, conversations } from '$lib/stores/conversations.svelte';
        import { toast } from 'svelte-sonner';
-       import DialogConfirmation from '$lib/components/app/dialogs/DialogConfirmation.svelte';
 
        let exportedConversations = $state<DatabaseConversation[]>([]);
        let importedConversations = $state<DatabaseConversation[]>([]);
index aa0c27f6d391d726162f583c80528d370e35e38b..970394baa4a4779e772c23b4b40f71aee36f3f58 100644 (file)
@@ -9,7 +9,7 @@
        import Input from '$lib/components/ui/input/input.svelte';
        import { conversationsStore, conversations } from '$lib/stores/conversations.svelte';
        import { chatStore } from '$lib/stores/chat.svelte';
-       import { getPreviewText } from '$lib/utils/text';
+       import { getPreviewText } from '$lib/utils';
        import ChatSidebarActions from './ChatSidebarActions.svelte';
 
        const sidebar = Sidebar.useSidebar();
index d9c71dba8ee21b8e2c5e99238479c1fab35be8a0..5c48909cd81bc73192f2021be9e9f2b1ede9b05b 100644 (file)
@@ -1,6 +1,6 @@
 <script lang="ts">
        import { Trash2, Pencil, MoreHorizontal, Download, Loader2, Square } from '@lucide/svelte';
-       import { ActionDropdown } from '$lib/components/app';
+       import { DropdownMenuActions } from '$lib/components/app';
        import * as Tooltip from '$lib/components/ui/tooltip';
        import { getAllLoadingChats } from '$lib/stores/chat.svelte';
        import { conversationsStore } from '$lib/stores/conversations.svelte';
 
        {#if renderActionsDropdown}
                <div class="actions flex items-center">
-                       <ActionDropdown
+                       <DropdownMenuActions
                                triggerIcon={MoreHorizontal}
                                triggerTooltip="More actions"
                                bind:open={dropdownOpen}
index 022a1a45bb8f870f2e6018de312b1c8c32073403..0bc69a739f797173753cd50abaf0e893281e050a 100644 (file)
                                        code={incompleteCodeBlock.code}
                                        language={incompleteCodeBlock.language || 'text'}
                                        disabled={true}
-                                       onPreview={(code: string, lang: string) => {
+                                       onPreview={(code, lang) => {
                                                previewCode = code;
                                                previewLanguage = lang;
                                                previewDialogOpen = true;
index 7bac9ca156b5bc1da61961eee11f65bfa39d5e42..48874bf1b7cdb3957e8dbda9af98f073b68e788a 100644 (file)
@@ -18,9 +18,13 @@ import { ServerRole } from '$lib/enums';
  * - **Default Params**: Server-wide generation defaults
  */
 class ServerStore {
-       // ─────────────────────────────────────────────────────────────────────────────
-       // State
-       // ─────────────────────────────────────────────────────────────────────────────
+       /**
+        *
+        *
+        * State
+        *
+        *
+        */
 
        props = $state<ApiLlamaCppServerProps | null>(null);
        loading = $state(false);
@@ -28,16 +32,22 @@ class ServerStore {
        role = $state<ServerRole | null>(null);
        private fetchPromise: Promise<void> | null = null;
 
-       // ─────────────────────────────────────────────────────────────────────────────
-       // Getters
-       // ─────────────────────────────────────────────────────────────────────────────
+       /**
+        *
+        *
+        * Getters
+        *
+        *
+        */
 
        get defaultParams(): ApiLlamaCppServerProps['default_generation_settings']['params'] | null {
                return this.props?.default_generation_settings?.params || null;
        }
 
        get contextSize(): number | null {
-               return this.props?.default_generation_settings?.n_ctx ?? null;
+               const nCtx = this.props?.default_generation_settings?.n_ctx;
+
+               return typeof nCtx === 'number' ? nCtx : null;
        }
 
        get webuiSettings(): Record<string, string | number | boolean> | undefined {
@@ -52,9 +62,13 @@ class ServerStore {
                return this.role === ServerRole.MODEL;
        }
 
-       // ─────────────────────────────────────────────────────────────────────────────
-       // Data Handling
-       // ─────────────────────────────────────────────────────────────────────────────
+       /**
+        *
+        *
+        * Data Handling
+        *
+        *
+        */
 
        async fetch(): Promise<void> {
                if (this.fetchPromise) return this.fetchPromise;
@@ -115,9 +129,13 @@ class ServerStore {
                this.fetchPromise = null;
        }
 
-       // ─────────────────────────────────────────────────────────────────────────────
-       // Utilities
-       // ─────────────────────────────────────────────────────────────────────────────
+       /**
+        *
+        *
+        * Utilities
+        *
+        *
+        */
 
        private detectRole(props: ApiLlamaCppServerProps): void {
                const newRole = props?.role === ServerRole.ROUTER ? ServerRole.ROUTER : ServerRole.MODEL;
index 5fb5055d8c95227396e0b02c20565ef9903bf16f..68431f4e32ebfee090a0532608606e0bcceccb07 100644 (file)
@@ -47,18 +47,26 @@ import {
 } from '$lib/constants/localstorage-keys';
 
 class SettingsStore {
-       // ─────────────────────────────────────────────────────────────────────────────
-       // State
-       // ─────────────────────────────────────────────────────────────────────────────
+       /**
+        *
+        *
+        * State
+        *
+        *
+        */
 
        config = $state<SettingsConfigType>({ ...SETTING_CONFIG_DEFAULT });
        theme = $state<string>('auto');
        isInitialized = $state(false);
        userOverrides = $state<Set<string>>(new Set());
 
-       // ─────────────────────────────────────────────────────────────────────────────
-       // Utilities (private helpers)
-       // ─────────────────────────────────────────────────────────────────────────────
+       /**
+        *
+        *
+        * Utilities (private helpers)
+        *
+        *
+        */
 
        /**
         * Helper method to get server defaults with null safety
@@ -76,9 +84,13 @@ class SettingsStore {
                }
        }
 
-       // ─────────────────────────────────────────────────────────────────────────────
-       // Lifecycle
-       // ─────────────────────────────────────────────────────────────────────────────
+       /**
+        *
+        *
+        * Lifecycle
+        *
+        *
+        */
 
        /**
         * Initialize the settings store by loading from localStorage
@@ -130,9 +142,13 @@ class SettingsStore {
 
                this.theme = localStorage.getItem('theme') || 'auto';
        }
-       // ─────────────────────────────────────────────────────────────────────────────
-       // Config Updates
-       // ─────────────────────────────────────────────────────────────────────────────
+       /**
+        *
+        *
+        * Config Updates
+        *
+        *
+        */
 
        /**
         * Update a specific configuration setting
@@ -234,9 +250,13 @@ class SettingsStore {
                }
        }
 
-       // ─────────────────────────────────────────────────────────────────────────────
-       // Reset
-       // ─────────────────────────────────────────────────────────────────────────────
+       /**
+        *
+        *
+        * Reset
+        *
+        *
+        */
 
        /**
         * Reset configuration to defaults
@@ -285,9 +305,13 @@ class SettingsStore {
                this.saveConfig();
        }
 
-       // ─────────────────────────────────────────────────────────────────────────────
-       // Server Sync
-       // ─────────────────────────────────────────────────────────────────────────────
+       /**
+        *
+        *
+        * Server Sync
+        *
+        *
+        */
 
        /**
         * Initialize settings with props defaults when server properties are first loaded
@@ -349,9 +373,13 @@ class SettingsStore {
                this.saveConfig();
        }
 
-       // ─────────────────────────────────────────────────────────────────────────────
-       // Utilities
-       // ─────────────────────────────────────────────────────────────────────────────
+       /**
+        *
+        *
+        * Utilities
+        *
+        *
+        */
 
        /**
         * Get a specific configuration value
index f3cbde2173dabe6b69c51ed9ea0ce23c6facced3..4c1734345955d7da3440d303614564c4c74af5af 100644 (file)
@@ -44,8 +44,7 @@
 <Story
        name="Default"
        args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
-       play={async (context) => {
-               const { canvas, userEvent } = context;
+       play={async ({ canvas, userEvent }) => {
                const textarea = await canvas.findByRole('textbox');
                const submitButton = await canvas.findByRole('button', { name: 'Send' });
 
@@ -75,8 +74,7 @@
                class: 'max-w-[56rem] w-[calc(100vw-2rem)]',
                uploadedFiles: fileAttachments
        }}
-       play={async (context) => {
-               const { canvas } = context;
+       play={async ({ canvas }) => {
                const jpgAttachment = canvas.getByAltText('1.jpg');
                const svgAttachment = canvas.getByAltText('hf-logo.svg');
                const pdfFileExtension = canvas.getByText('PDF');