char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aleksander Grygierui: Stores split refactor (#27240)521a64cd0

char/aliceai-t5-moe
25.5 KiB734 linesraw

llama-ui

A modern, feature-rich web interface for llama-server built with SvelteKit. This UI provides an intuitive chat interface with advanced file handling, conversation management, and comprehensive model interaction capabilities.

Llama UI supports two server operation modes:

  • MODEL mode - Single model operation (standard llama-server)
  • ROUTER mode - Multi-model operation with dynamic model loading/unloading

Table of Contents


Features

Chat Interface

  • Streaming responses with real-time updates
  • Reasoning content - Support for models with thinking/reasoning blocks
  • Dark/light theme with system preference detection
  • Responsive design for desktop and mobile

File Attachments

  • Images - JPEG, PNG, GIF, WebP, SVG (with PNG conversion)
  • Documents - PDF (text extraction or image conversion for vision models)
  • Audio - MP3, WAV for audio-capable models
  • Text files - Source code, markdown, and other text formats
  • Drag-and-drop and paste support with rich previews

Conversation Management

  • Branching - Branch messages conversations at any point by editing messages or regenerating responses, navigate between branches
  • Regeneration - Regenerate responses with optional model switching (ROUTER mode)
  • Import/Export - JSON format for backup and sharing
  • Search - Find conversations by title or content

Advanced Rendering

  • Syntax highlighting - Code blocks with language detection
  • Math formulas - KaTeX rendering for LaTeX expressions
  • Markdown - Full GFM support with tables, lists, and more

Multi-Model Support (ROUTER mode)

  • Model selector with Loaded/Available groups
  • Automatic loading - Models load on selection
  • Modality validation - Prevents sending images to non-vision models
  • LRU unloading - Server auto-manages model cache

Keyboard Shortcuts

ShortcutAction
Shift+Ctrl/Cmd+ONew chat
Shift+Ctrl/Cmd+EEdit conversation
Shift+Ctrl/Cmd+DDelete conversation
Ctrl/Cmd+KSearch conversations
Ctrl/Cmd+BToggle sidebar

Developer Experience

  • Request tracking - Monitor token generation with /slots endpoint
  • Storybook - Component library with visual testing
  • Hot reload - Instant updates during development

Getting Started

Prerequisites

  • Node.js 18+ (20+ recommended)
  • npm 9+
  • llama-server running locally (for API access)

1. Install Dependencies

cd tools/ui
npm ci

2. Start llama-server

In a separate terminal, start the backend server:

# Single model (MODEL mode)
./llama-server -m model.gguf

# Multi-model (ROUTER mode)
./llama-server --models-dir /path/to/models

3. Start Development Servers

npm run dev

This starts:

  • Vite dev server at http://localhost:5173 - The main UI frontend app
  • Storybook at http://localhost:6006 - Component documentation

The Vite dev server proxies API requests to SERVER_ORIGIN (with fallback to default llama-server 8080 port):

// vite.config.ts proxy configuration
proxy: {
	'/v1': SERVER_ORIGIN,
	'/props': SERVER_ORIGIN,
	'/models': SERVER_ORIGIN,
	'/tools': SERVER_ORIGIN,
	'/slots': SERVER_ORIGIN,
	'/cors-proxy': SERVER_ORIGIN
},

Development Workflow

  1. Open http://localhost:5173 in your browser
  2. Make changes to .svelte, .ts, or .css files
  3. Changes hot-reload instantly
  4. Use Storybook at http://localhost:6006 for isolated component development

Tech Stack

LayerTechnologyPurpose
FrameworkSvelteKit + Svelte 5Reactive UI with runes ($state, $derived, $effect)
UI Componentsshadcn-svelte + bits-uiAccessible, customizable component library
StylingTailwindCSS 4Utility-first CSS with design tokens
DatabaseIndexedDB (Dexie)Client-side storage for conversations and messages
BuildViteFast bundling with static adapter
TestingPlaywright + Vitest + StorybookE2E, unit, and visual testing
Markdownremark + rehypeMarkdown processing with KaTeX and syntax highlighting

Key Dependencies

{
	"svelte": "^5.0.0",
	"bits-ui": "^2.8.11",
	"dexie": "^4.0.11",
	"pdfjs-dist": "^5.4.54",
	"highlight.js": "^11.11.1",
	"rehype-katex": "^7.0.1"
}

Build Pipeline

Development Build

npm run dev

Runs Vite in development mode with:

  • Hot Module Replacement (HMR)
  • Source maps
  • Proxy to llama-server

Production Build

npm run build

The build process:

  1. Vite Build - Bundles all TypeScript, Svelte, and CSS
  2. Static Adapter - Outputs to ../../build/tools/ui/dist (llama-server's static file directory)
  3. Post-Build Script - Cleans up intermediate files
  4. Custom Plugin - Creates index.html with:
    • Inlined favicon as base64
    • GZIP compression (level 9)
    • Deterministic output (zeroed timestamps)
tools/ui/        →  build  →  build/tools/ui/dist/
├── src/                                 ├── index.html  (served by llama-server)
├── static/                              └── (favicon inlined)
└── ...

SvelteKit Configuration

// svelte.config.js
adapter: adapter({
  pages: '../../build/tools/ui/dist',      // Output directory
  assets: '../../build/tools/ui/dist',     // Static assets
  fallback: 'index.html',  // SPA fallback
  strict: true
}),
output: {
  bundleStrategy: 'inline' // Single-file bundle
}

Integration with llama-server

llama-ui is embedded directly into the llama-server binary:

  1. npm run build outputs index.html to build/tools/ui/dist/
  2. llama-server compiles this into the binary at build time
  3. When accessing /, llama-server serves the bundled HTML

This results in a single portable binary with the full Llama UI included.


Architecture

Llama UI follows a layered architecture with unidirectional data flow:

Routes → Components → Hooks → Stores → Services → Storage/API

High-Level Architecture

flowchart TB
    subgraph Routes["📍 Routes"]
        R1["/ (Welcome)"]
        R2["/chat/[id]"]
        R3["/mcp-servers"]
        R4["/search"]
        R5["/settings"]
        RL["+layout.svelte"]
    end

    subgraph Components["🧩 Components"]
        C_Screen["ChatScreen"]
        C_Form["ChatForm"]
        C_Messages["ChatMessages"]
        C_Sidebar["ChatSidebar"]
        C_Models["ModelsSelector"]
        C_Settings["ChatSettings"]
        C_Mcp["McpServers"]
    end

    subgraph Hooks["🔌 Hooks"]
        H1["use-chat-screen-active-model"]
        H2["use-processing-state"]
        H3["use-context-gauge"]
        H4["use-models-selector"]
        H5["use-tools-panel"]
    end

    subgraph Stores["🗄️ Stores"]
        S1["chatStore"]
        S2["conversationsStore"]
        S3["modelsStore"]
        S4["mcpStore"]
        S5["agenticStore"]
        S6["serverStore"]
        S7["settingsStore"]
        S8["toolsStore"]
    end

    subgraph Services["⚙️ Services"]
        SV1["ChatService"]
        SV2["ModelsService"]
        SV3["PropsService"]
        SV4["DatabaseService"]
        SV5["MCPService"]
        SV6["ToolsService"]
        SV7["SandboxService"]
    end

    subgraph Storage["💾 Storage"]
        ST1["IndexedDB"]
        ST2["LocalStorage"]
    end

    subgraph APIs["🌐 llama-server"]
        API1["/v1/chat/completions"]
        API2["/props"]
        API3["/models/*"]
        API4["/tools"]
    end

    R1 & R2 --> C_Screen
    RL --> C_Sidebar
    C_Screen --> C_Form & C_Messages & C_Settings
    C_Screen --> H1 & H2 & H3
    C_Models --> H4
    C_Mcp --> S4
    C_Screen --> S1 & S2 & S3
    C_Models --> S3
    H1 --> S3
    S1 --> SV1 & SV4
    S2 --> SV4
    S3 --> SV2 & SV3
    S4 --> SV5
    S5 --> SV1 & SV5 & SV6 & SV7
    SV4 --> ST1
    SV1 --> API1
    SV2 --> API3
    SV3 --> API2
    SV6 --> API4

Layer Breakdown

Routes (src/routes/)

  • / - Welcome screen, creates new conversation
  • /chat/[id] - Active chat interface
  • /mcp-servers - MCP server management
  • /search - Conversation search
  • /settings - Settings (optional [[section]])
  • +layout.svelte - Sidebar, navigation, global initialization

Components (src/lib/components/)

Components are organized in app/ (application-specific) and ui/ (shadcn-svelte primitives).

Chat Components (app/chat/):

ComponentResponsibility
ChatScreen/Main chat container, coordinates message list, input form, and attachments
ChatForm/Message input textarea with file upload, paste handling, keyboard shortcuts
ChatMessages/Message list with branch navigation, regenerate/continue/edit actions
ChatAttachments/File attachment previews, drag-and-drop, PDF/image/audio handling
ChatSettings/Parameter sliders (temperature, top-p, etc.) with server default sync
ChatSidebar/Conversation list, search, import/export, navigation

Dialog Components (app/dialogs/):

ComponentResponsibility
DialogChatSettingsFull-screen settings configuration
DialogModelInformationModel details (context size, modalities, parallel slots)
DialogChatAttachmentPreviewFull preview for images, PDFs (text or page view), code
DialogConfirmationGeneric confirmation for destructive actions
DialogConversationTitleUpdateEdit conversation title

Server/Model Components (app/server/, app/models/):

ComponentResponsibility
ServerErrorSplashError display when server is unreachable
ModelsSelectorModel dropdown with Loaded/Available groups (ROUTER mode)

Shared UI Components (app/misc/):

ComponentResponsibility
MarkdownContentMarkdown rendering with KaTeX, syntax highlighting, copy buttons
SyntaxHighlightedCodeCode blocks with language detection and highlighting
ActionButton, ActionDropdownReusable action buttons and menus
BadgeModality, BadgeInfoStatus and capability badges

Hooks (src/lib/hooks/)

Hooks are the thin view-layer between components and stores: they own UI concerns (scroll, drag-and-drop, keyboard shortcuts, pickers, selection) and translate store state into view state.

HookResponsibility
use-chat-screen-active-modelActive model resolution + modality capability detection
use-processing-stateView over chatStore.processing for streaming progress/tokens
use-context-gaugeView over contextStatsStore for the context usage gauge
use-models-selectorModel selector dropdown state (loaded/available groups)
use-tools-panelTools panel state
use-reasoning-menuReasoning-effort menu state
use-attachment-menuAttachment menu + modality flags
use-draft-messagesPer-chat draft message/files persistence
use-chat-form-pickersChat form pickers (commands, mentions)
use-debounced-searchShared debounced async search for pickers
use-picker-navigationPicker keyboard navigation
use-chat-message-edit-contextMessage edit context (content + extras)
use-chat-screen-drag-and-dropDrag-and-drop state machine
use-chat-screen-file-uploadFile upload queue + capability validation
use-chat-screen-scrollScroll container binding + navigation guard
use-auto-scrollAuto-scroll controller for streaming
use-marquee-selectionShift+click / marquee range selection
use-keyboard-shortcutsGlobal keyboard shortcuts
use-settings-navigationSettings section navigation
use-pwaPWA install/update + version mismatch detection

Stores (src/lib/stores/)

Stores own reactive application state as Svelte 5 runes. Larger stores are split into directories and compose focused sub-stores behind a narrow host interface (see Architectural Patterns).

StoreResponsibility
chatStoreChat lifecycle, streaming, abort control, error handling; composes processing, activity, streams, flows
conversationsStoreConversation CRUD, message branching, navigation, import/export; composes preferences
modelsStoreModel list, selection, loading/unloading (ROUTER); composes props, status
mcpStoreMCP host role: multi-server lifecycle, tool routing; composes health, resources
agenticStoreMulti-turn agentic loop orchestration, tool execution; composes gates
serverStoreServer connection state, /props, role detection, modalities
settingsStoreUser preferences, theme, parameter sync with server defaults
toolsStoreTool registry: server + MCP tools, enabled set for the LLM
permissionsStorePersisted tool permission grants
contextStatsStoreContext window usage for the active conversation
draftMessagesStorePer-chat draft message/files
deviceStoreBrowser environment signals (mobile, OS, theme)
versionStoreBuild version information

Services (src/lib/services/)

Services are a stateless protocol layer: static methods, pure I/O, no reactive state. Stores consume them for all API and storage access.

ServiceResponsibility
ChatService/v1/chat/completions streaming + SSE parsing, message format conversion
ModelsService/models, /models/load, /models/unload
PropsService/props, /props?model=
DatabaseServiceIndexedDB operations via Dexie
MCPServiceMCP protocol: transports, connect, list/execute tools, prompts, resources
ToolsServiceServer tool list/execute/stream (/tools)
SandboxServiceBrowser JS execution in a sandboxed worker
ParameterSyncServiceSyncs settings with server defaults
ConversationTransferServiceConversation import/export JSONL + ZIP format
MigrationServiceNon-destructive localStorage/IndexedDB migrations
RouterServiceDynamic route URL construction

Data Flows

MODEL Mode (Single Model)

sequenceDiagram
    participant User
    participant UI
    participant Stores
    participant DB as IndexedDB
    participant API as llama-server

    Note over User,API: Initialization
    UI->>Stores: initStores() (awaited by route loads)
    Stores->>Stores: run migrations
    Stores->>DB: load conversations (background)
    Stores->>API: GET /props
    API-->>Stores: server config
    Stores->>API: GET /v1/models
    API-->>Stores: single model (auto-selected)

    Note over User,API: Chat Flow
    User->>UI: send message
    Stores->>DB: save user message
    Stores->>API: POST /v1/chat/completions (stream)
    loop streaming
        API-->>Stores: SSE chunks
        Stores-->>UI: reactive update
    end
    Stores->>DB: save assistant message

ROUTER Mode (Multi-Model)

sequenceDiagram
    participant User
    participant UI
    participant Stores
    participant API as llama-server

    Note over User,API: Initialization
    Stores->>API: GET /props
    API-->>Stores: {role: "router"}
    Stores->>API: GET /models
    API-->>Stores: models[] with status

    Note over User,API: Model Selection
    User->>UI: select model
    alt model not loaded
        Stores->>API: POST /models/load
        loop poll status
            Stores->>API: GET /models
        end
        Stores->>API: GET /props?model=X
    end
    Stores->>Stores: validate modalities

    Note over User,API: Chat Flow
    Stores->>API: POST /v1/chat/completions {model: X}
    loop streaming
        API-->>Stores: SSE chunks + model info
    end

Architectural Patterns

1. Reactive State with Svelte 5 Runes

All stores use Svelte 5's fine-grained reactivity:

// Store with reactive state
class ChatStore {
	#isLoading = $state(false);
	#currentResponse = $state('');

	// Derived values auto-update
	get isStreaming() {
		return $derived(this.#isLoading && this.#currentResponse.length > 0);
	}
}

// Exported reactive accessors
export const isLoading = () => chatStore.isLoading;
export const currentResponse = () => chatStore.currentResponse;

2. Unidirectional Data Flow

Data flows in one direction, making state predictable:

flowchart LR
    subgraph UI["UI Layer"]
        A[User Action] --> B[Component]
    end

    subgraph State["State Layer"]
        B --> C[Store Method]
        C --> D[State Update]
    end

    subgraph IO["I/O Layer"]
        C --> E[Service]
        E --> F[API / IndexedDB]
        F -.->|Response| D
    end

    D -->|Reactive| B

Components dispatch actions to stores, stores coordinate with services for I/O, and state updates reactively propagate back to the UI.

3. Per-Conversation State

Enables concurrent streaming across multiple conversations. Loading is tracked per conversation by the activity ledger (chatStore.activity), while streaming state and abort controllers live in per-conversation maps:

class ChatStore {
	chatStreamingStates = new SvelteMap<string, { response: string; messageId: string }>();
	abortControllers = new SvelteMap<string, AbortController>();
}

4. Message Branching with Tree Structure

Conversations are stored as a tree, not a linear list:

interface DatabaseMessage {
	id: string;
	parent: string | null; // Points to parent message
	children: string[]; // List of child message IDs
	// ...
}

interface DatabaseConversation {
	currentNode: string; // Currently viewed branch tip
	// ...
}

Navigation between branches updates currentNode without losing history.

5. Layered Service Architecture

Stores handle state; services handle I/O:

┌─────────────────┐
│     Stores      │  Business logic, state management
├─────────────────┤
│    Services     │  API calls, database operations
├─────────────────┤
│   Storage/API   │  IndexedDB, LocalStorage, HTTP
└─────────────────┘

6. Server Role Abstraction

Single codebase handles both MODEL and ROUTER modes:

// serverStore.ts
get isRouterMode() {
  return this.role === ServerRole.ROUTER;
}

// Components conditionally render based on mode
{#if isRouterMode()}
  <ModelsSelector />
{/if}

7. Modality Validation

Prevents sending attachments to incompatible models. The use-chat-screen-active-model hook derives the active model's capabilities from modelsStore.props:

// use-chat-screen-active-model hook
const hasVisionModality = $derived.by(() => modelsStore.props.modelSupportsVision(activeModelId));
const hasAudioModality = $derived.by(() => modelsStore.props.modelSupportsAudio(activeModelId));

8. Persistent Storage Strategy

Data is persisted across sessions using two storage mechanisms:

flowchart TB
    subgraph Browser["Browser Storage"]
        subgraph IDB["IndexedDB (Dexie)"]
            C[Conversations]
            M[Messages]
        end
        subgraph LS["LocalStorage"]
            S[Settings Config]
            O[User Overrides]
            T[Theme Preference]
        end
    end

    subgraph Stores["Svelte Stores"]
        CS[conversationsStore] --> C
        CS --> M
        SS[settingsStore] --> S
        SS --> O
        SS --> T
    end
  • IndexedDB: Conversations and messages (large, structured data)
  • LocalStorage: Settings, user parameter overrides, theme (small key-value data)
  • Memory only: Server props, model list (fetched fresh on each session)

Testing

Test Types

TypeToolLocationCommand
UnitVitesttests/unit/npm run test:unit
UI/VisualStorybook + Vitesttests/stories/npm run test:ui
E2EPlaywrighttests/e2e/npm run test:e2e
ClientVitesttests/client/.npm run test:unit

Running Tests

# All tests
npm run test

# Individual test suites
npm run test:e2e      # End-to-end (requires llama-server)
npm run test:client   # Client-side unit tests
npm run test:server   # Server-side unit tests
npm run test:ui       # Storybook visual tests

Storybook Development

npm run storybook     # Start Storybook dev server on :6006
npm run build-storybook  # Build static Storybook

Linting and Formatting

npm run lint          # Check code style
npm run format        # Auto-format with Prettier
npm run check         # TypeScript type checking

Project Structure

tools/ui/
├── src/
│   ├── lib/
│   │   ├── components/   # UI components (app/, ui/)
│   │   ├── hooks/        # Svelte hooks
│   │   ├── stores/       # State management
│   │   ├── services/     # API and database services
│   │   ├── types/        # TypeScript interfaces
│   │   └── utils/        # Utility functions
│   ├── routes/           # SvelteKit routes
│   └── styles/           # Global styles
├── static/               # Static assets
├── tests/                # Test files
└── .storybook/           # Storybook configuration

Related Documentation

1# llama-ui
2
3A modern, feature-rich web interface for llama-server built with SvelteKit. This UI provides an intuitive chat interface with advanced file handling, conversation management, and comprehensive model interaction capabilities.
4
5Llama UI supports two server operation modes:
6
7- **MODEL mode** - Single model operation (standard llama-server)
8- **ROUTER mode** - Multi-model operation with dynamic model loading/unloading
9
10---
11
12## Table of Contents
13
14- [Features](#features)
15- [Getting Started](#getting-started)
16- [Tech Stack](#tech-stack)
17- [Build Pipeline](#build-pipeline)
18- [Architecture](#architecture)
19- [Data Flows](#data-flows)
20- [Architectural Patterns](#architectural-patterns)
21- [Testing](#testing)
22
23---
24
25## Features
26
27### Chat Interface
28
29- **Streaming responses** with real-time updates
30- **Reasoning content** - Support for models with thinking/reasoning blocks
31- **Dark/light theme** with system preference detection
32- **Responsive design** for desktop and mobile
33
34### File Attachments
35
36- **Images** - JPEG, PNG, GIF, WebP, SVG (with PNG conversion)
37- **Documents** - PDF (text extraction or image conversion for vision models)
38- **Audio** - MP3, WAV for audio-capable models
39- **Text files** - Source code, markdown, and other text formats
40- **Drag-and-drop** and paste support with rich previews
41
42### Conversation Management
43
44- **Branching** - Branch messages conversations at any point by editing messages or regenerating responses, navigate between branches
45- **Regeneration** - Regenerate responses with optional model switching (ROUTER mode)
46- **Import/Export** - JSON format for backup and sharing
47- **Search** - Find conversations by title or content
48
49### Advanced Rendering
50
51- **Syntax highlighting** - Code blocks with language detection
52- **Math formulas** - KaTeX rendering for LaTeX expressions
53- **Markdown** - Full GFM support with tables, lists, and more
54
55### Multi-Model Support (ROUTER mode)
56
57- **Model selector** with Loaded/Available groups
58- **Automatic loading** - Models load on selection
59- **Modality validation** - Prevents sending images to non-vision models
60- **LRU unloading** - Server auto-manages model cache
61
62### Keyboard Shortcuts
63
64| Shortcut           | Action               |
65| ------------------ | -------------------- |
66| `Shift+Ctrl/Cmd+O` | New chat             |
67| `Shift+Ctrl/Cmd+E` | Edit conversation    |
68| `Shift+Ctrl/Cmd+D` | Delete conversation  |
69| `Ctrl/Cmd+K`       | Search conversations |
70| `Ctrl/Cmd+B`       | Toggle sidebar       |
71
72### Developer Experience
73
74- **Request tracking** - Monitor token generation with `/slots` endpoint
75- **Storybook** - Component library with visual testing
76- **Hot reload** - Instant updates during development
77
78---
79
80## Getting Started
81
82### Prerequisites
83
84- **Node.js** 18+ (20+ recommended)
85- **npm** 9+
86- **llama-server** running locally (for API access)
87
88### 1. Install Dependencies
89
90```bash
91cd tools/ui
92npm ci
93```
94
95### 2. Start llama-server
96
97In a separate terminal, start the backend server:
98
99```bash
100# Single model (MODEL mode)
101./llama-server -m model.gguf
102
103# Multi-model (ROUTER mode)
104./llama-server --models-dir /path/to/models
105```
106
107### 3. Start Development Servers
108
109```bash
110npm run dev
111```
112
113This starts:
114
115- **Vite dev server** at `http://localhost:5173` - The main UI frontend app
116- **Storybook** at `http://localhost:6006` - Component documentation
117
118The Vite dev server proxies API requests to `SERVER_ORIGIN` (with fallback to default llama-server `8080` port):
119
120```typescript
121// vite.config.ts proxy configuration
122proxy: {
123	'/v1': SERVER_ORIGIN,
124	'/props': SERVER_ORIGIN,
125	'/models': SERVER_ORIGIN,
126	'/tools': SERVER_ORIGIN,
127	'/slots': SERVER_ORIGIN,
128	'/cors-proxy': SERVER_ORIGIN
129},
130```
131
132### Development Workflow
133
1341. Open `http://localhost:5173` in your browser
1352. Make changes to `.svelte`, `.ts`, or `.css` files
1363. Changes hot-reload instantly
1374. Use Storybook at `http://localhost:6006` for isolated component development
138
139---
140
141## Tech Stack
142
143| Layer             | Technology                      | Purpose                                                  |
144| ----------------- | ------------------------------- | -------------------------------------------------------- |
145| **Framework**     | SvelteKit + Svelte 5            | Reactive UI with runes (`$state`, `$derived`, `$effect`) |
146| **UI Components** | shadcn-svelte + bits-ui         | Accessible, customizable component library               |
147| **Styling**       | TailwindCSS 4                   | Utility-first CSS with design tokens                     |
148| **Database**      | IndexedDB (Dexie)               | Client-side storage for conversations and messages       |
149| **Build**         | Vite                            | Fast bundling with static adapter                        |
150| **Testing**       | Playwright + Vitest + Storybook | E2E, unit, and visual testing                            |
151| **Markdown**      | remark + rehype                 | Markdown processing with KaTeX and syntax highlighting   |
152
153### Key Dependencies
154
155```json
156{
157	"svelte": "^5.0.0",
158	"bits-ui": "^2.8.11",
159	"dexie": "^4.0.11",
160	"pdfjs-dist": "^5.4.54",
161	"highlight.js": "^11.11.1",
162	"rehype-katex": "^7.0.1"
163}
164```
165
166---
167
168## Build Pipeline
169
170### Development Build
171
172```bash
173npm run dev
174```
175
176Runs Vite in development mode with:
177
178- Hot Module Replacement (HMR)
179- Source maps
180- Proxy to llama-server
181
182### Production Build
183
184```bash
185npm run build
186```
187
188The build process:
189
1901. **Vite Build** - Bundles all TypeScript, Svelte, and CSS
1912. **Static Adapter** - Outputs to `../../build/tools/ui/dist` (llama-server's static file directory)
1923. **Post-Build Script** - Cleans up intermediate files
1934. **Custom Plugin** - Creates `index.html` with:
194   - Inlined favicon as base64
195   - GZIP compression (level 9)
196   - Deterministic output (zeroed timestamps)
197
198```text
199tools/ui/        →  build  →  build/tools/ui/dist/
200├── src/                                 ├── index.html  (served by llama-server)
201├── static/                              └── (favicon inlined)
202└── ...
203```
204
205### SvelteKit Configuration
206
207```javascript
208// svelte.config.js
209adapter: adapter({
210  pages: '../../build/tools/ui/dist',      // Output directory
211  assets: '../../build/tools/ui/dist',     // Static assets
212  fallback: 'index.html',  // SPA fallback
213  strict: true
214}),
215output: {
216  bundleStrategy: 'inline' // Single-file bundle
217}
218```
219
220### Integration with llama-server
221
222llama-ui is embedded directly into the llama-server binary:
223
2241. `npm run build` outputs `index.html` to `build/tools/ui/dist/`
2252. llama-server compiles this into the binary at build time
2263. When accessing `/`, llama-server serves the bundled HTML
227
228This results in a **single portable binary** with the full Llama UI included.
229
230---
231
232## Architecture
233
234Llama UI follows a layered architecture with unidirectional data flow:
235
236```text
237Routes → Components → Hooks → Stores → Services → Storage/API
238```
239
240### High-Level Architecture
241
242```mermaid
243flowchart TB
244    subgraph Routes["📍 Routes"]
245        R1["/ (Welcome)"]
246        R2["/chat/[id]"]
247        R3["/mcp-servers"]
248        R4["/search"]
249        R5["/settings"]
250        RL["+layout.svelte"]
251    end
252
253    subgraph Components["🧩 Components"]
254        C_Screen["ChatScreen"]
255        C_Form["ChatForm"]
256        C_Messages["ChatMessages"]
257        C_Sidebar["ChatSidebar"]
258        C_Models["ModelsSelector"]
259        C_Settings["ChatSettings"]
260        C_Mcp["McpServers"]
261    end
262
263    subgraph Hooks["🔌 Hooks"]
264        H1["use-chat-screen-active-model"]
265        H2["use-processing-state"]
266        H3["use-context-gauge"]
267        H4["use-models-selector"]
268        H5["use-tools-panel"]
269    end
270
271    subgraph Stores["🗄️ Stores"]
272        S1["chatStore"]
273        S2["conversationsStore"]
274        S3["modelsStore"]
275        S4["mcpStore"]
276        S5["agenticStore"]
277        S6["serverStore"]
278        S7["settingsStore"]
279        S8["toolsStore"]
280    end
281
282    subgraph Services["⚙️ Services"]
283        SV1["ChatService"]
284        SV2["ModelsService"]
285        SV3["PropsService"]
286        SV4["DatabaseService"]
287        SV5["MCPService"]
288        SV6["ToolsService"]
289        SV7["SandboxService"]
290    end
291
292    subgraph Storage["💾 Storage"]
293        ST1["IndexedDB"]
294        ST2["LocalStorage"]
295    end
296
297    subgraph APIs["🌐 llama-server"]
298        API1["/v1/chat/completions"]
299        API2["/props"]
300        API3["/models/*"]
301        API4["/tools"]
302    end
303
304    R1 & R2 --> C_Screen
305    RL --> C_Sidebar
306    C_Screen --> C_Form & C_Messages & C_Settings
307    C_Screen --> H1 & H2 & H3
308    C_Models --> H4
309    C_Mcp --> S4
310    C_Screen --> S1 & S2 & S3
311    C_Models --> S3
312    H1 --> S3
313    S1 --> SV1 & SV4
314    S2 --> SV4
315    S3 --> SV2 & SV3
316    S4 --> SV5
317    S5 --> SV1 & SV5 & SV6 & SV7
318    SV4 --> ST1
319    SV1 --> API1
320    SV2 --> API3
321    SV3 --> API2
322    SV6 --> API4
323```
324
325### Layer Breakdown
326
327#### Routes (`src/routes/`)
328
329- **`/`** - Welcome screen, creates new conversation
330- **`/chat/[id]`** - Active chat interface
331- **`/mcp-servers`** - MCP server management
332- **`/search`** - Conversation search
333- **`/settings`** - Settings (optional `[[section]]`)
334- **`+layout.svelte`** - Sidebar, navigation, global initialization
335
336#### Components (`src/lib/components/`)
337
338Components are organized in `app/` (application-specific) and `ui/` (shadcn-svelte primitives).
339
340**Chat Components** (`app/chat/`):
341
342| Component          | Responsibility                                                              |
343| ------------------ | --------------------------------------------------------------------------- |
344| `ChatScreen/`      | Main chat container, coordinates message list, input form, and attachments  |
345| `ChatForm/`        | Message input textarea with file upload, paste handling, keyboard shortcuts |
346| `ChatMessages/`    | Message list with branch navigation, regenerate/continue/edit actions       |
347| `ChatAttachments/` | File attachment previews, drag-and-drop, PDF/image/audio handling           |
348| `ChatSettings/`    | Parameter sliders (temperature, top-p, etc.) with server default sync       |
349| `ChatSidebar/`     | Conversation list, search, import/export, navigation                        |
350
351**Dialog Components** (`app/dialogs/`):
352
353| Component                       | Responsibility                                           |
354| ------------------------------- | -------------------------------------------------------- |
355| `DialogChatSettings`            | Full-screen settings configuration                       |
356| `DialogModelInformation`        | Model details (context size, modalities, parallel slots) |
357| `DialogChatAttachmentPreview`   | Full preview for images, PDFs (text or page view), code  |
358| `DialogConfirmation`            | Generic confirmation for destructive actions             |
359| `DialogConversationTitleUpdate` | Edit conversation title                                  |
360
361**Server/Model Components** (`app/server/`, `app/models/`):
362
363| Component           | Responsibility                                            |
364| ------------------- | --------------------------------------------------------- |
365| `ServerErrorSplash` | Error display when server is unreachable                  |
366| `ModelsSelector`    | Model dropdown with Loaded/Available groups (ROUTER mode) |
367
368**Shared UI Components** (`app/misc/`):
369
370| Component                        | Responsibility                                                   |
371| -------------------------------- | ---------------------------------------------------------------- |
372| `MarkdownContent`                | Markdown rendering with KaTeX, syntax highlighting, copy buttons |
373| `SyntaxHighlightedCode`          | Code blocks with language detection and highlighting             |
374| `ActionButton`, `ActionDropdown` | Reusable action buttons and menus                                |
375| `BadgeModality`, `BadgeInfo`     | Status and capability badges                                     |
376
377#### Hooks (`src/lib/hooks/`)
378
379Hooks are the thin view-layer between components and stores: they own UI concerns (scroll, drag-and-drop, keyboard shortcuts, pickers, selection) and translate store state into view state.
380
381| Hook                            | Responsibility                                                 |
382| ------------------------------- | -------------------------------------------------------------- |
383| `use-chat-screen-active-model`  | Active model resolution + modality capability detection        |
384| `use-processing-state`          | View over `chatStore.processing` for streaming progress/tokens |
385| `use-context-gauge`             | View over `contextStatsStore` for the context usage gauge      |
386| `use-models-selector`           | Model selector dropdown state (loaded/available groups)        |
387| `use-tools-panel`               | Tools panel state                                              |
388| `use-reasoning-menu`            | Reasoning-effort menu state                                    |
389| `use-attachment-menu`           | Attachment menu + modality flags                               |
390| `use-draft-messages`            | Per-chat draft message/files persistence                       |
391| `use-chat-form-pickers`         | Chat form pickers (commands, mentions)                         |
392| `use-debounced-search`          | Shared debounced async search for pickers                      |
393| `use-picker-navigation`         | Picker keyboard navigation                                     |
394| `use-chat-message-edit-context` | Message edit context (content + extras)                        |
395| `use-chat-screen-drag-and-drop` | Drag-and-drop state machine                                    |
396| `use-chat-screen-file-upload`   | File upload queue + capability validation                      |
397| `use-chat-screen-scroll`        | Scroll container binding + navigation guard                    |
398| `use-auto-scroll`               | Auto-scroll controller for streaming                           |
399| `use-marquee-selection`         | Shift+click / marquee range selection                          |
400| `use-keyboard-shortcuts`        | Global keyboard shortcuts                                      |
401| `use-settings-navigation`       | Settings section navigation                                    |
402| `use-pwa`                       | PWA install/update + version mismatch detection                |
403
404#### Stores (`src/lib/stores/`)
405
406Stores own reactive application state as Svelte 5 runes. Larger stores are split into directories and compose focused sub-stores behind a narrow host interface (see Architectural Patterns).
407
408| Store                | Responsibility                                                                                                  |
409| -------------------- | --------------------------------------------------------------------------------------------------------------- |
410| `chatStore`          | Chat lifecycle, streaming, abort control, error handling; composes `processing`, `activity`, `streams`, `flows` |
411| `conversationsStore` | Conversation CRUD, message branching, navigation, import/export; composes `preferences`                         |
412| `modelsStore`        | Model list, selection, loading/unloading (ROUTER); composes `props`, `status`                                   |
413| `mcpStore`           | MCP host role: multi-server lifecycle, tool routing; composes `health`, `resources`                             |
414| `agenticStore`       | Multi-turn agentic loop orchestration, tool execution; composes `gates`                                         |
415| `serverStore`        | Server connection state, `/props`, role detection, modalities                                                   |
416| `settingsStore`      | User preferences, theme, parameter sync with server defaults                                                    |
417| `toolsStore`         | Tool registry: server + MCP tools, enabled set for the LLM                                                      |
418| `permissionsStore`   | Persisted tool permission grants                                                                                |
419| `contextStatsStore`  | Context window usage for the active conversation                                                                |
420| `draftMessagesStore` | Per-chat draft message/files                                                                                    |
421| `deviceStore`        | Browser environment signals (mobile, OS, theme)                                                                 |
422| `versionStore`       | Build version information                                                                                       |
423
424#### Services (`src/lib/services/`)
425
426Services are a stateless protocol layer: static methods, pure I/O, no reactive state. Stores consume them for all API and storage access.
427
428| Service                       | Responsibility                                                            |
429| ----------------------------- | ------------------------------------------------------------------------- |
430| `ChatService`                 | `/v1/chat/completions` streaming + SSE parsing, message format conversion |
431| `ModelsService`               | `/models`, `/models/load`, `/models/unload`                               |
432| `PropsService`                | `/props`, `/props?model=`                                                 |
433| `DatabaseService`             | IndexedDB operations via Dexie                                            |
434| `MCPService`                  | MCP protocol: transports, connect, list/execute tools, prompts, resources |
435| `ToolsService`                | Server tool list/execute/stream (`/tools`)                                |
436| `SandboxService`              | Browser JS execution in a sandboxed worker                                |
437| `ParameterSyncService`        | Syncs settings with server defaults                                       |
438| `ConversationTransferService` | Conversation import/export JSONL + ZIP format                             |
439| `MigrationService`            | Non-destructive localStorage/IndexedDB migrations                         |
440| `RouterService`               | Dynamic route URL construction                                            |
441
442---
443
444## Data Flows
445
446### MODEL Mode (Single Model)
447
448```mermaid
449sequenceDiagram
450    participant User
451    participant UI
452    participant Stores
453    participant DB as IndexedDB
454    participant API as llama-server
455
456    Note over User,API: Initialization
457    UI->>Stores: initStores() (awaited by route loads)
458    Stores->>Stores: run migrations
459    Stores->>DB: load conversations (background)
460    Stores->>API: GET /props
461    API-->>Stores: server config
462    Stores->>API: GET /v1/models
463    API-->>Stores: single model (auto-selected)
464
465    Note over User,API: Chat Flow
466    User->>UI: send message
467    Stores->>DB: save user message
468    Stores->>API: POST /v1/chat/completions (stream)
469    loop streaming
470        API-->>Stores: SSE chunks
471        Stores-->>UI: reactive update
472    end
473    Stores->>DB: save assistant message
474```
475
476### ROUTER Mode (Multi-Model)
477
478```mermaid
479sequenceDiagram
480    participant User
481    participant UI
482    participant Stores
483    participant API as llama-server
484
485    Note over User,API: Initialization
486    Stores->>API: GET /props
487    API-->>Stores: {role: "router"}
488    Stores->>API: GET /models
489    API-->>Stores: models[] with status
490
491    Note over User,API: Model Selection
492    User->>UI: select model
493    alt model not loaded
494        Stores->>API: POST /models/load
495        loop poll status
496            Stores->>API: GET /models
497        end
498        Stores->>API: GET /props?model=X
499    end
500    Stores->>Stores: validate modalities
501
502    Note over User,API: Chat Flow
503    Stores->>API: POST /v1/chat/completions {model: X}
504    loop streaming
505        API-->>Stores: SSE chunks + model info
506    end
507```
508
509---
510
511## Architectural Patterns
512
513### 1. Reactive State with Svelte 5 Runes
514
515All stores use Svelte 5's fine-grained reactivity:
516
517```typescript
518// Store with reactive state
519class ChatStore {
520	#isLoading = $state(false);
521	#currentResponse = $state('');
522
523	// Derived values auto-update
524	get isStreaming() {
525		return $derived(this.#isLoading && this.#currentResponse.length > 0);
526	}
527}
528
529// Exported reactive accessors
530export const isLoading = () => chatStore.isLoading;
531export const currentResponse = () => chatStore.currentResponse;
532```
533
534### 2. Unidirectional Data Flow
535
536Data flows in one direction, making state predictable:
537
538```mermaid
539flowchart LR
540    subgraph UI["UI Layer"]
541        A[User Action] --> B[Component]
542    end
543
544    subgraph State["State Layer"]
545        B --> C[Store Method]
546        C --> D[State Update]
547    end
548
549    subgraph IO["I/O Layer"]
550        C --> E[Service]
551        E --> F[API / IndexedDB]
552        F -.->|Response| D
553    end
554
555    D -->|Reactive| B
556```
557
558Components dispatch actions to stores, stores coordinate with services for I/O, and state updates reactively propagate back to the UI.
559
560### 3. Per-Conversation State
561
562Enables concurrent streaming across multiple conversations. Loading is tracked
563per conversation by the activity ledger (`chatStore.activity`), while streaming
564state and abort controllers live in per-conversation maps:
565
566```typescript
567class ChatStore {
568	chatStreamingStates = new SvelteMap<string, { response: string; messageId: string }>();
569	abortControllers = new SvelteMap<string, AbortController>();
570}
571```
572
573### 4. Message Branching with Tree Structure
574
575Conversations are stored as a tree, not a linear list:
576
577```typescript
578interface DatabaseMessage {
579	id: string;
580	parent: string | null; // Points to parent message
581	children: string[]; // List of child message IDs
582	// ...
583}
584
585interface DatabaseConversation {
586	currentNode: string; // Currently viewed branch tip
587	// ...
588}
589```
590
591Navigation between branches updates `currentNode` without losing history.
592
593### 5. Layered Service Architecture
594
595Stores handle state; services handle I/O:
596
597```text
598┌─────────────────┐
599│     Stores      │  Business logic, state management
600├─────────────────┤
601│    Services     │  API calls, database operations
602├─────────────────┤
603│   Storage/API   │  IndexedDB, LocalStorage, HTTP
604└─────────────────┘
605```
606
607### 6. Server Role Abstraction
608
609Single codebase handles both MODEL and ROUTER modes:
610
611```typescript
612// serverStore.ts
613get isRouterMode() {
614  return this.role === ServerRole.ROUTER;
615}
616
617// Components conditionally render based on mode
618{#if isRouterMode()}
619  <ModelsSelector />
620{/if}
621```
622
623### 7. Modality Validation
624
625Prevents sending attachments to incompatible models. The
626`use-chat-screen-active-model` hook derives the active model's capabilities
627from `modelsStore.props`:
628
629```typescript
630// use-chat-screen-active-model hook
631const hasVisionModality = $derived.by(() => modelsStore.props.modelSupportsVision(activeModelId));
632const hasAudioModality = $derived.by(() => modelsStore.props.modelSupportsAudio(activeModelId));
633```
634
635### 8. Persistent Storage Strategy
636
637Data is persisted across sessions using two storage mechanisms:
638
639```mermaid
640flowchart TB
641    subgraph Browser["Browser Storage"]
642        subgraph IDB["IndexedDB (Dexie)"]
643            C[Conversations]
644            M[Messages]
645        end
646        subgraph LS["LocalStorage"]
647            S[Settings Config]
648            O[User Overrides]
649            T[Theme Preference]
650        end
651    end
652
653    subgraph Stores["Svelte Stores"]
654        CS[conversationsStore] --> C
655        CS --> M
656        SS[settingsStore] --> S
657        SS --> O
658        SS --> T
659    end
660```
661
662- **IndexedDB**: Conversations and messages (large, structured data)
663- **LocalStorage**: Settings, user parameter overrides, theme (small key-value data)
664- **Memory only**: Server props, model list (fetched fresh on each session)
665
666---
667
668## Testing
669
670### Test Types
671
672| Type          | Tool               | Location         | Command             |
673| ------------- | ------------------ | ---------------- | ------------------- |
674| **Unit**      | Vitest             | `tests/unit/`    | `npm run test:unit` |
675| **UI/Visual** | Storybook + Vitest | `tests/stories/` | `npm run test:ui`   |
676| **E2E**       | Playwright         | `tests/e2e/`     | `npm run test:e2e`  |
677| **Client**    | Vitest             | `tests/client/`. | `npm run test:unit` |
678
679### Running Tests
680
681```bash
682# All tests
683npm run test
684
685# Individual test suites
686npm run test:e2e      # End-to-end (requires llama-server)
687npm run test:client   # Client-side unit tests
688npm run test:server   # Server-side unit tests
689npm run test:ui       # Storybook visual tests
690```
691
692### Storybook Development
693
694```bash
695npm run storybook     # Start Storybook dev server on :6006
696npm run build-storybook  # Build static Storybook
697```
698
699### Linting and Formatting
700
701```bash
702npm run lint          # Check code style
703npm run format        # Auto-format with Prettier
704npm run check         # TypeScript type checking
705```
706
707---
708
709## Project Structure
710
711```text
712tools/ui/
713├── src/
714│   ├── lib/
715│   │   ├── components/   # UI components (app/, ui/)
716│   │   ├── hooks/        # Svelte hooks
717│   │   ├── stores/       # State management
718│   │   ├── services/     # API and database services
719│   │   ├── types/        # TypeScript interfaces
720│   │   └── utils/        # Utility functions
721│   ├── routes/           # SvelteKit routes
722│   └── styles/           # Global styles
723├── static/               # Static assets
724├── tests/                # Test files
725└── .storybook/           # Storybook configuration
726```
727
728---
729
730## Related Documentation
731
732- [llama.cpp Server README](../server/README.md) - Full server documentation
733- [Multimodal Documentation](../../docs/multimodal.md) - Image and audio support
734- [Function Calling](../../docs/function-calling.md) - Tool use capabilities