Skip to content

Commit 2a3d057

Browse files
committed
feat: Improves chat settings and UI layout
Refactors the chat settings dialog to use localStorage for persistence and introduces dedicated components for sections and footer. This change also adjusts the header's position relative to the sidebar and adds border styling for improved visual clarity.
1 parent e714579 commit 2a3d057

File tree

7 files changed

+132
-72
lines changed

7 files changed

+132
-72
lines changed

tools/server/webui/src/lib/components/app/chat/ChatHeader.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</script>
1212

1313
<header
14-
class="md:background-transparent bg-background/40 pointer-events-none fixed left-0 right-0 top-0 z-50 flex w-full items-center justify-end p-4 backdrop-blur-xl"
14+
class="md:background-transparent bg-background/40 pointer-events-none fixed left-[var(--sidebar-width)] right-0 top-0 z-50 flex items-center justify-end p-4 backdrop-blur-xl"
1515
>
1616
<div class="pointer-events-auto flex items-center space-x-2">
1717
<Button variant="ghost" size="sm" onclick={toggleSettings}>

tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsDialog.svelte

Lines changed: 23 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@
66
import { ScrollArea } from '$lib/components/ui/scroll-area';
77
import { Textarea } from '$lib/components/ui/textarea';
88
import { Settings, Filter, Hand, MessageSquare, Plus, Beaker } from '@lucide/svelte';
9-
import type { ConfigValue, FieldConfig } from '$lib/types/settings';
9+
import type { FieldConfig } from '$lib/types/settings';
10+
import {
11+
getConfig,
12+
setConfig,
13+
resetConfig,
14+
CONFIG_DEFAULT,
15+
type ConfigType
16+
} from '$lib/utils/settings';
17+
import ChatSettingsFooter from './ChatSettingsFooter.svelte';
18+
import ChatSettingsSection from './ChatSettingsSection.svelte';
1019
1120
interface Props {
1221
onOpenChange?: (open: boolean) => void;
@@ -15,59 +24,22 @@
1524
1625
let { onOpenChange, open = false }: Props = $props();
1726
18-
// Mock configuration - will be replaced with real stores
19-
let config: Record<string, ConfigValue> = $state({
20-
// General
21-
apiKey: '',
22-
systemMessage: '',
23-
temperature: 0.8,
24-
top_k: 40,
25-
top_p: 0.95,
26-
min_p: 0.05,
27-
max_tokens: 2048,
28-
pasteLongTextToFileLen: 2000,
29-
pdfAsImage: false,
27+
// Load configuration from localStorage
28+
let config: ConfigType = $state(getConfig());
3029
31-
// Samplers
32-
samplers: 'top_k;tfs_z;typical_p;top_p;min_p;temperature',
33-
dynatemp_range: 0.0,
34-
dynatemp_exponent: 1.0,
35-
typical_p: 1.0,
36-
xtc_probability: 0.0,
37-
xtc_threshold: 0.1,
38-
39-
// Penalties
40-
repeat_last_n: 64,
41-
repeat_penalty: 1.0,
42-
presence_penalty: 0.0,
43-
frequency_penalty: 0.0,
44-
dry_multiplier: 0.0,
45-
dry_base: 1.75,
46-
dry_allowed_length: 2,
47-
dry_penalty_last_n: -1,
48-
49-
// Reasoning
50-
showThoughtInProgress: true,
51-
excludeThoughtOnReq: false,
52-
53-
// Advanced
54-
showTokensPerSecond: false,
55-
custom: '',
56-
57-
// Experimental
58-
pyInterpreterEnabled: false
59-
});
60-
61-
const defaultConfig = $state.snapshot(config);
30+
// Use CONFIG_DEFAULT for placeholders to avoid state reference issues
31+
const defaultConfig = CONFIG_DEFAULT;
6232
6333
function handleSave() {
64-
// TODO: Save to stores and localStorage
65-
console.log('Saving config:', config);
34+
// Save configuration to localStorage
35+
setConfig(config);
36+
console.log('Settings saved to localStorage');
6637
onOpenChange?.(false);
6738
}
6839
6940
function handleReset() {
70-
Object.assign(config, defaultConfig);
41+
// Reset to default configuration
42+
config = resetConfig();
7143
}
7244
7345
function handleClose() {
@@ -172,7 +144,7 @@
172144
<Dialog.Root {open} {onOpenChange}>
173145
<Dialog.Content class="flex h-[64vh] flex-col gap-0 p-0" style="max-width: 48rem;">
174146
<div class="flex flex-1 overflow-hidden">
175-
<div class="w-64 border-r p-6">
147+
<div class="border-border/30 w-64 border-r p-6">
176148
<nav class="space-y-1 py-2">
177149
<Dialog.Title class="mb-6 flex items-center gap-2">Settings</Dialog.Title>
178150

@@ -193,12 +165,7 @@
193165

194166
<ScrollArea class="flex-1">
195167
<div class="space-y-6 p-6">
196-
<div class="flex items-center gap-2 border-b pb-2">
197-
<currentSection.icon class="h-5 w-5" />
198-
<h3 class="text-lg font-semibold">{currentSection.title}</h3>
199-
</div>
200-
201-
<div class="space-y-6">
168+
<ChatSettingsSection title={currentSection.title} icon={currentSection.icon}>
202169
{#each currentSection.fields as field}
203170
<div class="space-y-2">
204171
{#if field.type === 'input'}
@@ -265,7 +232,7 @@
265232
{/if}
266233
</div>
267234
{/each}
268-
</div>
235+
</ChatSettingsSection>
269236

270237
<div class="mt-8 border-t pt-6">
271238
<p class="text-muted-foreground text-xs">
@@ -276,14 +243,6 @@
276243
</ScrollArea>
277244
</div>
278245

279-
<div class="flex justify-between border-t p-6">
280-
<Button variant="outline" onclick={handleReset}>Reset to default</Button>
281-
282-
<div class="flex gap-2">
283-
<Button variant="outline" onclick={handleClose}>Close</Button>
284-
285-
<Button variant="default" onclick={handleSave}>Save</Button>
286-
</div>
287-
</div>
246+
<ChatSettingsFooter onSave={handleSave} onReset={handleReset} onClose={handleClose} />
288247
</Dialog.Content>
289248
</Dialog.Root>

tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsFooter.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
}
2323
</script>
2424

25-
<div class="flex justify-between border-t p-6">
25+
<div class="border-border/30 flex justify-between border-t p-6">
2626
<Button variant="outline" onclick={handleReset}>Reset to default</Button>
2727
<div class="flex gap-2">
2828
<Button variant="outline" onclick={handleClose}>Close</Button>

tools/server/webui/src/lib/components/app/chat/ChatSettings/ChatSettingsSection.svelte

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
let { children, title, icon }: Props = $props();
1111
</script>
1212

13-
<div class="flex-column flex items-center gap-2 border-b pb-2">
14-
{@render icon({ class: 'h-5 w-5' })}
15-
<h3 class="text-lg font-semibold">{title}</h3>
13+
<div>
14+
<div class="border-border/30 flex items-center gap-2 border-b pb-2">
15+
{@render icon({ class: 'h-5 w-5' })}
16+
<h3 class="text-lg font-semibold">{title}</h3>
17+
</div>
1618

1719
<div class="space-y-6">
1820
{@render children()}

tools/server/webui/src/lib/components/ui/dialog/dialog-content.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
bind:ref
2626
data-slot="dialog-content"
2727
class={cn(
28-
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed left-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
28+
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 border-border/30 fixed left-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
2929
className
3030
)}
3131
{...restProps}

tools/server/webui/src/lib/components/ui/sidebar/sidebar.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
data-sidebar="sidebar"
4343
data-slot="sidebar"
4444
data-mobile="true"
45-
class="z-99999 bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
45+
class="z-99999 sm:z-99 bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
4646
style="--sidebar-width: {SIDEBAR_WIDTH_MOBILE};"
4747
{side}
4848
>
@@ -80,7 +80,7 @@
8080
<div
8181
data-slot="sidebar-container"
8282
class={cn(
83-
'w-(--sidebar-width) z-999 fixed inset-y-0 hidden h-svh transition-[left,right,width] duration-200 ease-linear md:flex',
83+
'w-(--sidebar-width) z-999 fixed inset-y-0 hidden h-svh transition-[left,right,width] duration-200 ease-linear md:z-0 md:flex',
8484
side === 'left'
8585
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
8686
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Settings management using localStorage, compatible with webui-old structure
2+
3+
import { browser } from "$app/environment";
4+
5+
export const CONFIG_DEFAULT: Record<string, string | number | boolean> = {
6+
// Note: in order not to introduce breaking changes, please keep the same data type (number, string, etc) if you want to change the default value. Do not use null or undefined for default value.
7+
// Do not use nested objects, keep it single level. Prefix the key if you need to group them.
8+
apiKey: '',
9+
systemMessage: '',
10+
showTokensPerSecond: false,
11+
showThoughtInProgress: true,
12+
excludeThoughtOnReq: false,
13+
pasteLongTextToFileLen: 2000,
14+
pdfAsImage: false,
15+
// make sure these default values are in sync with `common.h`
16+
samplers: 'top_k;tfs_z;typical_p;top_p;min_p;temperature',
17+
temperature: 0.8,
18+
dynatemp_range: 0.0,
19+
dynatemp_exponent: 1.0,
20+
top_k: 40,
21+
top_p: 0.95,
22+
min_p: 0.05,
23+
xtc_probability: 0.0,
24+
xtc_threshold: 0.1,
25+
typical_p: 1.0,
26+
repeat_last_n: 64,
27+
repeat_penalty: 1.0,
28+
presence_penalty: 0.0,
29+
frequency_penalty: 0.0,
30+
dry_multiplier: 0.0,
31+
dry_base: 1.75,
32+
dry_allowed_length: 2,
33+
dry_penalty_last_n: -1,
34+
max_tokens: 2048,
35+
custom: '', // custom json-stringified object
36+
// experimental features
37+
pyInterpreterEnabled: false
38+
};
39+
40+
export type ConfigType = typeof CONFIG_DEFAULT & {
41+
[key: string]: string | number | boolean;
42+
};
43+
44+
/**
45+
* Get configuration from localStorage
46+
* Returns default values for missing keys to prevent breaking changes
47+
*/
48+
export function getConfig(): ConfigType {
49+
if (!browser) return { ...CONFIG_DEFAULT };
50+
try {
51+
const savedVal = JSON.parse(localStorage.getItem('config') || '{}');
52+
// to prevent breaking changes in the future, we always provide default value for missing keys
53+
return {
54+
...CONFIG_DEFAULT,
55+
...savedVal,
56+
};
57+
} catch (error) {
58+
console.warn('Failed to parse config from localStorage, using defaults:', error);
59+
return { ...CONFIG_DEFAULT };
60+
}
61+
}
62+
63+
/**
64+
* Save configuration to localStorage
65+
*/
66+
export function setConfig(config: ConfigType): void {
67+
try {
68+
localStorage.setItem('config', JSON.stringify(config));
69+
} catch (error) {
70+
console.error('Failed to save config to localStorage:', error);
71+
}
72+
}
73+
74+
/**
75+
* Get theme from localStorage
76+
*/
77+
export function getTheme(): string {
78+
return localStorage.getItem('theme') || 'auto';
79+
}
80+
81+
/**
82+
* Set theme in localStorage
83+
*/
84+
export function setTheme(theme: string): void {
85+
if (theme === 'auto') {
86+
localStorage.removeItem('theme');
87+
} else {
88+
localStorage.setItem('theme', theme);
89+
}
90+
}
91+
92+
/**
93+
* Reset configuration to defaults
94+
*/
95+
export function resetConfig(): ConfigType {
96+
const defaultConfig = { ...CONFIG_DEFAULT };
97+
setConfig(defaultConfig);
98+
return defaultConfig;
99+
}

0 commit comments

Comments
 (0)