<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>
<script lang="ts">
- import { RemoveButton } from '$lib/components/app';
+ import { ActionIconRemove } from '$lib/components/app';
interface Props {
id: string;
<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>
interface Props {
class?: string;
disabled?: boolean;
+ onInput?: () => void;
onKeydown?: (event: KeyboardEvent) => void;
onPaste?: (event: ClipboardEvent) => void;
placeholder?: string;
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>
</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>
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
<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>
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;
<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[]>([]);
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();
<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}
code={incompleteCodeBlock.code}
language={incompleteCodeBlock.language || 'text'}
disabled={true}
- onPreview={(code: string, lang: string) => {
+ onPreview={(code, lang) => {
previewCode = code;
previewLanguage = lang;
previewDialogOpen = true;
* - **Default Params**: Server-wide generation defaults
*/
class ServerStore {
- // ─────────────────────────────────────────────────────────────────────────────
- // State
- // ─────────────────────────────────────────────────────────────────────────────
+ /**
+ *
+ *
+ * State
+ *
+ *
+ */
props = $state<ApiLlamaCppServerProps | null>(null);
loading = $state(false);
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 {
return this.role === ServerRole.MODEL;
}
- // ─────────────────────────────────────────────────────────────────────────────
- // Data Handling
- // ─────────────────────────────────────────────────────────────────────────────
+ /**
+ *
+ *
+ * Data Handling
+ *
+ *
+ */
async fetch(): Promise<void> {
if (this.fetchPromise) return this.fetchPromise;
this.fetchPromise = null;
}
- // ─────────────────────────────────────────────────────────────────────────────
- // Utilities
- // ─────────────────────────────────────────────────────────────────────────────
+ /**
+ *
+ *
+ * Utilities
+ *
+ *
+ */
private detectRole(props: ApiLlamaCppServerProps): void {
const newRole = props?.role === ServerRole.ROUTER ? ServerRole.ROUTER : ServerRole.MODEL;
} 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
}
}
- // ─────────────────────────────────────────────────────────────────────────────
- // Lifecycle
- // ─────────────────────────────────────────────────────────────────────────────
+ /**
+ *
+ *
+ * Lifecycle
+ *
+ *
+ */
/**
* Initialize the settings store by loading from localStorage
this.theme = localStorage.getItem('theme') || 'auto';
}
- // ─────────────────────────────────────────────────────────────────────────────
- // Config Updates
- // ─────────────────────────────────────────────────────────────────────────────
+ /**
+ *
+ *
+ * Config Updates
+ *
+ *
+ */
/**
* Update a specific configuration setting
}
}
- // ─────────────────────────────────────────────────────────────────────────────
- // Reset
- // ─────────────────────────────────────────────────────────────────────────────
+ /**
+ *
+ *
+ * Reset
+ *
+ *
+ */
/**
* Reset configuration to defaults
this.saveConfig();
}
- // ─────────────────────────────────────────────────────────────────────────────
- // Server Sync
- // ─────────────────────────────────────────────────────────────────────────────
+ /**
+ *
+ *
+ * Server Sync
+ *
+ *
+ */
/**
* Initialize settings with props defaults when server properties are first loaded
this.saveConfig();
}
- // ─────────────────────────────────────────────────────────────────────────────
- // Utilities
- // ─────────────────────────────────────────────────────────────────────────────
+ /**
+ *
+ *
+ * Utilities
+ *
+ *
+ */
/**
* Get a specific configuration value
<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' });
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');