]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/commitdiff
Webui: Disable attachment button and model selector button when prompt textbox is...
authorDarius Lukas <redacted>
Tue, 16 Dec 2025 06:15:49 +0000 (01:15 -0500)
committerGitHub <redacted>
Tue, 16 Dec 2025 06:15:49 +0000 (07:15 +0100)
* Pass disabled state to the file attachments button and the model
selector button.

* Update index.html.gz

* Fix model info card in non-router mode.

* Update index.html.gz

tools/server/public/index.html.gz
tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActionFileAttachments.svelte
tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte
tools/server/webui/src/lib/components/app/models/ModelsSelector.svelte

index 036feff1c3e182484fa80cc64e53e386a4560558..85be6c4c001e97e8a1c06eae8f032c829379e94e 100644 (file)
Binary files a/tools/server/public/index.html.gz and b/tools/server/public/index.html.gz differ
index f4aa8a3a3f02d8023e760d0e06253a16d2a3977e..127130fb8470e324371978c4d8226a94b3a6aadd 100644 (file)
@@ -35,7 +35,7 @@
 
 <div class="flex items-center gap-1 {className}">
        <DropdownMenu.Root>
-               <DropdownMenu.Trigger name="Attach files">
+               <DropdownMenu.Trigger name="Attach files" {disabled}>
                        <Tooltip.Root>
                                <Tooltip.Trigger>
                                        <Button
index 8607e00c02534839d9f2b397bc945a1f0f8105e3..730c348b3025372f24db0073140a68bcb8785a47 100644 (file)
        />
 
        <ModelsSelector
+               {disabled}
                bind:this={selectorModelRef}
                currentModel={conversationModel}
                forceForegroundText={true}
index ac0937696d449c777d21c2ae333bacc885783821..efc9cd4e2f8085bd65ce86707f509fdfebd369e7 100644 (file)
                });
        });
 
+       // Handle changes to the model selector pop-down or the model dialog, depending on if the server is in
+       // router mode or not.
        function handleOpenChange(open: boolean) {
                if (loading || updating) return;
 
-               if (open) {
-                       isOpen = true;
-                       searchTerm = '';
-                       highlightedIndex = -1;
-
-                       // Focus search input after popover opens
-                       tick().then(() => {
-                               requestAnimationFrame(() => searchInputRef?.focus());
-                       });
+               if (isRouter) {
+                       if (open) {
+                               isOpen = true;
+                               searchTerm = '';
+                               highlightedIndex = -1;
+
+                               // Focus search input after popover opens
+                               tick().then(() => {
+                                       requestAnimationFrame(() => searchInputRef?.focus());
+                               });
 
-                       if (isRouter) {
                                modelsStore.fetchRouterModels().then(() => {
                                        modelsStore.fetchModalitiesForLoadedModels();
                                });
+                       } else {
+                               isOpen = false;
+                               searchTerm = '';
+                               highlightedIndex = -1;
                        }
                } else {
-                       isOpen = false;
-                       searchTerm = '';
-                       highlightedIndex = -1;
-               }
-       }
-
-       function handleTriggerClick() {
-               if (loading || updating) return;
-
-               if (!isRouter) {
-                       // Single model mode: show dialog instead of popover
-                       showModelDialog = true;
+                       showModelDialog = open;
                }
-               // For router mode, the Popover handles open/close
        }
 
        export function open() {
-               if (isRouter) {
-                       handleOpenChange(true);
-               } else {
-                       showModelDialog = true;
-               }
-       }
-
-       function closeMenu() {
-               handleOpenChange(false);
+               handleOpenChange(true);
        }
 
        function handleSearchKeyDown(event: KeyboardEvent) {
                }
 
                if (shouldCloseMenu) {
-                       closeMenu();
+                       handleOpenChange(false);
 
                        // Focus the chat textarea after model selection
                        requestAnimationFrame(() => {
        {:else}
                {@const selectedOption = getDisplayOption()}
 
-               <Popover.Root bind:open={isOpen} onOpenChange={handleOpenChange}>
-                       <Popover.Trigger
+               {#if isRouter}
+                       <Popover.Root bind:open={isOpen} onOpenChange={handleOpenChange}>
+                               <Popover.Trigger
+                                       class={cn(
+                                               `inline-flex cursor-pointer items-center gap-1.5 rounded-sm bg-muted-foreground/10 px-1.5 py-1 text-xs transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60`,
+                                               !isCurrentModelInCache()
+                                                       ? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
+                                                       : forceForegroundText
+                                                               ? 'text-foreground'
+                                                               : isHighlightedCurrentModelActive
+                                                                       ? 'text-foreground'
+                                                                       : 'text-muted-foreground',
+                                               isOpen ? 'text-foreground' : ''
+                                       )}
+                                       style="max-width: min(calc(100cqw - 6.5rem), 32rem)"
+                                       disabled={disabled || updating}
+                               >
+                                       <Package class="h-3.5 w-3.5" />
+
+                                       <span class="truncate font-medium">
+                                               {selectedOption?.model || 'Select model'}
+                                       </span>
+
+                                       {#if updating}
+                                               <Loader2 class="h-3 w-3.5 animate-spin" />
+                                       {:else}
+                                               <ChevronDown class="h-3 w-3.5" />
+                                       {/if}
+                               </Popover.Trigger>
+
+                               <Popover.Content
+                                       class="group/popover-content w-96 max-w-[calc(100vw-2rem)] p-0"
+                                       align="end"
+                                       sideOffset={8}
+                                       collisionPadding={16}
+                               >
+                                       <div class="flex max-h-[50dvh] flex-col overflow-hidden">
+                                               <div
+                                                       class="order-1 shrink-0 border-b p-4 group-data-[side=top]/popover-content:order-2 group-data-[side=top]/popover-content:border-t group-data-[side=top]/popover-content:border-b-0"
+                                               >
+                                                       <SearchInput
+                                                               id="model-search"
+                                                               placeholder="Search models..."
+                                                               bind:value={searchTerm}
+                                                               bind:ref={searchInputRef}
+                                                               onClose={() => handleOpenChange(false)}
+                                                               onKeyDown={handleSearchKeyDown}
+                                                       />
+                                               </div>
+                                               <div
+                                                       class="models-list order-2 min-h-0 flex-1 overflow-y-auto group-data-[side=top]/popover-content:order-1"
+                                               >
+                                                       {#if !isCurrentModelInCache() && currentModel}
+                                                               <!-- Show unavailable model as first option (disabled) -->
+                                                               <button
+                                                                       type="button"
+                                                                       class="flex w-full cursor-not-allowed items-center bg-red-400/10 px-4 py-2 text-left text-sm text-red-400"
+                                                                       role="option"
+                                                                       aria-selected="true"
+                                                                       aria-disabled="true"
+                                                                       disabled
+                                                               >
+                                                                       <span class="truncate">{selectedOption?.name || currentModel}</span>
+                                                                       <span class="ml-2 text-xs whitespace-nowrap opacity-70">(not available)</span>
+                                                               </button>
+                                                               <div class="my-1 h-px bg-border"></div>
+                                                       {/if}
+                                                       {#if filteredOptions.length === 0}
+                                                               <p class="px-4 py-3 text-sm text-muted-foreground">No models found.</p>
+                                                       {/if}
+                                                       {#each filteredOptions as option, index (option.id)}
+                                                               {@const status = getModelStatus(option.model)}
+                                                               {@const isLoaded = status === ServerModelStatus.LOADED}
+                                                               {@const isLoading = status === ServerModelStatus.LOADING}
+                                                               {@const isSelected = currentModel === option.model || activeId === option.id}
+                                                               {@const isCompatible = isModelCompatible(option)}
+                                                               {@const isHighlighted = index === highlightedIndex}
+                                                               {@const missingModalities = getMissingModalities(option)}
+
+                                                               <div
+                                                                       class={cn(
+                                                                               'group flex w-full items-center gap-2 px-4 py-2 text-left text-sm transition focus:outline-none',
+                                                                               isCompatible
+                                                                                       ? 'cursor-pointer hover:bg-muted focus:bg-muted'
+                                                                                       : 'cursor-not-allowed opacity-50',
+                                                                               isSelected || isHighlighted
+                                                                                       ? 'bg-accent text-accent-foreground'
+                                                                                       : isCompatible
+                                                                                               ? 'hover:bg-accent hover:text-accent-foreground'
+                                                                                               : '',
+                                                                               isLoaded ? 'text-popover-foreground' : 'text-muted-foreground'
+                                                                       )}
+                                                                       role="option"
+                                                                       aria-selected={isSelected || isHighlighted}
+                                                                       aria-disabled={!isCompatible}
+                                                                       tabindex={isCompatible ? 0 : -1}
+                                                                       onclick={() => isCompatible && handleSelect(option.id)}
+                                                                       onmouseenter={() => (highlightedIndex = index)}
+                                                                       onkeydown={(e) => {
+                                                                               if (isCompatible && (e.key === 'Enter' || e.key === ' ')) {
+                                                                                       e.preventDefault();
+                                                                                       handleSelect(option.id);
+                                                                               }
+                                                                       }}
+                                                               >
+                                                                       <span class="min-w-0 flex-1 truncate">{option.model}</span>
+
+                                                                       {#if missingModalities}
+                                                                               <span class="flex shrink-0 items-center gap-1 text-muted-foreground/70">
+                                                                                       {#if missingModalities.vision}
+                                                                                               <Tooltip.Root>
+                                                                                                       <Tooltip.Trigger>
+                                                                                                               <EyeOff class="h-3.5 w-3.5" />
+                                                                                                       </Tooltip.Trigger>
+                                                                                                       <Tooltip.Content class="z-[9999]">
+                                                                                                               <p>No vision support</p>
+                                                                                                       </Tooltip.Content>
+                                                                                               </Tooltip.Root>
+                                                                                       {/if}
+                                                                                       {#if missingModalities.audio}
+                                                                                               <Tooltip.Root>
+                                                                                                       <Tooltip.Trigger>
+                                                                                                               <MicOff class="h-3.5 w-3.5" />
+                                                                                                       </Tooltip.Trigger>
+                                                                                                       <Tooltip.Content class="z-[9999]">
+                                                                                                               <p>No audio support</p>
+                                                                                                       </Tooltip.Content>
+                                                                                               </Tooltip.Root>
+                                                                                       {/if}
+                                                                               </span>
+                                                                       {/if}
+
+                                                                       {#if isLoading}
+                                                                               <Tooltip.Root>
+                                                                                       <Tooltip.Trigger>
+                                                                                               <Loader2 class="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
+                                                                                       </Tooltip.Trigger>
+                                                                                       <Tooltip.Content class="z-[9999]">
+                                                                                               <p>Loading model...</p>
+                                                                                       </Tooltip.Content>
+                                                                               </Tooltip.Root>
+                                                                       {:else if isLoaded}
+                                                                               <Tooltip.Root>
+                                                                                       <Tooltip.Trigger>
+                                                                                               <button
+                                                                                                       type="button"
+                                                                                                       class="relative ml-2 flex h-4 w-4 shrink-0 items-center justify-center"
+                                                                                                       onclick={(e) => {
+                                                                                                               e.stopPropagation();
+                                                                                                               modelsStore.unloadModel(option.model);
+                                                                                                       }}
+                                                                                               >
+                                                                                                       <span
+                                                                                                               class="mr-2 h-2 w-2 rounded-full bg-green-500 transition-opacity group-hover:opacity-0"
+                                                                                                       ></span>
+                                                                                                       <Power
+                                                                                                               class="absolute mr-2 h-4 w-4 text-red-500 opacity-0 transition-opacity group-hover:opacity-100 hover:text-red-600"
+                                                                                                       />
+                                                                                               </button>
+                                                                                       </Tooltip.Trigger>
+                                                                                       <Tooltip.Content class="z-[9999]">
+                                                                                               <p>Unload model</p>
+                                                                                       </Tooltip.Content>
+                                                                               </Tooltip.Root>
+                                                                       {:else}
+                                                                               <span class="mx-2 h-2 w-2 rounded-full bg-muted-foreground/50"></span>
+                                                                       {/if}
+                                                               </div>
+                                                       {/each}
+                                               </div>
+                                       </div>
+                               </Popover.Content>
+                       </Popover.Root>
+               {:else}
+                       <button
                                class={cn(
                                        `inline-flex cursor-pointer items-center gap-1.5 rounded-sm bg-muted-foreground/10 px-1.5 py-1 text-xs transition hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60`,
                                        !isCurrentModelInCache()
                                        isOpen ? 'text-foreground' : ''
                                )}
                                style="max-width: min(calc(100cqw - 6.5rem), 32rem)"
-                               onclick={handleTriggerClick}
-                               disabled={disabled || updating || !isRouter}
+                               onclick={() => handleOpenChange(true)}
+                               disabled={disabled || updating}
                        >
                                <Package class="h-3.5 w-3.5" />
 
                                <span class="truncate font-medium">
-                                       {selectedOption?.model || 'Select model'}
+                                       {selectedOption?.model}
                                </span>
 
                                {#if updating}
                                        <Loader2 class="h-3 w-3.5 animate-spin" />
-                               {:else if isRouter}
-                                       <ChevronDown class="h-3 w-3.5" />
                                {/if}
-                       </Popover.Trigger>
-
-                       <Popover.Content
-                               class="group/popover-content w-96 max-w-[calc(100vw-2rem)] p-0"
-                               align="end"
-                               sideOffset={8}
-                               collisionPadding={16}
-                       >
-                               <div class="flex max-h-[50dvh] flex-col overflow-hidden">
-                                       <div
-                                               class="order-1 shrink-0 border-b p-4 group-data-[side=top]/popover-content:order-2 group-data-[side=top]/popover-content:border-t group-data-[side=top]/popover-content:border-b-0"
-                                       >
-                                               <SearchInput
-                                                       id="model-search"
-                                                       placeholder="Search models..."
-                                                       bind:value={searchTerm}
-                                                       bind:ref={searchInputRef}
-                                                       onClose={closeMenu}
-                                                       onKeyDown={handleSearchKeyDown}
-                                               />
-                                       </div>
-                                       <div
-                                               class="models-list order-2 min-h-0 flex-1 overflow-y-auto group-data-[side=top]/popover-content:order-1"
-                                       >
-                                               {#if !isCurrentModelInCache() && currentModel}
-                                                       <!-- Show unavailable model as first option (disabled) -->
-                                                       <button
-                                                               type="button"
-                                                               class="flex w-full cursor-not-allowed items-center bg-red-400/10 px-4 py-2 text-left text-sm text-red-400"
-                                                               role="option"
-                                                               aria-selected="true"
-                                                               aria-disabled="true"
-                                                               disabled
-                                                       >
-                                                               <span class="truncate">{selectedOption?.name || currentModel}</span>
-                                                               <span class="ml-2 text-xs whitespace-nowrap opacity-70">(not available)</span>
-                                                       </button>
-                                                       <div class="my-1 h-px bg-border"></div>
-                                               {/if}
-                                               {#if filteredOptions.length === 0}
-                                                       <p class="px-4 py-3 text-sm text-muted-foreground">No models found.</p>
-                                               {/if}
-                                               {#each filteredOptions as option, index (option.id)}
-                                                       {@const status = getModelStatus(option.model)}
-                                                       {@const isLoaded = status === ServerModelStatus.LOADED}
-                                                       {@const isLoading = status === ServerModelStatus.LOADING}
-                                                       {@const isSelected = currentModel === option.model || activeId === option.id}
-                                                       {@const isCompatible = isModelCompatible(option)}
-                                                       {@const isHighlighted = index === highlightedIndex}
-                                                       {@const missingModalities = getMissingModalities(option)}
-
-                                                       <div
-                                                               class={cn(
-                                                                       'group flex w-full items-center gap-2 px-4 py-2 text-left text-sm transition focus:outline-none',
-                                                                       isCompatible
-                                                                               ? 'cursor-pointer hover:bg-muted focus:bg-muted'
-                                                                               : 'cursor-not-allowed opacity-50',
-                                                                       isSelected || isHighlighted
-                                                                               ? 'bg-accent text-accent-foreground'
-                                                                               : isCompatible
-                                                                                       ? 'hover:bg-accent hover:text-accent-foreground'
-                                                                                       : '',
-                                                                       isLoaded ? 'text-popover-foreground' : 'text-muted-foreground'
-                                                               )}
-                                                               role="option"
-                                                               aria-selected={isSelected || isHighlighted}
-                                                               aria-disabled={!isCompatible}
-                                                               tabindex={isCompatible ? 0 : -1}
-                                                               onclick={() => isCompatible && handleSelect(option.id)}
-                                                               onmouseenter={() => (highlightedIndex = index)}
-                                                               onkeydown={(e) => {
-                                                                       if (isCompatible && (e.key === 'Enter' || e.key === ' ')) {
-                                                                               e.preventDefault();
-                                                                               handleSelect(option.id);
-                                                                       }
-                                                               }}
-                                                       >
-                                                               <span class="min-w-0 flex-1 truncate">{option.model}</span>
-
-                                                               {#if missingModalities}
-                                                                       <span class="flex shrink-0 items-center gap-1 text-muted-foreground/70">
-                                                                               {#if missingModalities.vision}
-                                                                                       <Tooltip.Root>
-                                                                                               <Tooltip.Trigger>
-                                                                                                       <EyeOff class="h-3.5 w-3.5" />
-                                                                                               </Tooltip.Trigger>
-                                                                                               <Tooltip.Content class="z-[9999]">
-                                                                                                       <p>No vision support</p>
-                                                                                               </Tooltip.Content>
-                                                                                       </Tooltip.Root>
-                                                                               {/if}
-                                                                               {#if missingModalities.audio}
-                                                                                       <Tooltip.Root>
-                                                                                               <Tooltip.Trigger>
-                                                                                                       <MicOff class="h-3.5 w-3.5" />
-                                                                                               </Tooltip.Trigger>
-                                                                                               <Tooltip.Content class="z-[9999]">
-                                                                                                       <p>No audio support</p>
-                                                                                               </Tooltip.Content>
-                                                                                       </Tooltip.Root>
-                                                                               {/if}
-                                                                       </span>
-                                                               {/if}
-
-                                                               {#if isLoading}
-                                                                       <Tooltip.Root>
-                                                                               <Tooltip.Trigger>
-                                                                                       <Loader2 class="h-4 w-4 shrink-0 animate-spin text-muted-foreground" />
-                                                                               </Tooltip.Trigger>
-                                                                               <Tooltip.Content class="z-[9999]">
-                                                                                       <p>Loading model...</p>
-                                                                               </Tooltip.Content>
-                                                                       </Tooltip.Root>
-                                                               {:else if isLoaded}
-                                                                       <Tooltip.Root>
-                                                                               <Tooltip.Trigger>
-                                                                                       <button
-                                                                                               type="button"
-                                                                                               class="relative ml-2 flex h-4 w-4 shrink-0 items-center justify-center"
-                                                                                               onclick={(e) => {
-                                                                                                       e.stopPropagation();
-                                                                                                       modelsStore.unloadModel(option.model);
-                                                                                               }}
-                                                                                       >
-                                                                                               <span
-                                                                                                       class="mr-2 h-2 w-2 rounded-full bg-green-500 transition-opacity group-hover:opacity-0"
-                                                                                               ></span>
-                                                                                               <Power
-                                                                                                       class="absolute mr-2 h-4 w-4 text-red-500 opacity-0 transition-opacity group-hover:opacity-100 hover:text-red-600"
-                                                                                               />
-                                                                                       </button>
-                                                                               </Tooltip.Trigger>
-                                                                               <Tooltip.Content class="z-[9999]">
-                                                                                       <p>Unload model</p>
-                                                                               </Tooltip.Content>
-                                                                       </Tooltip.Root>
-                                                               {:else}
-                                                                       <span class="mx-2 h-2 w-2 rounded-full bg-muted-foreground/50"></span>
-                                                               {/if}
-                                                       </div>
-                                               {/each}
-                                       </div>
-                               </div>
-                       </Popover.Content>
-               </Popover.Root>
+                       </button>
+               {/if}
        {/if}
 </div>