mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-28 17:07:31 -05:00
ui : polish the shell
The secondary button gets its own look back and the chat add button its own light surface. Pressable elements get a pointer cursor again, the font rendering smooths on the app shell, the root layout resolves its props probe through the providers' api url, and the agent skills stay out of prettier's way. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
@@ -19,3 +19,7 @@ test-results
|
||||
|
||||
# Vendored third party sources, kept byte identical to upstream
|
||||
src/lib/vendors/
|
||||
|
||||
# Agent skills installed from upstream, kept as installed
|
||||
.agents/
|
||||
skills-lock.json
|
||||
|
||||
+28
-1
@@ -6,6 +6,33 @@
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
/* shadcn components key their orientation styles off these, toggle-group among them */
|
||||
@custom-variant data-horizontal (&[data-orientation='horizontal']);
|
||||
@custom-variant data-vertical (&[data-orientation='vertical']);
|
||||
|
||||
/*
|
||||
* Tailwind v4 dropped the pointer cursor from buttons. Every control a user can
|
||||
* press gets it back, and anything disabled keeps the cursor its own class asks for.
|
||||
*/
|
||||
@layer base {
|
||||
button:not(:disabled),
|
||||
[role='button']:not([data-disabled]),
|
||||
[role='checkbox']:not([data-disabled]),
|
||||
[role='combobox']:not([data-disabled]),
|
||||
[role='menuitem']:not([data-disabled]),
|
||||
[role='menuitemcheckbox']:not([data-disabled]),
|
||||
[role='menuitemradio']:not([data-disabled]),
|
||||
[role='option']:not([data-disabled]),
|
||||
[role='radio']:not([data-disabled]),
|
||||
[role='switch']:not([data-disabled]),
|
||||
[role='tab']:not([data-disabled]),
|
||||
label[for],
|
||||
select,
|
||||
summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
@@ -140,7 +167,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
@apply bg-background text-foreground antialiased;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { page } from '$app/state';
|
||||
import { ChatForm } from '$lib/components/app';
|
||||
import { useDraftMessages } from '$lib/hooks/use-draft-messages.svelte';
|
||||
import { deviceStore } from '$lib/stores';
|
||||
import { deviceStore, uiStore } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
@@ -33,6 +33,15 @@
|
||||
}: Props = $props();
|
||||
|
||||
let chatFormRef: ChatForm | undefined = $state(undefined);
|
||||
|
||||
// a dialog that closed onto the chat asks for the composer to take focus
|
||||
$effect(() => {
|
||||
if (!uiStore.consumeComposerFocus()) return;
|
||||
|
||||
const frame = requestAnimationFrame(() => chatFormRef?.focus());
|
||||
|
||||
return () => cancelAnimationFrame(frame);
|
||||
});
|
||||
let formWrapperEl: HTMLDivElement | undefined = $state();
|
||||
let chatId = $derived(page.params.id as string | undefined);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
outline:
|
||||
'shadow-sm hover:text-accent-foreground hover:bg-muted-foreground/10 backdrop-blur-sm dark:border-input border',
|
||||
secondary:
|
||||
'bg-background dark:bg-muted-foreground/15 dark:text-secondary-foreground shadow-sm text-foreground hover:bg-muted-foreground/20 dark:hover:bg-muted-foreground/25',
|
||||
'bg-muted/30 dark:bg-muted-foreground/15 dark:text-secondary-foreground shadow-sm border-muted border text-foreground hover:bg-muted dark:hover:bg-muted-foreground/25',
|
||||
tertiary:
|
||||
'bg-muted/60 dark:bg-muted/75 shadow-sm border border-border/30 text-foreground hover:bg-muted/80 dark:border-border/20 dark:hover:bg-muted'
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import '../app.css';
|
||||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { SidebarNavigation } from '$lib/components/app';
|
||||
import { PwaMetaTags, PwaRefreshAlert } from '$lib/components/pwa';
|
||||
@@ -31,6 +30,7 @@
|
||||
versionStore
|
||||
} from '$lib/stores';
|
||||
import { initStores } from '$lib/stores/init';
|
||||
import { apiUrl } from '$lib/utils/api-base';
|
||||
import { ModeWatcher } from 'mode-watcher';
|
||||
import { untrack } from 'svelte';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -147,6 +147,11 @@
|
||||
});
|
||||
|
||||
function checkApiKey() {
|
||||
// the stored key authenticates the llama.cpp server serving this UI
|
||||
if (!serverStore.capabilities.props) {
|
||||
return;
|
||||
}
|
||||
|
||||
const apiKey = settingsStore.config.apiKey;
|
||||
|
||||
// Without a stored key there is nothing to re-validate here; the keyless
|
||||
@@ -167,7 +172,7 @@
|
||||
[HEADERS.AUTHORIZATION]: `${HEADERS.BEARER}${apiKey.trim()}`
|
||||
};
|
||||
|
||||
fetch(`${base}/props`, { headers })
|
||||
fetch(apiUrl('/props'), { headers })
|
||||
.then((response) => {
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
window.location.reload();
|
||||
@@ -230,37 +235,17 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch router models when in router mode (for status and modalities)
|
||||
// Wait for models to be loaded first, run only once
|
||||
let routerModelsFetched = false;
|
||||
|
||||
$effect(() => {
|
||||
const isRouter = serverStore.isRouterMode;
|
||||
const modelsCount = modelsStore.models.length;
|
||||
|
||||
// Only fetch router models once when we have models loaded and in router mode
|
||||
if (isRouter && modelsCount > 0 && !routerModelsFetched) {
|
||||
routerModelsFetched = true;
|
||||
|
||||
untrack(() => {
|
||||
modelsStore.fetchRouterModels();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Live model status and load progress via the /models/sse feed (router mode)
|
||||
// Live model status and load progress via the /models/sse feed (router mode).
|
||||
// The feed is kept for the session: switching to an external backend and back
|
||||
// must not tear it down and reconnect on every tab switch.
|
||||
$effect(() => {
|
||||
if (!browser) return;
|
||||
|
||||
if (!serverStore.isRouterMode) return;
|
||||
if (!serverStore.localIsRouter) return;
|
||||
|
||||
untrack(() => {
|
||||
modelsStore.status.subscribe();
|
||||
});
|
||||
|
||||
return () => {
|
||||
modelsStore.status.unsubscribe();
|
||||
};
|
||||
});
|
||||
|
||||
// Background MCP server health checks on app load.
|
||||
|
||||
Reference in New Issue
Block a user