Pre-MCP UI and architecture cleanup (#19685)
* webui: extract non-MCP changes from mcp-mvp review split * webui: extract additional pre-MCP UI and architecture cleanup * chore: update webui build output
This commit is contained in:
parent
ae2d3f28a8
commit
afa6bfe4f7
Binary file not shown.
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { RemoveButton } from '$lib/components/app';
|
import { ActionIconRemove } from '$lib/components/app';
|
||||||
import { formatFileSize, getFileTypeLabel, getPreviewText, isTextFile } from '$lib/utils';
|
import { formatFileSize, getFileTypeLabel, getPreviewText, isTextFile } from '$lib/utils';
|
||||||
import { AttachmentType } from '$lib/enums';
|
import { AttachmentType } from '$lib/enums';
|
||||||
|
|
||||||
|
|
@ -104,7 +104,7 @@
|
||||||
onclick={onClick}
|
onclick={onClick}
|
||||||
>
|
>
|
||||||
<div class="absolute top-2 right-2 opacity-0 transition-opacity group-hover:opacity-100">
|
<div class="absolute top-2 right-2 opacity-0 transition-opacity group-hover:opacity-100">
|
||||||
<RemoveButton {id} {onRemove} />
|
<ActionIconRemove {id} {onRemove} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pr-8">
|
<div class="pr-8">
|
||||||
|
|
@ -158,7 +158,7 @@
|
||||||
|
|
||||||
{#if !readonly}
|
{#if !readonly}
|
||||||
<div class="absolute top-2 right-2 opacity-0 transition-opacity group-hover:opacity-100">
|
<div class="absolute top-2 right-2 opacity-0 transition-opacity group-hover:opacity-100">
|
||||||
<RemoveButton {id} {onRemove} />
|
<ActionIconRemove {id} {onRemove} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { RemoveButton } from '$lib/components/app';
|
import { ActionIconRemove } from '$lib/components/app';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -58,7 +58,7 @@
|
||||||
<div
|
<div
|
||||||
class="absolute top-1 right-1 flex items-center justify-center opacity-0 transition-opacity group-hover:opacity-100"
|
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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
interface Props {
|
interface Props {
|
||||||
class?: string;
|
class?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
onInput?: () => void;
|
||||||
onKeydown?: (event: KeyboardEvent) => void;
|
onKeydown?: (event: KeyboardEvent) => void;
|
||||||
onPaste?: (event: ClipboardEvent) => void;
|
onPaste?: (event: ClipboardEvent) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|
@ -14,6 +15,7 @@
|
||||||
let {
|
let {
|
||||||
class: className = '',
|
class: className = '',
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
onInput,
|
||||||
onKeydown,
|
onKeydown,
|
||||||
onPaste,
|
onPaste,
|
||||||
placeholder = 'Ask anything...',
|
placeholder = 'Ask anything...',
|
||||||
|
|
@ -52,7 +54,10 @@
|
||||||
class:cursor-not-allowed={disabled}
|
class:cursor-not-allowed={disabled}
|
||||||
{disabled}
|
{disabled}
|
||||||
onkeydown={onKeydown}
|
onkeydown={onKeydown}
|
||||||
oninput={(event) => autoResizeTextarea(event.currentTarget)}
|
oninput={(event) => {
|
||||||
|
autoResizeTextarea(event.currentTarget);
|
||||||
|
onInput?.();
|
||||||
|
}}
|
||||||
onpaste={onPaste}
|
onpaste={onPaste}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
></textarea>
|
></textarea>
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,17 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header
|
<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)]'
|
? 'md:left-[var(--sidebar-width)]'
|
||||||
: ''}"
|
: ''}"
|
||||||
>
|
>
|
||||||
<div class="pointer-events-auto flex items-center space-x-2">
|
<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" />
|
<Settings class="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
let isCurrentConversationLoading = $derived(isLoading());
|
let isCurrentConversationLoading = $derived(isLoading());
|
||||||
let isStreaming = $derived(isChatStreaming());
|
let isStreaming = $derived(isChatStreaming());
|
||||||
let hasProcessingData = $derived(processingState.processingState !== null);
|
let hasProcessingData = $derived(processingState.processingState !== null);
|
||||||
let processingDetails = $derived(processingState.getProcessingDetails());
|
let processingDetails = $derived(processingState.getTechnicalDetails());
|
||||||
|
|
||||||
let showProcessingInfo = $derived(
|
let showProcessingInfo = $derived(
|
||||||
isCurrentConversationLoading || isStreaming || config().keepStatsVisible || hasProcessingData
|
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-container pointer-events-none" class:visible={showProcessingInfo}>
|
||||||
<div class="chat-processing-info-content">
|
<div class="chat-processing-info-content">
|
||||||
{#each processingDetails as detail (detail)}
|
{#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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
padding: 1.5rem 1rem;
|
padding: 0 1rem 0.75rem;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(50%);
|
transform: translateY(50%);
|
||||||
transition:
|
transition:
|
||||||
|
|
@ -100,7 +100,6 @@
|
||||||
color: var(--muted-foreground);
|
color: var(--muted-foreground);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
padding: 0.25rem 0.75rem;
|
padding: 0.25rem 0.75rem;
|
||||||
background: var(--muted);
|
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
font-family:
|
font-family:
|
||||||
ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
|
ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Download, Upload, Trash2 } from '@lucide/svelte';
|
import { Download, Upload, Trash2 } from '@lucide/svelte';
|
||||||
import { Button } from '$lib/components/ui/button';
|
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 { createMessageCountMap } from '$lib/utils';
|
||||||
import { conversationsStore, conversations } from '$lib/stores/conversations.svelte';
|
import { conversationsStore, conversations } from '$lib/stores/conversations.svelte';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import DialogConfirmation from '$lib/components/app/dialogs/DialogConfirmation.svelte';
|
|
||||||
|
|
||||||
let exportedConversations = $state<DatabaseConversation[]>([]);
|
let exportedConversations = $state<DatabaseConversation[]>([]);
|
||||||
let importedConversations = $state<DatabaseConversation[]>([]);
|
let importedConversations = $state<DatabaseConversation[]>([]);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
import Input from '$lib/components/ui/input/input.svelte';
|
import Input from '$lib/components/ui/input/input.svelte';
|
||||||
import { conversationsStore, conversations } from '$lib/stores/conversations.svelte';
|
import { conversationsStore, conversations } from '$lib/stores/conversations.svelte';
|
||||||
import { chatStore } from '$lib/stores/chat.svelte';
|
import { chatStore } from '$lib/stores/chat.svelte';
|
||||||
import { getPreviewText } from '$lib/utils/text';
|
import { getPreviewText } from '$lib/utils';
|
||||||
import ChatSidebarActions from './ChatSidebarActions.svelte';
|
import ChatSidebarActions from './ChatSidebarActions.svelte';
|
||||||
|
|
||||||
const sidebar = Sidebar.useSidebar();
|
const sidebar = Sidebar.useSidebar();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Trash2, Pencil, MoreHorizontal, Download, Loader2, Square } from '@lucide/svelte';
|
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 * as Tooltip from '$lib/components/ui/tooltip';
|
||||||
import { getAllLoadingChats } from '$lib/stores/chat.svelte';
|
import { getAllLoadingChats } from '$lib/stores/chat.svelte';
|
||||||
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
import { conversationsStore } from '$lib/stores/conversations.svelte';
|
||||||
|
|
@ -128,7 +128,7 @@
|
||||||
|
|
||||||
{#if renderActionsDropdown}
|
{#if renderActionsDropdown}
|
||||||
<div class="actions flex items-center">
|
<div class="actions flex items-center">
|
||||||
<ActionDropdown
|
<DropdownMenuActions
|
||||||
triggerIcon={MoreHorizontal}
|
triggerIcon={MoreHorizontal}
|
||||||
triggerTooltip="More actions"
|
triggerTooltip="More actions"
|
||||||
bind:open={dropdownOpen}
|
bind:open={dropdownOpen}
|
||||||
|
|
|
||||||
|
|
@ -616,7 +616,7 @@
|
||||||
code={incompleteCodeBlock.code}
|
code={incompleteCodeBlock.code}
|
||||||
language={incompleteCodeBlock.language || 'text'}
|
language={incompleteCodeBlock.language || 'text'}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
onPreview={(code: string, lang: string) => {
|
onPreview={(code, lang) => {
|
||||||
previewCode = code;
|
previewCode = code;
|
||||||
previewLanguage = lang;
|
previewLanguage = lang;
|
||||||
previewDialogOpen = true;
|
previewDialogOpen = true;
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,13 @@ import { ServerRole } from '$lib/enums';
|
||||||
* - **Default Params**: Server-wide generation defaults
|
* - **Default Params**: Server-wide generation defaults
|
||||||
*/
|
*/
|
||||||
class ServerStore {
|
class ServerStore {
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
/**
|
||||||
// State
|
*
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
*
|
||||||
|
* State
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
props = $state<ApiLlamaCppServerProps | null>(null);
|
props = $state<ApiLlamaCppServerProps | null>(null);
|
||||||
loading = $state(false);
|
loading = $state(false);
|
||||||
|
|
@ -28,16 +32,22 @@ class ServerStore {
|
||||||
role = $state<ServerRole | null>(null);
|
role = $state<ServerRole | null>(null);
|
||||||
private fetchPromise: Promise<void> | null = null;
|
private fetchPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
/**
|
||||||
// Getters
|
*
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
*
|
||||||
|
* Getters
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
get defaultParams(): ApiLlamaCppServerProps['default_generation_settings']['params'] | null {
|
get defaultParams(): ApiLlamaCppServerProps['default_generation_settings']['params'] | null {
|
||||||
return this.props?.default_generation_settings?.params || null;
|
return this.props?.default_generation_settings?.params || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get contextSize(): number | 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 {
|
get webuiSettings(): Record<string, string | number | boolean> | undefined {
|
||||||
|
|
@ -52,9 +62,13 @@ class ServerStore {
|
||||||
return this.role === ServerRole.MODEL;
|
return this.role === ServerRole.MODEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
/**
|
||||||
// Data Handling
|
*
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
*
|
||||||
|
* Data Handling
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
async fetch(): Promise<void> {
|
async fetch(): Promise<void> {
|
||||||
if (this.fetchPromise) return this.fetchPromise;
|
if (this.fetchPromise) return this.fetchPromise;
|
||||||
|
|
@ -115,9 +129,13 @@ class ServerStore {
|
||||||
this.fetchPromise = null;
|
this.fetchPromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
/**
|
||||||
// Utilities
|
*
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
*
|
||||||
|
* Utilities
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
private detectRole(props: ApiLlamaCppServerProps): void {
|
private detectRole(props: ApiLlamaCppServerProps): void {
|
||||||
const newRole = props?.role === ServerRole.ROUTER ? ServerRole.ROUTER : ServerRole.MODEL;
|
const newRole = props?.role === ServerRole.ROUTER ? ServerRole.ROUTER : ServerRole.MODEL;
|
||||||
|
|
|
||||||
|
|
@ -47,18 +47,26 @@ import {
|
||||||
} from '$lib/constants/localstorage-keys';
|
} from '$lib/constants/localstorage-keys';
|
||||||
|
|
||||||
class SettingsStore {
|
class SettingsStore {
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
/**
|
||||||
// State
|
*
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
*
|
||||||
|
* State
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
config = $state<SettingsConfigType>({ ...SETTING_CONFIG_DEFAULT });
|
config = $state<SettingsConfigType>({ ...SETTING_CONFIG_DEFAULT });
|
||||||
theme = $state<string>('auto');
|
theme = $state<string>('auto');
|
||||||
isInitialized = $state(false);
|
isInitialized = $state(false);
|
||||||
userOverrides = $state<Set<string>>(new Set());
|
userOverrides = $state<Set<string>>(new Set());
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
/**
|
||||||
// Utilities (private helpers)
|
*
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
*
|
||||||
|
* Utilities (private helpers)
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to get server defaults with null safety
|
* 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
|
* Initialize the settings store by loading from localStorage
|
||||||
|
|
@ -130,9 +142,13 @@ class SettingsStore {
|
||||||
|
|
||||||
this.theme = localStorage.getItem('theme') || 'auto';
|
this.theme = localStorage.getItem('theme') || 'auto';
|
||||||
}
|
}
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
/**
|
||||||
// Config Updates
|
*
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
*
|
||||||
|
* Config Updates
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a specific configuration setting
|
* Update a specific configuration setting
|
||||||
|
|
@ -234,9 +250,13 @@ class SettingsStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
/**
|
||||||
// Reset
|
*
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
*
|
||||||
|
* Reset
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset configuration to defaults
|
* Reset configuration to defaults
|
||||||
|
|
@ -285,9 +305,13 @@ class SettingsStore {
|
||||||
this.saveConfig();
|
this.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
/**
|
||||||
// Server Sync
|
*
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
*
|
||||||
|
* Server Sync
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize settings with props defaults when server properties are first loaded
|
* Initialize settings with props defaults when server properties are first loaded
|
||||||
|
|
@ -349,9 +373,13 @@ class SettingsStore {
|
||||||
this.saveConfig();
|
this.saveConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
/**
|
||||||
// Utilities
|
*
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
*
|
||||||
|
* Utilities
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a specific configuration value
|
* Get a specific configuration value
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,7 @@
|
||||||
<Story
|
<Story
|
||||||
name="Default"
|
name="Default"
|
||||||
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
|
args={{ class: 'max-w-[56rem] w-[calc(100vw-2rem)]' }}
|
||||||
play={async (context) => {
|
play={async ({ canvas, userEvent }) => {
|
||||||
const { canvas, userEvent } = context;
|
|
||||||
const textarea = await canvas.findByRole('textbox');
|
const textarea = await canvas.findByRole('textbox');
|
||||||
const submitButton = await canvas.findByRole('button', { name: 'Send' });
|
const submitButton = await canvas.findByRole('button', { name: 'Send' });
|
||||||
|
|
||||||
|
|
@ -75,8 +74,7 @@
|
||||||
class: 'max-w-[56rem] w-[calc(100vw-2rem)]',
|
class: 'max-w-[56rem] w-[calc(100vw-2rem)]',
|
||||||
uploadedFiles: fileAttachments
|
uploadedFiles: fileAttachments
|
||||||
}}
|
}}
|
||||||
play={async (context) => {
|
play={async ({ canvas }) => {
|
||||||
const { canvas } = context;
|
|
||||||
const jpgAttachment = canvas.getByAltText('1.jpg');
|
const jpgAttachment = canvas.getByAltText('1.jpg');
|
||||||
const svgAttachment = canvas.getByAltText('hf-logo.svg');
|
const svgAttachment = canvas.getByAltText('hf-logo.svg');
|
||||||
const pdfFileExtension = canvas.getByText('PDF');
|
const pdfFileExtension = canvas.getByText('PDF');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue