open-webui/src/lib/components/common/InterfaceSettings.svelte
G30 e979a1f9e2
fix: saving Settings > Interface no longer turns the admin's default font into your own (#32497)
The Interface tab's Save sent fontFamily and imageCompressionSize every
time, read from the settings store, which already carries the admin's
Default Interface Settings for anything the user has not set. One Save
with nothing changed therefore stored the admin's default as a personal
value: the row switched from "Default" to "Custom", and a later change
to the admin default never reached that account.

Leave a value out of that Save while the user only inherits it, as
isDefaultSetting already decides for the "Default" label. Values the
user set keep saving as before.
2026-10-16 03:02:58 +04:00

1939 lines
55 KiB
Svelte

<script lang="ts">
import { canUseTemporaryChats, isSettingsAdmin } from '$lib/utils/settings-access';
import { config, settings, user } from '$lib/stores';
import { onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { updateUserInfo } from '$lib/apis/users';
import { getUserPosition } from '$lib/utils';
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';
import Switch from '$lib/components/common/Switch.svelte';
import ManageFloatingActionButtonsModal from '$lib/components/chat/Settings/Interface/ManageFloatingActionButtonsModal.svelte';
import ManageImageCompressionModal from '$lib/components/chat/Settings/Interface/ManageImageCompressionModal.svelte';
const i18n: any = getContext('i18n');
export let saveSettings: Function;
export let settingsValue: Record<string, any> | null = null;
export let personalSettingsValue: Record<string, any> = {};
$: currentSettings = (settingsValue ?? $settings ?? {}) as Record<string, any>;
$: externalSettings = settingsValue !== null;
$: defaultSettings = ($config?.ui?.default_interface_settings ?? {}) as Record<string, any>;
let backgroundImageUrl: string | null = null;
let inputFiles: FileList | null = null;
let filesInputElement: HTMLInputElement | null = null;
// Addons
let titleAutoGenerate = true;
let autoFollowUps = true;
let autoTags = true;
let responseAutoCopy = false;
let widescreenMode = false;
let splitLargeChunks = false;
let scrollOnBranchChange = true;
let scrollOnResponseGeneration = true;
let showFilesOnTerminalSelect = true;
let terminalFileDisplay: 'sidebar' | 'inline' = 'sidebar';
let userLocation = false;
// Interface
let showUsername = false;
let highContrastMode = false;
let detectArtifacts = true;
let displayMultiModelResponsesInTabs = false;
let richTextInput = true;
let showFormattingToolbar = false;
let insertPromptAsRichText = false;
let promptAutocomplete = false;
let largeTextAsFile = false;
let insertSuggestionPrompt = false;
let keepFollowUpPrompts = false;
let insertFollowUpPrompt = false;
let regenerateMenu = true;
let enableMessageQueue = true;
let landingPageMode = '';
let chatBubble = true;
let chatDirection: 'LTR' | 'RTL' | 'auto' = 'auto';
let ctrlEnterToSend = false;
let copyFormatted = false;
let temporaryChatByDefault = false;
let chatFadeStreamingText = true;
let collapseCodeBlocks = false;
let renderMarkdownInUserMessages = true;
let renderMarkdownInAssistantMessages = true;
let expandDetails = false;
let chatHoverPreview = true;
let renderMarkdownInPreviews = true;
let showChatTitleInTab = true;
let showFloatingActionButtons = true;
let floatingActionButtons: any = null;
let imageCompression = false;
let imageCompressionSize: any = {
width: '',
height: ''
};
let imageCompressionInChannels = true;
// chat export
let stylizedPdfExport = true;
// Admin - Show Update Available Toast
let showUpdateToast = true;
let showChangelog = true;
// File
let defaultUploadContext: 'full' | 'focused' = 'focused';
let showEmojiInCall = false;
let voiceInterruption = false;
let hapticFeedback = false;
let webSearch: string | null = null;
let iframeSandboxAllowScripts = true;
let iframeSandboxAllowSameOrigin = false;
let iframeSandboxAllowForms = true;
let iframeSandboxAllowDownloads = true;
let terminalPreviewAllowSameOrigin = false;
let showManageFloatingActionButtonsModal = false;
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';
const sectionHeadingClass = 'mt-4 text-xs text-gray-600 dark:text-gray-500';
const firstSectionHeadingClass = 'text-xs text-gray-600 dark:text-gray-500';
const settingDescriptionClass = 'mt-1.5 text-[0.6875rem] text-gray-600 dark:text-gray-500';
const actionButtonClass =
'text-xs text-gray-600 transition-colors hover:text-gray-900 dark:text-gray-400 dark:hover:text-white';
const hasSettingPath = (source: Record<string, any>, path: string) => {
let current: any = source;
for (const part of path.split('.')) {
if (
!current ||
typeof current !== 'object' ||
!Object.prototype.hasOwnProperty.call(current, part)
) {
return false;
}
current = current[part];
}
return true;
};
const settingPathByState: Record<string, string> = {
titleAutoGenerate: 'title.auto'
};
const isDefaultSetting = (stateKey: string) => {
const path = settingPathByState[stateKey] ?? stateKey;
return (
!externalSettings &&
hasSettingPath(defaultSettings, path) &&
!hasSettingPath(personalSettingsValue, path)
);
};
const toggleLandingPageMode = async () => {
landingPageMode = landingPageMode === '' ? 'chat' : '';
saveSettings({ landingPageMode: landingPageMode });
};
const toggleUserLocation = async () => {
if (externalSettings) {
saveSettings({ userLocation });
return;
}
if (userLocation) {
const position = await getUserPosition().catch((error) => {
toast.error(error.message);
return null;
});
if (position) {
await updateUserInfo(localStorage.token, { location: position });
toast.success($i18n.t('User location successfully retrieved.'));
} else {
userLocation = false;
}
}
saveSettings({ userLocation });
};
const toggleTitleAutoGenerate = async () => {
saveSettings({
title: {
...(currentSettings.title ?? {}),
auto: titleAutoGenerate
}
});
};
const toggleResponseAutoCopy = async () => {
if (externalSettings) {
saveSettings({ responseAutoCopy: responseAutoCopy });
return;
}
const permission = await navigator.clipboard
.readText()
.then(() => {
return 'granted';
})
.catch(() => {
return '';
});
if (permission === 'granted') {
saveSettings({ responseAutoCopy: responseAutoCopy });
} else {
responseAutoCopy = false;
toast.error(
$i18n.t(
'Clipboard write permission denied. Please check your browser settings to grant the necessary access.'
)
);
}
};
const toggleChangeChatDirection = async () => {
if (chatDirection === 'auto') {
chatDirection = 'LTR';
} else if (chatDirection === 'LTR') {
chatDirection = 'RTL';
} else if (chatDirection === 'RTL') {
chatDirection = 'auto';
}
saveSettings({ chatDirection });
};
const togglectrlEnterToSend = async () => {
ctrlEnterToSend = !ctrlEnterToSend;
saveSettings({ ctrlEnterToSend });
};
export const save = async () => {
const normalizedFontFamily = normalizeAppFontFamily(fontFamilyInput) || null;
fontFamily = normalizedFontFamily;
fontFamilyInput = normalizedFontFamily ?? '';
if (!externalSettings) {
setAppFontFamily(fontFamily || defaultFontFamily());
}
saveSettings({
...(isDefaultSetting('imageCompressionSize') ? {} : { imageCompressionSize }),
...(isDefaultSetting('fontFamily') && fontFamily === defaultFontFamily()
? {}
: { fontFamily })
});
};
const toggleWebSearch = async () => {
webSearch = webSearch === null ? 'always' : null;
saveSettings({ webSearch: webSearch });
};
const setTextScaleHandler = (scale: number) => {
textScale = scale;
if (!externalSettings) {
setTextScale(textScale);
}
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;
autoFollowUps = currentSettings?.autoFollowUps ?? true;
highContrastMode = currentSettings?.highContrastMode ?? false;
detectArtifacts = currentSettings?.detectArtifacts ?? true;
responseAutoCopy = currentSettings?.responseAutoCopy ?? false;
showUsername = currentSettings?.showUsername ?? false;
showUpdateToast = currentSettings?.showUpdateToast ?? true;
showChangelog = currentSettings?.showChangelog ?? true;
showEmojiInCall = currentSettings?.showEmojiInCall ?? false;
voiceInterruption = currentSettings?.voiceInterruption ?? false;
displayMultiModelResponsesInTabs = currentSettings?.displayMultiModelResponsesInTabs ?? false;
chatFadeStreamingText = currentSettings?.chatFadeStreamingText ?? true;
richTextInput = currentSettings?.richTextInput ?? true;
showFormattingToolbar = currentSettings?.showFormattingToolbar ?? false;
insertPromptAsRichText = currentSettings?.insertPromptAsRichText ?? false;
promptAutocomplete = currentSettings?.promptAutocomplete ?? false;
insertSuggestionPrompt = currentSettings?.insertSuggestionPrompt ?? false;
keepFollowUpPrompts = currentSettings?.keepFollowUpPrompts ?? false;
insertFollowUpPrompt = currentSettings?.insertFollowUpPrompt ?? false;
regenerateMenu = currentSettings?.regenerateMenu ?? true;
enableMessageQueue = currentSettings?.enableMessageQueue ?? true;
largeTextAsFile = currentSettings?.largeTextAsFile ?? false;
copyFormatted = currentSettings?.copyFormatted ?? false;
collapseCodeBlocks = currentSettings?.collapseCodeBlocks ?? false;
renderMarkdownInUserMessages = currentSettings?.renderMarkdownInUserMessages ?? true;
renderMarkdownInAssistantMessages = currentSettings?.renderMarkdownInAssistantMessages ?? true;
expandDetails = currentSettings?.expandDetails ?? false;
chatHoverPreview = currentSettings?.chatHoverPreview ?? true;
renderMarkdownInPreviews = currentSettings?.renderMarkdownInPreviews ?? true;
landingPageMode = currentSettings?.landingPageMode ?? '';
chatBubble = currentSettings?.chatBubble ?? true;
widescreenMode = currentSettings?.widescreenMode ?? false;
splitLargeChunks = currentSettings?.splitLargeChunks ?? false;
scrollOnBranchChange = currentSettings?.scrollOnBranchChange ?? true;
scrollOnResponseGeneration = currentSettings?.scrollOnResponseGeneration ?? true;
showFilesOnTerminalSelect = currentSettings?.showFilesOnTerminalSelect ?? true;
terminalFileDisplay = currentSettings?.terminalFileDisplay === 'inline' ? 'inline' : 'sidebar';
temporaryChatByDefault = currentSettings?.temporaryChatByDefault ?? false;
chatDirection = currentSettings?.chatDirection ?? 'auto';
userLocation = currentSettings?.userLocation ?? false;
showChatTitleInTab = currentSettings?.showChatTitleInTab ?? true;
iframeSandboxAllowScripts = currentSettings?.iframeSandboxAllowScripts ?? true;
iframeSandboxAllowSameOrigin = currentSettings?.iframeSandboxAllowSameOrigin ?? false;
iframeSandboxAllowForms = currentSettings?.iframeSandboxAllowForms ?? true;
iframeSandboxAllowDownloads = currentSettings?.iframeSandboxAllowDownloads ?? true;
terminalPreviewAllowSameOrigin = currentSettings?.terminalPreviewAllowSameOrigin ?? false;
stylizedPdfExport = currentSettings?.stylizedPdfExport ?? true;
hapticFeedback = currentSettings?.hapticFeedback ?? false;
ctrlEnterToSend = currentSettings?.ctrlEnterToSend ?? false;
showFloatingActionButtons = currentSettings?.showFloatingActionButtons ?? true;
floatingActionButtons = currentSettings?.floatingActionButtons ?? null;
imageCompression = currentSettings?.imageCompression ?? false;
imageCompressionSize = currentSettings?.imageCompressionSize ?? { width: '', height: '' };
imageCompressionInChannels = currentSettings?.imageCompressionInChannels ?? true;
backgroundImageUrl = currentSettings?.backgroundImageUrl ?? null;
webSearch = currentSettings?.webSearch ?? null;
textScale = currentSettings?.textScale ?? null;
fontFamily = normalizeAppFontFamily(currentSettings?.fontFamily) || null;
fontFamilyInput = fontFamily ?? '';
showTextScaleSlider = false;
showFontFamilyInput = false;
defaultUploadContext = currentSettings?.defaultUploadContext ?? 'focused';
};
let lastSettingsValue = settingsValue;
$: if (settingsValue !== lastSettingsValue) {
lastSettingsValue = settingsValue;
init();
}
onMount(async () => {
init();
});
</script>
<ManageFloatingActionButtonsModal
bind:show={showManageFloatingActionButtonsModal}
bind:floatingActionButtons
onSave={() => {
saveSettings({ floatingActionButtons });
}}
/>
<ManageImageCompressionModal
bind:show={showManageImageCompressionModal}
bind:size={imageCompressionSize}
onSave={() => {
saveSettings({ imageCompressionSize });
}}
/>
<input
bind:this={filesInputElement}
bind:files={inputFiles}
type="file"
hidden
accept="image/*"
on:change={() => {
let reader = new FileReader();
reader.onload = (event) => {
let originalImageUrl = `${(event.target as FileReader).result}`;
backgroundImageUrl = originalImageUrl;
saveSettings({ backgroundImageUrl });
};
if (
inputFiles &&
inputFiles.length > 0 &&
['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
) {
reader.readAsDataURL(inputFiles[0]);
} else {
console.log(`Unsupported File Type '${inputFiles?.[0]?.type}'.`);
inputFiles = null;
}
}}
/>
<div class="flex flex-col gap-2.5">
<h3 class={firstSectionHeadingClass}>
{$i18n.t('settings.personal.interface.sections.ui.title')}
</h3>
<div>
<div class={settingRowClass}>
<label id="ui-scale-label" class={settingLabelClass} for="ui-scale-slider">
{$i18n.t('settings.personal.interface.uiScale.label')}
</label>
<div class={settingControlClass}>
<button
class={actionButtonClass}
aria-live="polite"
type="button"
on:click={() => {
if (textScale === null || (isDefaultSetting('textScale') && !showTextScaleSlider)) {
textScale = textScale ?? defaultSettings.textScale ?? 1;
showTextScaleSlider = true;
} else {
showTextScaleSlider = false;
if (!externalSettings) {
setTextScale(defaultSettings.textScale ?? 1);
}
textScale = null;
saveSettings({ textScale });
}
}}
>
{#if textScale === null || (isDefaultSetting('textScale') && !showTextScaleSlider)}
<span>{$i18n.t('Default')}</span>
{:else}
<span>{textScale}x</span>
{/if}
</button>
</div>
</div>
{#if textScale !== null && (showTextScaleSlider || !isDefaultSetting('textScale'))}
<div class=" flex items-center gap-2 px-1 pb-1">
<button
type="button"
class="rounded-lg p-1 transition outline-gray-200 hover:bg-gray-100 dark:outline-gray-700 dark:hover:bg-gray-800"
on:click={() => {
textScale = Math.max(1, parseFloat(((textScale ?? 1) - 0.1).toFixed(2)));
setTextScaleHandler(textScale);
}}
aria-labelledby="ui-scale-label"
aria-label={$i18n.t('settings.personal.interface.decreaseUiScale.label')}
>
<Minus className="h-3.5 w-3.5" />
</button>
<div class="flex-1 flex items-center">
<input
id="ui-scale-slider"
class="w-full"
type="range"
min="1"
max="1.5"
step={0.01}
bind:value={textScale}
on:change={() => {
setTextScaleHandler(textScale ?? 1);
}}
aria-labelledby="ui-scale-label"
aria-valuemin="1"
aria-valuemax="1.5"
aria-valuenow={textScale}
aria-valuetext={`${textScale}x`}
/>
</div>
<button
type="button"
class="rounded-lg p-1 transition outline-gray-200 hover:bg-gray-100 dark:outline-gray-700 dark:hover:bg-gray-800"
on:click={() => {
textScale = Math.min(1.5, parseFloat(((textScale ?? 1) + 0.1).toFixed(2)));
setTextScaleHandler(textScale);
}}
aria-labelledby="ui-scale-label"
aria-label={$i18n.t('settings.personal.interface.increaseUiScale.label')}
>
<Plus className="h-3.5 w-3.5" />
</button>
</div>
{/if}
<p class={settingDescriptionClass}>
{$i18n.t('Set a local zoom level for the app interface.')}
</p>
</div>
<div>
<div class={settingRowClass}>
<label id="font-family-label" class={settingLabelClass} for="font-family-input">
{$i18n.t('settings.personal.interface.fontFamily.label')}
</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-600 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-500 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('settings.personal.interface.fontFamily.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="accessibility-mode-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.accessibilityMode.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="accessibility-mode-label"
tooltip={true}
bind:state={highContrastMode}
inherited={isDefaultSetting('highContrastMode')}
on:change={() => {
saveSettings({ highContrastMode });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.accessibilityMode.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="use-chat-title-as-tab-title-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.displayChatTitleInTab.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="use-chat-title-as-tab-title-label"
tooltip={true}
bind:state={showChatTitleInTab}
inherited={isDefaultSetting('showChatTitleInTab')}
on:change={() => {
saveSettings({ showChatTitleInTab });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.displayChatTitleInTab.description')}
</p>
</div>
<div>
<div id="allow-user-location-label" class={settingRowClass}>
<div class={settingLabelClass}>
{$i18n.t('settings.personal.interface.allowUserLocation.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="allow-user-location-label"
tooltip={true}
bind:state={userLocation}
inherited={isDefaultSetting('userLocation')}
on:change={() => {
toggleUserLocation();
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.allowUserLocation.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="haptic-feedback-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.hapticFeedback.label')} ({$i18n.t(
'settings.personal.interface.android.label'
)})
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="haptic-feedback-label"
tooltip={true}
bind:state={hapticFeedback}
inherited={isDefaultSetting('hapticFeedback')}
on:change={() => {
saveSettings({ hapticFeedback });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Use device vibration feedback on supported Android devices.')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="copy-formatted-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.copyFormattedText.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="copy-formatted-label"
tooltip={true}
bind:state={copyFormatted}
inherited={isDefaultSetting('copyFormatted')}
on:change={() => {
saveSettings({ copyFormatted });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.copyFormattedText.description')}
</p>
</div>
{#if isSettingsAdmin({ user: $user, config: $config })}
<div>
<div class={settingRowClass}>
<div id="toast-notifications-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.toastNotificationsForNewUpdates.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="toast-notifications-label"
tooltip={true}
bind:state={showUpdateToast}
inherited={isDefaultSetting('showUpdateToast')}
on:change={() => {
saveSettings({ showUpdateToast });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.toastNotificationsForNewUpdates.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="whats-new-label" class={settingLabelClass}>
{$i18n.t(`Show "What's New" Modal on Login`)}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="whats-new-label"
tooltip={true}
bind:state={showChangelog}
inherited={isDefaultSetting('showChangelog')}
on:change={() => {
saveSettings({ showChangelog });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Open the changelog modal after sign-in when enabled.')}
</p>
</div>
{/if}
<div class={sectionHeadingClass}>
{$i18n.t('settings.personal.interface.sections.chat.title')}
</div>
<div>
<div class={settingRowClass}>
<div id="enable-message-queue-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.enableMessageQueue.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="enable-message-queue-label"
tooltip={true}
bind:state={enableMessageQueue}
inherited={isDefaultSetting('enableMessageQueue')}
on:change={() => {
saveSettings({ enableMessageQueue });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.enableMessageQueue.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="chat-direction-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.chatDirection.label')}
</div>
<button
aria-labelledby="chat-direction-label chat-direction-mode"
class={actionButtonClass}
on:click={toggleChangeChatDirection}
type="button"
>
<span id="chat-direction-mode">
{chatDirection === 'LTR'
? $i18n.t('LTR')
: chatDirection === 'RTL'
? $i18n.t('RTL')
: $i18n.t('Auto')}
</span>
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.chatDirection.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="landing-page-mode-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.landingPageMode.label')}
</div>
<button
aria-labelledby="landing-page-mode-label notification-sound-state"
class={actionButtonClass}
on:click={() => {
toggleLandingPageMode();
}}
type="button"
>
<span id="notification-sound-state"
>{landingPageMode === ''
? $i18n.t('Default')
: $i18n.t('settings.personal.interface.sections.chat.title')}</span
>
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.landingPageMode.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="chat-background-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.chatBackgroundImage.label')}
</div>
<button
aria-labelledby="chat-background-label background-image-url-state"
class={actionButtonClass}
on:click={() => {
if (backgroundImageUrl !== null) {
backgroundImageUrl = null;
saveSettings({ backgroundImageUrl });
} else {
filesInputElement?.click();
}
}}
type="button"
>
<span id="background-image-url-state"
>{backgroundImageUrl !== null ? $i18n.t('Reset') : $i18n.t('Upload')}</span
>
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.chatBackgroundImage.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="chat-bubble-ui-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.chatBubbleUi.label')}
</div>
<div class={settingControlClass}>
<Switch
tooltip={true}
ariaLabelledbyId="chat-bubble-ui-label"
bind:state={chatBubble}
inherited={isDefaultSetting('chatBubble')}
on:change={() => {
saveSettings({ chatBubble });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.chatBubbleUi.description')}
</p>
</div>
{#if !chatBubble}
<div>
<div class={settingRowClass}>
<div id="chat-bubble-username-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.displayTheUsernameInsteadOfYouInTheChat.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="chat-bubble-username-label"
tooltip={true}
bind:state={showUsername}
inherited={isDefaultSetting('showUsername')}
on:change={() => {
saveSettings({ showUsername });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.displayTheUsernameInsteadOfYouInTheChat.description')}
</p>
</div>
{/if}
<div>
<div class={settingRowClass}>
<div id="widescreen-mode-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.widescreenMode.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="widescreen-mode-label"
tooltip={true}
bind:state={widescreenMode}
inherited={isDefaultSetting('widescreenMode')}
on:change={() => {
saveSettings({ widescreenMode });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.widescreenMode.description')}
</p>
</div>
{#if canUseTemporaryChats({ user: $user, config: $config })}
<div>
<div class={settingRowClass}>
<div id="temp-chat-default-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.temporaryChatByDefault.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="temp-chat-default-label"
tooltip={true}
bind:state={temporaryChatByDefault}
inherited={isDefaultSetting('temporaryChatByDefault')}
on:change={() => {
saveSettings({ temporaryChatByDefault });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.temporaryChatByDefault.description')}
</p>
</div>
{/if}
<div>
<div class={settingRowClass}>
<div id="fade-streaming-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.fadeEffectForStreamingText.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="fade-streaming-label"
tooltip={true}
bind:state={chatFadeStreamingText}
inherited={isDefaultSetting('chatFadeStreamingText')}
on:change={() => {
saveSettings({ chatFadeStreamingText });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.fadeEffectForStreamingText.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="render-markdown-user-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.renderMarkdownInUserMessages.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="render-markdown-user-label"
tooltip={true}
bind:state={renderMarkdownInUserMessages}
inherited={isDefaultSetting('renderMarkdownInUserMessages')}
on:change={() => {
saveSettings({ renderMarkdownInUserMessages });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.renderMarkdownInUserMessages.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="render-markdown-assistant-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.renderMarkdownInAssistantMessages.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="render-markdown-assistant-label"
tooltip={true}
bind:state={renderMarkdownInAssistantMessages}
inherited={isDefaultSetting('renderMarkdownInAssistantMessages')}
on:change={() => {
saveSettings({ renderMarkdownInAssistantMessages });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.renderMarkdownInAssistantMessages.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="render-markdown-in-previews-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.renderMarkdownInPreviews.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="render-markdown-in-previews-label"
tooltip={true}
bind:state={renderMarkdownInPreviews}
inherited={isDefaultSetting('renderMarkdownInPreviews')}
on:change={() => {
saveSettings({ renderMarkdownInPreviews });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.renderMarkdownInPreviews.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="auto-generation-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.titleAutoGeneration.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="auto-generation-label"
tooltip={true}
bind:state={titleAutoGenerate}
inherited={isDefaultSetting('titleAutoGenerate')}
on:change={() => {
toggleTitleAutoGenerate();
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.titleAutoGeneration.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div class={settingLabelClass} id="follow-up-auto-generation-label">
{$i18n.t('settings.personal.interface.followUpAutoGeneration.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="follow-up-auto-generation-label"
tooltip={true}
bind:state={autoFollowUps}
inherited={isDefaultSetting('autoFollowUps')}
on:change={() => {
saveSettings({ autoFollowUps });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.followUpAutoGeneration.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="chat-tags-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.chatTagsAutoGeneration.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="chat-tags-label"
tooltip={true}
bind:state={autoTags}
inherited={isDefaultSetting('autoTags')}
on:change={() => {
saveSettings({ autoTags });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.chatTagsAutoGeneration.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="auto-copy-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.autoCopyResponseToClipboard.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="auto-copy-label"
tooltip={true}
bind:state={responseAutoCopy}
inherited={isDefaultSetting('responseAutoCopy')}
on:change={() => {
toggleResponseAutoCopy();
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.autoCopyResponseToClipboard.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="response-auto-scroll-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.responseAutoScroll.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="response-auto-scroll-label"
tooltip={true}
bind:state={scrollOnResponseGeneration}
inherited={isDefaultSetting('scrollOnResponseGeneration')}
on:change={() => {
saveSettings({ scrollOnResponseGeneration });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.responseAutoScroll.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="scroll-on-branch-change-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.scrollOnBranchChange.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="scroll-on-branch-change-label"
tooltip={true}
bind:state={scrollOnBranchChange}
inherited={isDefaultSetting('scrollOnBranchChange')}
on:change={() => {
saveSettings({ scrollOnBranchChange });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.scrollOnBranchChange.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="insert-suggestion-prompt-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.insertSuggestionPromptToInput.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="insert-suggestion-prompt-label"
tooltip={true}
bind:state={insertSuggestionPrompt}
inherited={isDefaultSetting('insertSuggestionPrompt')}
on:change={() => {
saveSettings({ insertSuggestionPrompt });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.insertSuggestionPromptToInput.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="keep-follow-up-prompts-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.keepFollowUpPromptsInChat.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="keep-follow-up-prompts-label"
tooltip={true}
bind:state={keepFollowUpPrompts}
inherited={isDefaultSetting('keepFollowUpPrompts')}
on:change={() => {
saveSettings({ keepFollowUpPrompts });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.keepFollowUpPromptsInChat.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="insert-follow-up-prompt-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.insertFollowUpPromptToInput.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="insert-follow-up-prompt-label"
tooltip={true}
bind:state={insertFollowUpPrompt}
inherited={isDefaultSetting('insertFollowUpPrompt')}
on:change={() => {
saveSettings({ insertFollowUpPrompt });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.insertFollowUpPromptToInput.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="regenerate-menu-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.regenerateMenu.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="regenerate-menu-label"
tooltip={true}
bind:state={regenerateMenu}
inherited={isDefaultSetting('regenerateMenu')}
on:change={() => {
saveSettings({ regenerateMenu });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.regenerateMenu.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="always-collapse-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.alwaysCollapseCodeBlocks.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="always-collapse-label"
tooltip={true}
bind:state={collapseCodeBlocks}
inherited={isDefaultSetting('collapseCodeBlocks')}
on:change={() => {
saveSettings({ collapseCodeBlocks });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.alwaysCollapseCodeBlocks.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="always-expand-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.alwaysExpandDetails.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="always-expand-label"
tooltip={true}
bind:state={expandDetails}
inherited={isDefaultSetting('expandDetails')}
on:change={() => {
saveSettings({ expandDetails });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.alwaysExpandDetails.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="chat-hover-preview-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.chatHoverPreviews.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="chat-hover-preview-label"
tooltip={true}
bind:state={chatHoverPreview}
inherited={isDefaultSetting('chatHoverPreview')}
on:change={() => {
saveSettings({ chatHoverPreview });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.chatHoverPreviews.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="keep-followup-prompts-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.displayMultiModelResponsesInTabs.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="keep-followup-prompts-label"
tooltip={true}
bind:state={displayMultiModelResponsesInTabs}
inherited={isDefaultSetting('displayMultiModelResponsesInTabs')}
on:change={() => {
saveSettings({ displayMultiModelResponsesInTabs });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.displayMultiModelResponsesInTabs.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="terminal-file-display-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.terminalFileDisplay.label')}
</div>
<button
aria-labelledby="terminal-file-display-label terminal-file-display-state"
class={actionButtonClass}
on:click={() => {
terminalFileDisplay = terminalFileDisplay === 'inline' ? 'sidebar' : 'inline';
saveSettings({ terminalFileDisplay });
}}
type="button"
>
<span id="terminal-file-display-state">
{terminalFileDisplay === 'inline' ? $i18n.t('Inline') : $i18n.t('Sidebar')}
</span>
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.terminalFileDisplay.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="show-files-on-terminal-select-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.showFilesOnTerminalSelect.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="show-files-on-terminal-select-label"
tooltip={true}
bind:state={showFilesOnTerminalSelect}
inherited={isDefaultSetting('showFilesOnTerminalSelect')}
on:change={() => {
saveSettings({ showFilesOnTerminalSelect });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.showFilesOnTerminalSelect.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="terminal-preview-allow-same-origin-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.terminalPreviewAllowSameOrigin.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="terminal-preview-allow-same-origin-label"
tooltip={true}
bind:state={terminalPreviewAllowSameOrigin}
inherited={isDefaultSetting('terminalPreviewAllowSameOrigin')}
on:change={() => {
saveSettings({ terminalPreviewAllowSameOrigin });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.terminalPreviewAllowSameOrigin.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="stylized-pdf-export-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.stylizedPdfExport.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="stylized-pdf-export-label"
tooltip={true}
bind:state={stylizedPdfExport}
inherited={isDefaultSetting('stylizedPdfExport')}
on:change={() => {
saveSettings({ stylizedPdfExport });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.stylizedPdfExport.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="floating-action-buttons-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.floatingQuickActions.label')}
</div>
<div class={settingControlClass}>
{#if showFloatingActionButtons}
<button
class={actionButtonClass}
type="button"
aria-label={$i18n.t(
'settings.personal.interface.openModalToManageFloatingQuickActions.label'
)}
on:click={() => {
showManageFloatingActionButtonsModal = true;
}}
>
{$i18n.t('Manage')}
</button>
{/if}
<Switch
ariaLabelledbyId="floating-action-buttons-label"
tooltip={true}
bind:state={showFloatingActionButtons}
inherited={isDefaultSetting('showFloatingActionButtons')}
on:change={() => {
saveSettings({ showFloatingActionButtons });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Show the floating quick-action toolbar in chat.')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="web-search-in-chat-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.webSearchInChat.label')}
</div>
<button
aria-labelledby="web-search-in-chat-label web-search-state"
class={actionButtonClass}
on:click={() => {
toggleWebSearch();
}}
type="button"
>
<span id="web-search-state"
>{webSearch === 'always' ? $i18n.t('Always') : $i18n.t('Default')}</span
>
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.webSearchInChat.description')}
</p>
</div>
<div class={sectionHeadingClass}>
{$i18n.t('settings.personal.interface.sections.input.title')}
</div>
<div>
<div class={settingRowClass}>
<div id="enter-key-behavior-label ctrl-enter-to-send-state" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.enterKeyBehavior.label')}
</div>
<button
aria-labelledby="enter-key-behavior-label"
class={actionButtonClass}
on:click={() => {
togglectrlEnterToSend();
}}
type="button"
>
<span id="ctrl-enter-to-send-state"
>{ctrlEnterToSend === true
? $i18n.t('Ctrl+Enter to Send')
: $i18n.t('Enter to Send')}</span
>
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.enterKeyBehavior.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="rich-input-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.richTextInputForChat.label')}
</div>
<div class={settingControlClass}>
<Switch
tooltip={true}
ariaLabelledbyId="rich-input-label"
bind:state={richTextInput}
inherited={isDefaultSetting('richTextInput')}
on:change={() => {
saveSettings({ richTextInput });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.richTextInputForChat.description')}
</p>
</div>
{#if $config?.features?.enable_autocomplete_generation}
<div>
<div class={settingRowClass}>
<div id="prompt-autocompletion-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.promptAutocompletion.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="prompt-autocompletion-label"
tooltip={true}
bind:state={promptAutocomplete}
inherited={isDefaultSetting('promptAutocomplete')}
on:change={() => {
saveSettings({ promptAutocomplete });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.promptAutocompletion.description')}
</p>
</div>
{/if}
{#if richTextInput}
<div>
<div class={settingRowClass}>
<div id="show-formatting-toolbar-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.showFormattingToolbar.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="show-formatting-toolbar-label"
tooltip={true}
bind:state={showFormattingToolbar}
inherited={isDefaultSetting('showFormattingToolbar')}
on:change={() => {
saveSettings({ showFormattingToolbar });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.showFormattingToolbar.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="insert-prompt-as-rich-text-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.insertPromptAsRichText.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="insert-prompt-as-rich-text-label"
tooltip={true}
bind:state={insertPromptAsRichText}
inherited={isDefaultSetting('insertPromptAsRichText')}
on:change={() => {
saveSettings({ insertPromptAsRichText });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.insertPromptAsRichText.description')}
</p>
</div>
{/if}
<div>
<div class={settingRowClass}>
<div id="paste-large-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.pasteLargeTextAsFile.label')}
</div>
<div class={settingControlClass}>
<Switch
tooltip={true}
ariaLabelledbyId="paste-large-label"
bind:state={largeTextAsFile}
inherited={isDefaultSetting('largeTextAsFile')}
on:change={() => {
saveSettings({ largeTextAsFile });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.pasteLargeTextAsFile.description')}
</p>
</div>
<div class={sectionHeadingClass}>
{$i18n.t('settings.personal.interface.sections.artifacts.title')}
</div>
<div>
<div class={settingRowClass}>
<div id="detect-artifacts-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.detectArtifactsAutomatically.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="detect-artifacts-label"
tooltip={true}
bind:state={detectArtifacts}
inherited={isDefaultSetting('detectArtifacts')}
on:change={() => {
saveSettings({ detectArtifacts });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.detectArtifactsAutomatically.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="iframe-sandbox-allow-scripts-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.iframeSandboxAllowScripts.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="iframe-sandbox-allow-scripts-label"
tooltip={true}
bind:state={iframeSandboxAllowScripts}
inherited={isDefaultSetting('iframeSandboxAllowScripts')}
on:change={() => {
saveSettings({ iframeSandboxAllowScripts });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.iframeSandboxAllowScripts.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="iframe-sandbox-allow-same-origin-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.iframeSandboxAllowSameOrigin.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="iframe-sandbox-allow-same-origin-label"
tooltip={true}
bind:state={iframeSandboxAllowSameOrigin}
inherited={isDefaultSetting('iframeSandboxAllowSameOrigin')}
on:change={() => {
saveSettings({ iframeSandboxAllowSameOrigin });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.iframeSandboxAllowSameOrigin.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="iframe-sandbox-allow-forms-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.iframeSandboxAllowForms.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="iframe-sandbox-allow-forms-label"
tooltip={true}
bind:state={iframeSandboxAllowForms}
inherited={isDefaultSetting('iframeSandboxAllowForms')}
on:change={() => {
saveSettings({ iframeSandboxAllowForms });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.iframeSandboxAllowForms.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="iframe-sandbox-allow-downloads-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.iframeSandboxAllowDownloads.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="iframe-sandbox-allow-downloads-label"
tooltip={true}
bind:state={iframeSandboxAllowDownloads}
inherited={isDefaultSetting('iframeSandboxAllowDownloads')}
on:change={() => {
saveSettings({ iframeSandboxAllowDownloads });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.iframeSandboxAllowDownloads.description')}
</p>
</div>
<div class={sectionHeadingClass}>
{$i18n.t('settings.personal.interface.sections.voice.title')}
</div>
<div>
<div class={settingRowClass}>
<div class={settingLabelClass} id="allow-voice-interruption-in-call-label">
{$i18n.t('settings.personal.interface.allowVoiceInterruptionInCall.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="allow-voice-interruption-in-call-label"
tooltip={true}
bind:state={voiceInterruption}
inherited={isDefaultSetting('voiceInterruption')}
on:change={() => {
saveSettings({ voiceInterruption });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.allowVoiceInterruptionInCall.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="display-emoji-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.displayEmojiInCall.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="display-emoji-label"
tooltip={true}
bind:state={showEmojiInCall}
inherited={isDefaultSetting('showEmojiInCall')}
on:change={() => {
saveSettings({ showEmojiInCall });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.displayEmojiInCall.description')}
</p>
</div>
<div class={sectionHeadingClass}>
{$i18n.t('settings.personal.interface.sections.file.title')}
</div>
<div>
<div class={settingRowClass}>
<div id="default-upload-mode-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.defaultUploadMode.label')}
</div>
<button
aria-labelledby="default-upload-mode-label default-upload-mode-state"
class={actionButtonClass}
on:click={() => {
defaultUploadContext = defaultUploadContext === 'full' ? 'focused' : 'full';
saveSettings({ defaultUploadContext });
}}
type="button"
>
<span id="default-upload-mode-state">
{defaultUploadContext === 'full'
? $i18n.t('Using Entire Document')
: $i18n.t('Using Focused Retrieval')}
</span>
</button>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.defaultUploadMode.description')}
</p>
</div>
<div>
<div class={settingRowClass}>
<div id="image-compression-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.imageCompression.label')}
</div>
<div class={settingControlClass}>
{#if imageCompression}
<button
class={actionButtonClass}
type="button"
aria-label={$i18n.t(
'settings.personal.interface.openModalToManageImageCompression.label'
)}
on:click={() => {
showManageImageCompressionModal = true;
}}
>
{$i18n.t('Manage')}
</button>
{/if}
<Switch
ariaLabelledbyId="image-compression-label"
tooltip={true}
bind:state={imageCompression}
inherited={isDefaultSetting('imageCompression')}
on:change={() => {
saveSettings({ imageCompression });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('Compress uploaded images before sending or storage.')}
</p>
</div>
{#if imageCompression}
<div>
<div class={settingRowClass}>
<div id="image-compression-in-channels-label" class={settingLabelClass}>
{$i18n.t('settings.personal.interface.compressImagesInChannels.label')}
</div>
<div class={settingControlClass}>
<Switch
ariaLabelledbyId="image-compression-in-channels-label"
tooltip={true}
bind:state={imageCompressionInChannels}
inherited={isDefaultSetting('imageCompressionInChannels')}
on:change={() => {
saveSettings({ imageCompressionInChannels });
}}
/>
</div>
</div>
<p class={settingDescriptionClass}>
{$i18n.t('settings.personal.interface.compressImagesInChannels.description')}
</p>
</div>
{/if}
</div>