ui: Restructure repo to use tools/ui folder and ui / UI / llama-ui / LLAMA_UI naming (#23064)
* webui: Move static build output from `tools/server/public` to `build/ui` directory * refactor: Move to `tools/ui` * refactor: rename CMake variables and preprocessor defines - Rename LLAMA_BUILD_WEBUI -> LLAMA_BUILD_UI (old kept as deprecated) - Rename LLAMA_USE_PREBUILT_WEBUI -> LLAMA_USE_PREBUILT_UI (old kept as deprecated) - Backward compat: old vars auto-forward to new ones with DEPRECATION warning - Rename internal vars: WEBUI_SOURCE -> UI_SOURCE, WEBUI_SOURCE_DIR -> UI_SOURCE_DIR, etc. - Rename HF bucket: LLAMA_WEBUI_HF_BUCKET -> LLAMA_UI_HF_BUCKET - Emit both LLAMA_BUILD_WEBUI and LLAMA_BUILD_UI preprocessor defines - Emit both LLAMA_WEBUI_DEFAULT_ENABLED and LLAMA_UI_DEFAULT_ENABLED * refactor: rename CLI flags (--webui -> --ui) with backward compat - Add --ui/--no-ui (old --webui/--no-webui kept as deprecated aliases) - Add --ui-config (old --webui-config kept as deprecated alias) - Add --ui-config-file (old --webui-config-file kept as deprecated alias) - Add --ui-mcp-proxy/--no-ui-mcp-proxy (old --webui-mcp-proxy kept as deprecated) - Add new env vars: LLAMA_ARG_UI, LLAMA_ARG_UI_CONFIG, LLAMA_ARG_UI_CONFIG_FILE, LLAMA_ARG_UI_MCP_PROXY - C++ struct fields: params.ui, params.ui_config_json, params.ui_mcp_proxy added alongside old fields - Backward compat: old fields synced to new ones in g_params_to_internals * refactor: update C++ server internals with backward compat - Rename json_webui_settings -> json_ui_settings (both kept in server_context_meta) - Rename params.webui usage -> params.ui (both synced, old still works) - JSON API emits both "ui"/"ui_settings" and "webui"/"webui_settings" keys - Server routes use params.ui_mcp_proxy || params.webui_mcp_proxy - Preprocessor guards use #if defined(LLAMA_BUILD_UI) || defined(LLAMA_BUILD_WEBUI) * refactor: rename CI/CD workflows, artifacts, and build script - Rename webui-build.yml -> ui-build.yml; artifact webui-build -> ui-build - Rename webui-publish.yml -> ui-publish.yml; var HF_BUCKET_WEBUI_STATIC_OUTPUT -> HF_BUCKET_UI_STATIC_OUTPUT - Rename server-webui.yml -> server-ui.yml; job webui-build/checks -> ui-build/checks - Update server.yml: job/artifact refs webui-build -> ui-build - Update release.yml: all webui-build/publish refs -> ui-build/publish; HF_TOKEN_WEBUI_STATIC_OUTPUT -> HF_TOKEN_UI_STATIC_OUTPUT - Update server-self-hosted.yml: webui-build -> ui-build - Update build-self-hosted.yml: HF_WEBUI_VERSION -> HF_UI_VERSION - Rename webui-download.cmake -> ui-download.cmake (internal refs updated) - Update labeler.yml: server/webui -> server/ui path label * docs: update CODEOWNERS and server README docs - Update CODEOWNERS: team ggml-org/llama-webui -> ggml-org/llama-ui, path /tools/server/webui/ -> /tools/ui/ - Update server README.md: CLI tables show --ui flags with deprecated --webui aliases - Update server README-dev.md: "WebUI" -> "UI", paths updated to tools/ui/ * fix: Small fixes for UI build * fix: CMake.txt syntax * chore: Formatting * fix: `.editorconfig` for llama-ui * chore: Formatting * refactor: Use `APP_NAME` in Error route * refactor: Cleanup * refactor: Single migration service * make llama-ui a linkable target * fix: UI Build output * fix: Missing change * fix: separate llama-ui npm build output into build/tools/ui/dist subfolder + use cmake npm build instead of downloading ui-build.yml artifacts in CI * refactor: UI workflows cleanup --------- Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
This commit is contained in:
co-authored by
Xuan Son Nguyen
parent
49d1701bd2
commit
59778f0196
@@ -0,0 +1,285 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { AlertTriangle, RefreshCw, Key, CheckCircle, XCircle } from '@lucide/svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import { serverStore, serverLoading } from '$lib/stores/server.svelte';
|
||||
import { config, settingsStore } from '$lib/stores/settings.svelte';
|
||||
import { SETTINGS_KEYS } from '$lib/constants';
|
||||
import { ROUTES } from '$lib/constants/routes';
|
||||
import { fade, fly, scale } from 'svelte/transition';
|
||||
import { KeyboardKey } from '$lib/enums';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
error: string;
|
||||
onRetry?: () => void;
|
||||
showRetry?: boolean;
|
||||
showTroubleshooting?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
class: className = '',
|
||||
error,
|
||||
onRetry,
|
||||
showRetry = true,
|
||||
showTroubleshooting = false
|
||||
}: Props = $props();
|
||||
|
||||
let isServerLoading = $derived(serverLoading());
|
||||
let isAccessDeniedError = $derived(
|
||||
error.toLowerCase().includes('access denied') ||
|
||||
error.toLowerCase().includes('invalid api key') ||
|
||||
error.toLowerCase().includes('unauthorized') ||
|
||||
error.toLowerCase().includes('401') ||
|
||||
error.toLowerCase().includes('403')
|
||||
);
|
||||
|
||||
let apiKeyInput = $state('');
|
||||
let showApiKeyInput = $state(false);
|
||||
let apiKeyState = $state<'idle' | 'validating' | 'success' | 'error'>('idle');
|
||||
let apiKeyError = $state('');
|
||||
|
||||
function handleRetryConnection() {
|
||||
if (onRetry) {
|
||||
onRetry();
|
||||
} else {
|
||||
serverStore.fetch();
|
||||
}
|
||||
}
|
||||
|
||||
function handleShowApiKeyInput() {
|
||||
showApiKeyInput = true;
|
||||
// Pre-fill with current API key if it exists
|
||||
const currentConfig = config();
|
||||
apiKeyInput = currentConfig.apiKey?.toString() || '';
|
||||
}
|
||||
|
||||
async function handleSaveApiKey() {
|
||||
if (!apiKeyInput.trim()) return;
|
||||
|
||||
apiKeyState = 'validating';
|
||||
apiKeyError = '';
|
||||
|
||||
try {
|
||||
// Update the API key in settings first
|
||||
settingsStore.updateConfig(SETTINGS_KEYS.API_KEY, apiKeyInput.trim());
|
||||
|
||||
// Test the API key by making a real request to the server
|
||||
const response = await fetch(`${base}/props`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${apiKeyInput.trim()}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// API key is valid - User Story B
|
||||
apiKeyState = 'success';
|
||||
|
||||
// Show success state briefly, then navigate to home
|
||||
setTimeout(() => {
|
||||
goto(ROUTES.START);
|
||||
}, 1000);
|
||||
} else {
|
||||
// API key is invalid - User Story A
|
||||
apiKeyState = 'error';
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
apiKeyError = 'Invalid API key - please check and try again';
|
||||
} else {
|
||||
apiKeyError = `Authentication failed (${response.status})`;
|
||||
}
|
||||
|
||||
// Reset to idle state after showing error (don't reload UI)
|
||||
setTimeout(() => {
|
||||
apiKeyState = 'idle';
|
||||
}, 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
// Network or other errors - User Story A
|
||||
apiKeyState = 'error';
|
||||
|
||||
if (error instanceof Error) {
|
||||
if (error.message.includes('fetch')) {
|
||||
apiKeyError = 'Cannot connect to server - check if server is running';
|
||||
} else {
|
||||
apiKeyError = error.message;
|
||||
}
|
||||
} else {
|
||||
apiKeyError = 'Connection error - please try again';
|
||||
}
|
||||
|
||||
// Reset to idle state after showing error (don't reload UI)
|
||||
setTimeout(() => {
|
||||
apiKeyState = 'idle';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function handleApiKeyKeydown(event: KeyboardEvent) {
|
||||
if (event.key === KeyboardKey.ENTER) {
|
||||
handleSaveApiKey();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex h-full items-center justify-center {className}">
|
||||
<div class="w-full max-w-md px-4 text-center">
|
||||
<div class="mb-6" in:fade={{ duration: 300 }}>
|
||||
<div
|
||||
class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-destructive/10"
|
||||
>
|
||||
<AlertTriangle class="h-8 w-8 text-destructive" />
|
||||
</div>
|
||||
|
||||
<h2 class="mb-2 text-xl font-semibold">Server Connection Error</h2>
|
||||
|
||||
<p class="mb-4 text-sm text-muted-foreground">
|
||||
{error}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{#if isAccessDeniedError && !showApiKeyInput}
|
||||
<div in:fly={{ y: 10, duration: 300, delay: 200 }} class="mb-4">
|
||||
<Button onclick={handleShowApiKeyInput} variant="outline" class="w-full">
|
||||
<Key class="h-4 w-4" />
|
||||
Enter API Key
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showApiKeyInput}
|
||||
<div in:fly={{ y: 10, duration: 300, delay: 200 }} class="mb-4 space-y-3 text-left">
|
||||
<div class="space-y-2">
|
||||
<Label for="api-key-input" class="text-sm font-medium">API Key</Label>
|
||||
|
||||
<div class="relative">
|
||||
<Input
|
||||
id="api-key-input"
|
||||
placeholder="Enter your API key..."
|
||||
bind:value={apiKeyInput}
|
||||
onkeydown={handleApiKeyKeydown}
|
||||
class="w-full pr-10 {apiKeyState === 'error'
|
||||
? 'border-destructive'
|
||||
: apiKeyState === 'success'
|
||||
? 'border-green-500'
|
||||
: ''}"
|
||||
disabled={apiKeyState === 'validating'}
|
||||
/>
|
||||
{#if apiKeyState === 'validating'}
|
||||
<div class="absolute top-1/2 right-3 -translate-y-1/2">
|
||||
<RefreshCw class="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
{:else if apiKeyState === 'success'}
|
||||
<div
|
||||
class="absolute top-1/2 right-3 -translate-y-1/2"
|
||||
in:scale={{ duration: 200, start: 0.8 }}
|
||||
>
|
||||
<CheckCircle class="h-4 w-4 text-green-500" />
|
||||
</div>
|
||||
{:else if apiKeyState === 'error'}
|
||||
<div
|
||||
class="absolute top-1/2 right-3 -translate-y-1/2"
|
||||
in:scale={{ duration: 200, start: 0.8 }}
|
||||
>
|
||||
<XCircle class="h-4 w-4 text-destructive" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if apiKeyError}
|
||||
<p class="text-sm text-destructive" in:fly={{ y: -10, duration: 200 }}>
|
||||
{apiKeyError}
|
||||
</p>
|
||||
{/if}
|
||||
{#if apiKeyState === 'success'}
|
||||
<p class="text-sm text-green-600" in:fly={{ y: -10, duration: 200 }}>
|
||||
✓ API key validated successfully! Connecting...
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
onclick={handleSaveApiKey}
|
||||
disabled={!apiKeyInput.trim() ||
|
||||
apiKeyState === 'validating' ||
|
||||
apiKeyState === 'success'}
|
||||
class="flex-1"
|
||||
>
|
||||
{#if apiKeyState === 'validating'}
|
||||
<RefreshCw class="h-4 w-4 animate-spin" />
|
||||
Validating...
|
||||
{:else if apiKeyState === 'success'}
|
||||
Success!
|
||||
{:else}
|
||||
Save & Retry
|
||||
{/if}
|
||||
</Button>
|
||||
<Button
|
||||
onclick={() => {
|
||||
showApiKeyInput = false;
|
||||
apiKeyState = 'idle';
|
||||
apiKeyError = '';
|
||||
}}
|
||||
variant="outline"
|
||||
class="flex-1"
|
||||
disabled={apiKeyState === 'validating'}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showRetry}
|
||||
<div in:fly={{ y: 10, duration: 300, delay: 200 }}>
|
||||
<Button onclick={handleRetryConnection} disabled={isServerLoading} class="w-full">
|
||||
{#if isServerLoading}
|
||||
<RefreshCw class="h-4 w-4 animate-spin" />
|
||||
|
||||
Connecting...
|
||||
{:else}
|
||||
<RefreshCw class="h-4 w-4" />
|
||||
|
||||
Retry Connection
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showTroubleshooting}
|
||||
<div class="mt-4 text-left" in:fly={{ y: 10, duration: 300, delay: 400 }}>
|
||||
<details class="text-sm">
|
||||
<summary class="cursor-pointer text-muted-foreground hover:text-foreground">
|
||||
Troubleshooting
|
||||
</summary>
|
||||
|
||||
<div class="mt-2 space-y-3 text-xs text-muted-foreground">
|
||||
<div class="space-y-2">
|
||||
<p class="mb-4 font-medium">Start the llama-server:</p>
|
||||
|
||||
<div class="rounded bg-muted/50 px-2 py-1 font-mono text-xs">
|
||||
<p>llama-server -hf ggml-org/gemma-3-4b-it-GGUF</p>
|
||||
</div>
|
||||
|
||||
<p>or</p>
|
||||
|
||||
<div class="rounded bg-muted/50 px-2 py-1 font-mono text-xs">
|
||||
<p class="mt-1">llama-server -m locally-stored-model.gguf</p>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-disc space-y-1 pl-4">
|
||||
<li>Check that the server is accessible at the correct URL</li>
|
||||
|
||||
<li>Verify your network connection</li>
|
||||
|
||||
<li>Check server logs for any error messages</li>
|
||||
</ul>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { Server } from '@lucide/svelte';
|
||||
import { ServerStatus } from '$lib/components/app';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
let { class: className = '', message = 'Initializing connection to server...' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex h-full items-center justify-center {className}">
|
||||
<div class="text-center">
|
||||
<div class="mb-4" in:fade={{ duration: 300 }}>
|
||||
<div class="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-muted">
|
||||
<Server class="h-8 w-8 animate-pulse text-muted-foreground" />
|
||||
</div>
|
||||
|
||||
<h2 class="mb-2 text-xl font-semibold">Connecting to Server</h2>
|
||||
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<ServerStatus class="justify-center" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import { AlertTriangle, Server } from '@lucide/svelte';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { serverProps, serverLoading, serverError } from '$lib/stores/server.svelte';
|
||||
import { singleModelName } from '$lib/stores/models.svelte';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
showActions?: boolean;
|
||||
}
|
||||
|
||||
let { class: className = '', showActions = false }: Props = $props();
|
||||
|
||||
let error = $derived(serverError());
|
||||
let loading = $derived(serverLoading());
|
||||
let model = $derived(singleModelName());
|
||||
let serverData = $derived(serverProps());
|
||||
|
||||
function getStatusColor() {
|
||||
if (loading) return 'bg-yellow-500';
|
||||
if (error) return 'bg-red-500';
|
||||
if (serverData) return 'bg-green-500';
|
||||
|
||||
return 'bg-gray-500';
|
||||
}
|
||||
|
||||
function getStatusText() {
|
||||
if (loading) return 'Connecting...';
|
||||
if (error) return 'Connection Error';
|
||||
if (serverData) return 'Connected';
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex items-center space-x-3 {className}">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="h-2 w-2 rounded-full {getStatusColor()}"></div>
|
||||
|
||||
<span class="text-sm text-muted-foreground">{getStatusText()}</span>
|
||||
</div>
|
||||
|
||||
{#if serverData && !error}
|
||||
<Badge variant="outline" class="text-xs">
|
||||
<Server class="mr-1 h-3 w-3" />
|
||||
|
||||
{model || 'Unknown Model'}
|
||||
</Badge>
|
||||
|
||||
{#if serverData?.default_generation_settings?.n_ctx}
|
||||
<Badge variant="secondary" class="text-xs">
|
||||
ctx: {serverData.default_generation_settings.n_ctx.toLocaleString()}
|
||||
</Badge>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if showActions && error}
|
||||
<Button variant="outline" size="sm" class="text-destructive">
|
||||
<AlertTriangle class="h-4 w-4" />
|
||||
|
||||
{error}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
*
|
||||
* SERVER
|
||||
*
|
||||
* Components for displaying server connection state and handling
|
||||
* connection errors. Integrates with serverStore for state management.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* **ServerStatus** - Server connection status indicator
|
||||
*
|
||||
* Compact status display showing connection state, model name,
|
||||
* and context size. Used in headers and loading screens.
|
||||
*
|
||||
* **Architecture:**
|
||||
* - Reads state from serverStore (props, loading, error)
|
||||
* - Displays model name from modelsStore
|
||||
*
|
||||
* **Features:**
|
||||
* - Status dot: green (connected), yellow (connecting), red (error), gray (unknown)
|
||||
* - Status text label
|
||||
* - Model name badge with icon
|
||||
* - Context size badge
|
||||
* - Optional error action button
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <ServerStatus showActions />
|
||||
* ```
|
||||
*/
|
||||
export { default as ServerStatus } from './ServerStatus.svelte';
|
||||
|
||||
/**
|
||||
* **ServerErrorSplash** - Full-screen connection error display
|
||||
*
|
||||
* Blocking error screen shown when server connection fails.
|
||||
* Provides retry options and API key input for authentication errors.
|
||||
*
|
||||
* **Architecture:**
|
||||
* - Detects access denied errors for API key flow
|
||||
* - Validates API key against server before saving
|
||||
* - Integrates with settingsStore for API key persistence
|
||||
*
|
||||
* **Features:**
|
||||
* - Error message display with icon
|
||||
* - Retry connection button with loading state
|
||||
* - API key input for authentication errors
|
||||
* - API key validation with success/error feedback
|
||||
* - Troubleshooting section with server start commands
|
||||
* - Animated transitions for UI elements
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <ServerErrorSplash
|
||||
* error={serverError}
|
||||
* onRetry={handleRetry}
|
||||
* showTroubleshooting
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
export { default as ServerErrorSplash } from './ServerErrorSplash.svelte';
|
||||
|
||||
/**
|
||||
* **ServerLoadingSplash** - Full-screen loading display
|
||||
*
|
||||
* Shown during initial server connection. Displays loading animation
|
||||
* with ServerStatus component for real-time connection state.
|
||||
*
|
||||
* **Features:**
|
||||
* - Animated server icon
|
||||
* - Customizable loading message
|
||||
* - Embedded ServerStatus for live updates
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <ServerLoadingSplash message="Connecting to server..." />
|
||||
* ```
|
||||
*/
|
||||
export { default as ServerLoadingSplash } from './ServerLoadingSplash.svelte';
|
||||
Reference in New Issue
Block a user