This commit is contained in:
Timothy Jaeryang Baek 2026-08-30 12:36:16 -04:00
parent 22379ded1a
commit b9765fe979
7 changed files with 138 additions and 5 deletions

View file

@ -4,7 +4,7 @@
import { toast } from 'svelte-sonner';
import { updateUserInfo } from '$lib/apis/users';
import { getUserPosition } from '$lib/utils';
import { setTextScale } from '$lib/utils/text-scale';
import { normalizeAppFontFamily, setAppFontFamily, setTextScale } from '$lib/utils/text-scale';
import Minus from '$lib/components/icons/Minus.svelte';
import Plus from '$lib/components/icons/Plus.svelte';
@ -115,7 +115,10 @@
let showManageImageCompressionModal = false;
let textScale: number | null = null;
let fontFamily: string | null = null;
let fontFamilyInput = '';
let showTextScaleSlider = false;
let showFontFamilyInput = false;
const settingRowClass = 'flex items-center justify-between gap-2.5';
const settingLabelClass = 'min-w-0 text-xs text-gray-600 dark:text-gray-400';
const settingControlClass = 'flex shrink-0 items-center justify-end gap-1.5';
@ -234,9 +237,18 @@
};
export const save = async () => {
const normalizedFontFamily = normalizeAppFontFamily(fontFamilyInput) || null;
fontFamily = normalizedFontFamily;
fontFamilyInput = normalizedFontFamily ?? '';
if (!externalSettings) {
setAppFontFamily(fontFamily || defaultFontFamily());
}
saveSettings({
models: [defaultModelId],
imageCompressionSize: imageCompressionSize
imageCompressionSize: imageCompressionSize,
fontFamily
});
};
@ -255,6 +267,51 @@
saveSettings({ textScale });
};
const defaultFontFamily = () => normalizeAppFontFamily(defaultSettings.fontFamily) || null;
const previewFontFamily = (value: string) => {
fontFamilyInput = value;
fontFamily = normalizeAppFontFamily(value) || null;
if (!externalSettings) {
setAppFontFamily(fontFamily || defaultFontFamily());
}
};
const setFontFamilyHandler = (value: string | null) => {
const normalized = normalizeAppFontFamily(value);
fontFamily = normalized || null;
fontFamilyInput = normalized;
if (!externalSettings) {
setAppFontFamily(fontFamily || defaultFontFamily());
}
saveSettings({ fontFamily });
};
const toggleFontFamilyInput = () => {
if (!showFontFamilyInput && (fontFamily === null || isDefaultSetting('fontFamily'))) {
fontFamily = fontFamily ?? defaultFontFamily();
fontFamilyInput = fontFamily ?? '';
showFontFamilyInput = true;
if (!externalSettings) {
setAppFontFamily(fontFamily);
}
} else {
showFontFamilyInput = false;
fontFamily = null;
fontFamilyInput = '';
if (!externalSettings) {
setAppFontFamily(defaultFontFamily());
}
saveSettings({ fontFamily });
}
};
const init = () => {
titleAutoGenerate = currentSettings?.title?.auto ?? true;
autoTags = currentSettings?.autoTags ?? true;
@ -338,7 +395,10 @@
webSearch = currentSettings?.webSearch ?? null;
textScale = currentSettings?.textScale ?? null;
fontFamily = normalizeAppFontFamily(currentSettings?.fontFamily) || null;
fontFamilyInput = fontFamily ?? '';
showTextScaleSlider = false;
showFontFamilyInput = false;
defaultUploadContext = currentSettings?.defaultUploadContext ?? 'focused';
};
@ -488,6 +548,46 @@
</p>
</div>
<div>
<div class={settingRowClass}>
<label id="font-family-label" class={settingLabelClass} for="font-family-input">
{$i18n.t('Font Family')}
</label>
<div class={settingControlClass}>
<button
class={actionButtonClass}
aria-labelledby="font-family-label font-family-state"
type="button"
on:click={toggleFontFamilyInput}
>
<span id="font-family-state">
{!showFontFamilyInput && (fontFamily === null || isDefaultSetting('fontFamily'))
? $i18n.t('Default')
: $i18n.t('Custom')}
</span>
</button>
</div>
</div>
{#if showFontFamilyInput || (fontFamily !== null && !isDefaultSetting('fontFamily'))}
<div class="mt-1.5 flex items-center pb-1">
<input
id="font-family-input"
class="h-7 w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500"
type="text"
placeholder="Aptos"
bind:value={fontFamilyInput}
on:input={(event) => previewFontFamily(event.currentTarget.value)}
on:change={(event) => setFontFamilyHandler(event.currentTarget.value)}
/>
</div>
{/if}
<p class={settingDescriptionClass}>
{$i18n.t('Use a local font family for the app interface.')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="accessibility-mode-label" class={settingLabelClass}>

View file

@ -1278,6 +1278,7 @@
"Firecrawl Timeout (s)": "",
"Floating Quick Actions": "",
"Focus Chat Input": "",
"Font Family": "",
"Folder": "",
"Folder Background Image": "",
"Folder created successfully": "",
@ -2903,6 +2904,7 @@
"Use": "",
"Use /v1/chat/completions endpoint instead of /v1/audio/transcriptions for potentially better accuracy.": "",
"Use a Gemini API key for image generation.": "",
"Use a local font family for the app interface.": "",
"Use a valid event name or pattern like user.*": "",
"Use a wider chat layout on large displays.": "",
"Use an API key when your ComfyUI server requires one.": "",

View file

@ -1278,6 +1278,7 @@
"Firecrawl Timeout (s)": "",
"Floating Quick Actions": "",
"Focus Chat Input": "",
"Font Family": "",
"Folder": "",
"Folder Background Image": "",
"Folder created successfully": "",
@ -2903,6 +2904,7 @@
"Use": "",
"Use /v1/chat/completions endpoint instead of /v1/audio/transcriptions for potentially better accuracy.": "",
"Use a Gemini API key for image generation.": "",
"Use a local font family for the app interface.": "",
"Use a valid event name or pattern like user.*": "",
"Use a wider chat layout on large displays.": "",
"Use an API key when your ComfyUI server requires one.": "",

View file

@ -231,6 +231,7 @@ type Settings = {
imageCompression?: boolean;
imageCompressionSize?: any;
textScale?: number;
fontFamily?: string | null;
widescreenMode?: null;
largeTextAsFile?: boolean;
promptAutocomplete?: boolean;

View file

@ -1,7 +1,27 @@
export const setTextScale = (scale) => {
export const setTextScale = (scale: number) => {
if (typeof document === 'undefined') {
return;
}
document.documentElement.style.setProperty('--app-text-scale', `${scale}`);
};
export const normalizeAppFontFamily = (fontFamily?: string | null) =>
typeof fontFamily === 'string' ? fontFamily.trim().replace(/[\u0000-\u001f\u007f]/g, '') : '';
export const setAppFontFamily = (fontFamily?: string | null) => {
if (typeof document === 'undefined') {
return;
}
const normalized = normalizeAppFontFamily(fontFamily);
if (!normalized) {
document.documentElement.style.removeProperty('--app-font-family');
return;
}
document.documentElement.style.setProperty(
'--app-font-family',
`"${normalized.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
);
};

View file

@ -14,7 +14,7 @@
import { getBanners } from '$lib/apis/configs';
import { getTerminalServers } from '$lib/apis/terminal';
import { getUserSettings } from '$lib/apis/users';
import { setTextScale } from '$lib/utils/text-scale';
import { setAppFontFamily, setTextScale } from '$lib/utils/text-scale';
import { WEBUI_VERSION, WEBUI_API_BASE_URL } from '$lib/constants';
import { compareVersion } from '$lib/utils';
@ -98,6 +98,7 @@
loadKeybindings(userSettings?.keybindings);
setTextScale($settings?.textScale ?? 1);
setAppFontFamily($settings?.fontFamily ?? null);
if (cb) {
await cb();

View file

@ -36,7 +36,14 @@
}
@layer base {
html,
html {
font-family:
var(--app-font-family, -apple-system), BlinkMacSystemFont, 'Inter', 'Vazirmatn',
ui-sans-serif, system-ui, 'Segoe UI', Roboto, Ubuntu, Cantarell, 'Noto Sans', sans-serif,
'Helvetica Neue', Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
}
pre {
font-family:
-apple-system, BlinkMacSystemFont, 'Inter', 'Vazirmatn', ui-sans-serif, system-ui, 'Segoe UI',