refactor: Components

This commit is contained in:
Aleksander Grygier 2026-01-27 10:26:14 +01:00
parent b8221e8915
commit 1b7f576baf
24 changed files with 321 additions and 298 deletions

View File

@ -0,0 +1,19 @@
/**
*
* ACTIONS
*
* Small interactive components for user actions.
*
*/
/** Styled button for action triggers with icon support. */
export { default as ActionButton } from './ActionButton.svelte';
/** Copy-to-clipboard button with success feedback. */
export { default as CopyToClipboardIcon } from './CopyToClipboardIcon.svelte';
/** Remove/delete button with X icon. */
export { default as RemoveButton } from './RemoveButton.svelte';
/** Display for keyboard shortcut hints (e.g., "⌘ + Enter"). */
export { default as KeyboardShortcutInfo } from './KeyboardShortcutInfo.svelte';

View File

@ -0,0 +1,16 @@
/**
*
* BADGES & INDICATORS
*
* Small visual indicators for status and metadata.
*
*/
/** Badge displaying chat statistics (tokens, timing). */
export { default as BadgeChatStatistic } from './BadgeChatStatistic.svelte';
/** Generic info badge with optional tooltip and click handler. */
export { default as BadgeInfo } from './BadgeInfo.svelte';
/** Badge indicating model modality (vision, audio, tools). */
export { default as BadgeModality } from './BadgeModality.svelte';

View File

@ -0,0 +1,54 @@
<script lang="ts">
import { Copy, Eye } from '@lucide/svelte';
import { copyCodeToClipboard } from '$lib/utils';
interface Props {
code: string;
language: string;
disabled?: boolean;
onPreview?: (code: string, language: string) => void;
}
let { code, language, disabled = false, onPreview }: Props = $props();
const showPreview = $derived(language?.toLowerCase() === 'html');
function handleCopy() {
if (disabled) return;
copyCodeToClipboard(code);
}
function handlePreview() {
if (disabled) return;
onPreview?.(code, language);
}
</script>
<div class="code-block-actions">
<button
class="copy-code-btn"
class:opacity-50={disabled}
class:!cursor-not-allowed={disabled}
title={disabled ? 'Code incomplete' : 'Copy code'}
aria-label="Copy code"
aria-disabled={disabled}
type="button"
onclick={handleCopy}
>
<Copy size={16} />
</button>
{#if showPreview}
<button
class="preview-code-btn"
class:opacity-50={disabled}
class:!cursor-not-allowed={disabled}
title={disabled ? 'Code incomplete' : 'Preview code'}
aria-label="Preview code"
aria-disabled={disabled}
type="button"
onclick={handlePreview}
>
<Eye size={16} />
</button>
{/if}
</div>

View File

@ -26,7 +26,8 @@
import githubDarkCss from 'highlight.js/styles/github-dark.css?inline';
import githubLightCss from 'highlight.js/styles/github.css?inline';
import { mode } from 'mode-watcher';
import CodePreviewDialog from './CodePreviewDialog.svelte';
import { DialogCodePreview } from '$lib/components/app/dialogs';
import CodeBlockActions from './CodeBlockActions.svelte';
import { createAutoScrollController } from '$lib/hooks/use-auto-scroll.svelte';
import type { DatabaseMessage } from '$lib/types/database';
@ -514,58 +515,16 @@
<div class="code-block-wrapper streaming-code-block relative">
<div class="code-block-header">
<span class="code-language">{incompleteCodeBlock.language || 'text'}</span>
<div class="code-block-actions">
<button
class="copy-code-btn"
title="Copy code"
aria-label="Copy code"
type="button"
onclick={() => copyCodeToClipboard(incompleteCodeBlock?.code ?? '')}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><rect width="14" height="14" x="8" y="8" rx="2" ry="2" /><path
d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"
/></svg
>
</button>
{#if incompleteCodeBlock.language?.toLowerCase() === 'html'}
<button
class="preview-code-btn"
title="Preview code"
aria-label="Preview code"
type="button"
onclick={() => {
previewCode = incompleteCodeBlock?.code ?? '';
previewLanguage = incompleteCodeBlock?.language ?? 'html';
previewDialogOpen = true;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><path
d="M2.062 12.345a1 1 0 0 1 0-.69C3.5 7.73 7.36 5 12 5s8.5 2.73 9.938 6.655a1 1 0 0 1 0 .69C20.5 16.27 16.64 19 12 19s-8.5-2.73-9.938-6.655"
/><circle cx="12" cy="12" r="3" /></svg
>
</button>
{/if}
</div>
<CodeBlockActions
code={incompleteCodeBlock.code}
language={incompleteCodeBlock.language || 'text'}
disabled={true}
onPreview={(code, lang) => {
previewCode = code;
previewLanguage = lang;
previewDialogOpen = true;
}}
/>
</div>
<div
bind:this={streamingCodeScrollContainer}
@ -584,7 +543,7 @@
{/if}
</div>
<CodePreviewDialog
<DialogCodePreview
open={previewDialogOpen}
code={previewCode}
language={previewLanguage}

View File

@ -0,0 +1,82 @@
/**
*
* CONTENT RENDERING
*
* Components for rendering rich content: markdown, code, and previews.
*
*/
/**
* **MarkdownContent** - Rich markdown renderer
*
* Renders markdown content with syntax highlighting, LaTeX math,
* tables, links, and code blocks. Optimized for streaming with
* incremental block-based rendering.
*
* **Features:**
* - GFM (GitHub Flavored Markdown): tables, task lists, strikethrough
* - LaTeX math via KaTeX (`$inline$` and `$$block$$`)
* - Syntax highlighting (highlight.js) with language detection
* - Code copy buttons with click feedback
* - External links open in new tab with security attrs
* - Image attachment resolution from message extras
* - Dark/light theme support (auto-switching)
* - Streaming-optimized incremental rendering
* - Code preview dialog for large blocks
*
* @example
* ```svelte
* <MarkdownContent content={message.content} {message} />
* ```
*/
export { default as MarkdownContent } from './MarkdownContent.svelte';
/**
* **SyntaxHighlightedCode** - Code syntax highlighting
*
* Renders code with syntax highlighting using highlight.js.
* Supports theme switching and scrollable containers.
*
* **Features:**
* - Auto language detection with fallback
* - Dark/light theme auto-switching
* - Scrollable container with configurable max dimensions
* - Monospace font styling
* - Preserves whitespace and formatting
*
* @example
* ```svelte
* <SyntaxHighlightedCode code={jsonString} language="json" />
* ```
*/
export { default as SyntaxHighlightedCode } from './SyntaxHighlightedCode.svelte';
/**
* **CollapsibleContentBlock** - Expandable content card
*
* Reusable collapsible card with header, icon, and auto-scroll.
* Used for tool calls and reasoning blocks in chat messages.
*
* **Features:**
* - Collapsible content with smooth animation
* - Custom icon and title display
* - Optional subtitle/status text
* - Auto-scroll during streaming (pauses on user scroll)
* - Configurable max height with overflow scroll
*
* @example
* ```svelte
* <CollapsibleContentBlock
* bind:open
* icon={BrainIcon}
* title="Thinking..."
* isStreaming={true}
* >
* {reasoningContent}
* </CollapsibleContentBlock>
* ```
*/
export { default as CollapsibleContentBlock } from './CollapsibleContentBlock.svelte';
/** Code block actions component (copy, preview). */
export { default as CodeBlockActions } from './CodeBlockActions.svelte';

View File

@ -136,6 +136,44 @@ export { default as DialogConfirmation } from './DialogConfirmation.svelte';
*/
export { default as DialogConversationTitleUpdate } from './DialogConversationTitleUpdate.svelte';
/**
*
* CONTENT PREVIEW DIALOGS
*
* Dialogs for previewing and displaying content in full-screen or modal views.
*
*/
/**
* **DialogCodePreview** - Full-screen code/HTML preview
*
* Full-screen dialog for previewing HTML or code in an isolated iframe.
* Used by MarkdownContent component for previewing rendered HTML blocks
* from code blocks in chat messages.
*
* **Architecture:**
* - Uses ShadCN Dialog with full viewport layout
* - Sandboxed iframe execution (allow-scripts only)
* - Clears content when closed for security
*
* **Features:**
* - Full viewport iframe preview
* - Sandboxed execution environment
* - Close button with mix-blend-difference for visibility over any content
* - Automatic content cleanup on close
* - Supports HTML preview with proper isolation
*
* @example
* ```svelte
* <DialogCodePreview
* bind:open={showPreview}
* code={htmlContent}
* language="html"
* />
* ```
*/
export { default as DialogCodePreview } from './DialogCodePreview.svelte';
/**
*
* ATTACHMENT DIALOGS

View File

@ -0,0 +1,30 @@
/**
*
* FORMS & INPUTS
*
* Form-related utility components.
*
*/
/**
* **SearchInput** - Search field with clear button
*
* Input field optimized for search with clear button and keyboard handling.
* Supports placeholder, autofocus, and change callbacks.
*/
export { default as SearchInput } from './SearchInput.svelte';
/**
* **KeyValuePairs** - Editable key-value list
*
* Dynamic list of key-value pairs with add/remove functionality.
* Used for HTTP headers, metadata, and configuration.
*
* **Features:**
* - Add new pairs with button
* - Remove individual pairs
* - Customizable placeholders and labels
* - Empty state message
* - Auto-resize value textarea
*/
export { default as KeyValuePairs } from './KeyValuePairs.svelte';

View File

@ -1,6 +1,11 @@
export * from './actions';
export * from './badges';
export * from './chat';
export * from './content';
export * from './dialogs';
export * from './forms';
export * from './mcp';
export * from './misc';
export * from './models';
export * from './navigation';
export * from './server';

View File

@ -2,113 +2,10 @@
*
* MISC
*
* Reusable utility components used across the application.
* Includes content rendering, UI primitives, and helper components.
* Miscellaneous utility components.
*
*/
/**
*
* CONTENT RENDERING
*
* Components for rendering rich content: markdown, code, and previews.
*
*/
/**
* **MarkdownContent** - Rich markdown renderer
*
* Renders markdown content with syntax highlighting, LaTeX math,
* tables, links, and code blocks. Optimized for streaming with
* incremental block-based rendering.
*
* **Features:**
* - GFM (GitHub Flavored Markdown): tables, task lists, strikethrough
* - LaTeX math via KaTeX (`$inline$` and `$$block$$`)
* - Syntax highlighting (highlight.js) with language detection
* - Code copy buttons with click feedback
* - External links open in new tab with security attrs
* - Image attachment resolution from message extras
* - Dark/light theme support (auto-switching)
* - Streaming-optimized incremental rendering
* - Code preview dialog for large blocks
*
* @example
* ```svelte
* <MarkdownContent content={message.content} {message} />
* ```
*/
export { default as MarkdownContent } from './MarkdownContent.svelte';
/**
* **SyntaxHighlightedCode** - Code syntax highlighting
*
* Renders code with syntax highlighting using highlight.js.
* Supports theme switching and scrollable containers.
*
* **Features:**
* - Auto language detection with fallback
* - Dark/light theme auto-switching
* - Scrollable container with configurable max dimensions
* - Monospace font styling
* - Preserves whitespace and formatting
*
* @example
* ```svelte
* <SyntaxHighlightedCode code={jsonString} language="json" />
* ```
*/
export { default as SyntaxHighlightedCode } from './SyntaxHighlightedCode.svelte';
/**
* **CodePreviewDialog** - Full-screen code preview
*
* Full-screen dialog for previewing HTML/code in an isolated iframe.
* Used by MarkdownContent for previewing rendered HTML blocks.
*
* **Features:**
* - Full viewport iframe preview
* - Sandboxed execution (allow-scripts only)
* - Close button with mix-blend-difference for visibility
* - Clears content when closed for security
*/
export { default as CodePreviewDialog } from './CodePreviewDialog.svelte';
/**
*
* COLLAPSIBLE & EXPANDABLE
*
* Components for showing/hiding content sections.
*
*/
/**
* **CollapsibleContentBlock** - Expandable content card
*
* Reusable collapsible card with header, icon, and auto-scroll.
* Used for tool calls and reasoning blocks in chat messages.
*
* **Features:**
* - Collapsible content with smooth animation
* - Custom icon and title display
* - Optional subtitle/status text
* - Auto-scroll during streaming (pauses on user scroll)
* - Configurable max height with overflow scroll
*
* @example
* ```svelte
* <CollapsibleContentBlock
* bind:open
* icon={BrainIcon}
* title="Thinking..."
* isStreaming={true}
* >
* {reasoningContent}
* </CollapsibleContentBlock>
* ```
*/
export { default as CollapsibleContentBlock } from './CollapsibleContentBlock.svelte';
/**
* **TruncatedText** - Text with ellipsis and tooltip
*
@ -117,135 +14,6 @@ export { default as CollapsibleContentBlock } from './CollapsibleContentBlock.sv
*/
export { default as TruncatedText } from './TruncatedText.svelte';
/**
*
* DROPDOWNS & MENUS
*
* Components for dropdown menus and action selection.
*
*/
/**
* **DropdownMenuSearchable** - Filterable dropdown menu
*
* Generic dropdown with search input for filtering options.
* Uses Svelte snippets for flexible content rendering.
*
* **Features:**
* - Search/filter input with clear on close
* - Keyboard navigation support
* - Custom trigger, content, and footer via snippets
* - Empty state message
* - Disabled state
* - Configurable alignment and width
*
* @example
* ```svelte
* <DropdownMenuSearchable
* bind:open
* bind:searchValue
* placeholder="Search..."
* isEmpty={filteredItems.length === 0}
* >
* {#snippet trigger()}<Button>Select</Button>{/snippet}
* {#snippet children()}{#each items as item}<Item {item} />{/each}{/snippet}
* </DropdownMenuSearchable>
* ```
*/
export { default as DropdownMenuSearchable } from './DropdownMenuSearchable.svelte';
/**
* **DropdownMenuActions** - Multi-action dropdown menu
*
* Dropdown menu for multiple action options with icons and shortcuts.
* Supports destructive variants and keyboard shortcut hints.
*
* **Features:**
* - Configurable trigger icon with tooltip
* - Action items with icons and labels
* - Destructive variant styling
* - Keyboard shortcut display
* - Separator support between groups
*
* @example
* ```svelte
* <DropdownMenuActions
* triggerIcon={MoreHorizontal}
* triggerTooltip="More actions"
* actions={[
* { icon: Edit, label: 'Edit', onclick: handleEdit },
* { icon: Trash, label: 'Delete', onclick: handleDelete, variant: 'destructive' }
* ]}
* />
* ```
*/
export { default as DropdownMenuActions } from './DropdownMenuActions.svelte';
/**
*
* BUTTONS & ACTIONS
*
* Small interactive components for user actions.
*
*/
/** Styled button for action triggers with icon support. */
export { default as ActionButton } from './ActionButton.svelte';
/** Copy-to-clipboard button with success feedback. */
export { default as CopyToClipboardIcon } from './CopyToClipboardIcon.svelte';
/** Remove/delete button with X icon. */
export { default as RemoveButton } from './RemoveButton.svelte';
/**
*
* BADGES & INDICATORS
*
* Small visual indicators for status and metadata.
*
*/
/** Badge displaying chat statistics (tokens, timing). */
export { default as BadgeChatStatistic } from './BadgeChatStatistic.svelte';
/** Generic info badge with optional tooltip and click handler. */
export { default as BadgeInfo } from './BadgeInfo.svelte';
/** Badge indicating model modality (vision, audio, tools). */
export { default as BadgeModality } from './BadgeModality.svelte';
/**
*
* FORMS & INPUTS
*
* Form-related utility components.
*
*/
/**
* **SearchInput** - Search field with clear button
*
* Input field optimized for search with clear button and keyboard handling.
* Supports placeholder, autofocus, and change callbacks.
*/
export { default as SearchInput } from './SearchInput.svelte';
/**
* **KeyValuePairs** - Editable key-value list
*
* Dynamic list of key-value pairs with add/remove functionality.
* Used for HTTP headers, metadata, and configuration.
*
* **Features:**
* - Add new pairs with button
* - Remove individual pairs
* - Customizable placeholders and labels
* - Empty state message
* - Auto-resize value textarea
*/
export { default as KeyValuePairs } from './KeyValuePairs.svelte';
/**
* **ConversationSelection** - Multi-select conversation picker
*
@ -260,14 +28,3 @@ export { default as KeyValuePairs } from './KeyValuePairs.svelte';
* - Mode-specific UI (export vs import)
*/
export { default as ConversationSelection } from './ConversationSelection.svelte';
/**
*
* KEYBOARD & SHORTCUTS
*
* Components for displaying keyboard shortcuts.
*
*/
/** Display for keyboard shortcut hints (e.g., "⌘ + Enter"). */
export { default as KeyboardShortcutInfo } from './KeyboardShortcutInfo.svelte';

View File

@ -0,0 +1,63 @@
/**
*
* NAVIGATION & MENUS
*
* Components for dropdown menus and action selection.
*
*/
/**
* **DropdownMenuSearchable** - Filterable dropdown menu
*
* Generic dropdown with search input for filtering options.
* Uses Svelte snippets for flexible content rendering.
*
* **Features:**
* - Search/filter input with clear on close
* - Keyboard navigation support
* - Custom trigger, content, and footer via snippets
* - Empty state message
* - Disabled state
* - Configurable alignment and width
*
* @example
* ```svelte
* <DropdownMenuSearchable
* bind:open
* bind:searchValue
* placeholder="Search..."
* isEmpty={filteredItems.length === 0}
* >
* {#snippet trigger()}<Button>Select</Button>{/snippet}
* {#snippet children()}{#each items as item}<Item {item} />{/each}{/snippet}
* </DropdownMenuSearchable>
* ```
*/
export { default as DropdownMenuSearchable } from './DropdownMenuSearchable.svelte';
/**
* **DropdownMenuActions** - Multi-action dropdown menu
*
* Dropdown menu for multiple action options with icons and shortcuts.
* Supports destructive variants and keyboard shortcut hints.
*
* **Features:**
* - Configurable trigger icon with tooltip
* - Action items with icons and labels
* - Destructive variant styling
* - Keyboard shortcut display
* - Separator support between groups
*
* @example
* ```svelte
* <DropdownMenuActions
* triggerIcon={MoreHorizontal}
* triggerTooltip="More actions"
* actions={[
* { icon: Edit, label: 'Edit', onclick: handleEdit },
* { icon: Trash, label: 'Delete', onclick: handleDelete, variant: 'destructive' }
* ]}
* />
* ```
*/
export { default as DropdownMenuActions } from './DropdownMenuActions.svelte';