open-webui/src/lib/components/chat/Settings/Interface.svelte

1169 lines
31 KiB
Svelte
Raw Normal View History

2024-02-05 09:58:54 +00:00
<script lang="ts">
import { config, models, settings, user } from '$lib/stores';
2024-03-01 04:40:36 +00:00
import { createEventDispatcher, onMount, getContext } from 'svelte';
2024-03-01 09:18:07 +00:00
import { toast } from 'svelte-sonner';
import Tooltip from '$lib/components/common/Tooltip.svelte';
2024-06-16 22:32:26 +00:00
import { updateUserInfo } from '$lib/apis/users';
import { getUserPosition } from '$lib/utils';
2025-08-06 22:11:16 +00:00
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 './Interface/ManageFloatingActionButtonsModal.svelte';
2024-02-05 09:58:54 +00:00
const dispatch = createEventDispatcher();
2024-03-01 04:40:36 +00:00
const i18n = getContext('i18n');
export let saveSettings: Function;
2024-06-17 08:31:22 +00:00
let backgroundImageUrl = null;
let inputFiles = null;
let filesInputElement;
// Addons
let titleAutoGenerate = true;
2025-06-03 14:47:49 +00:00
let autoFollowUps = true;
2024-10-20 03:34:17 +00:00
let autoTags = true;
let responseAutoCopy = false;
2024-06-04 18:48:46 +00:00
let widescreenMode = false;
let splitLargeChunks = false;
let scrollOnBranchChange = true;
2024-06-16 22:32:26 +00:00
let userLocation = false;
2024-02-05 09:58:54 +00:00
// Interface
let defaultModelId = '';
let showUsername = false;
2025-04-20 06:13:19 +00:00
let notificationSound = true;
2025-04-20 06:13:19 +00:00
let notificationSoundAlways = false;
2025-05-16 13:06:21 +00:00
let highContrastMode = false;
2025-04-10 17:36:57 +00:00
let detectArtifacts = true;
let displayMultiModelResponsesInTabs = false;
2025-04-10 17:36:57 +00:00
2024-10-26 04:31:18 +00:00
let richTextInput = true;
2025-08-06 08:21:18 +00:00
let showFormattingToolbar = false;
2025-07-07 17:43:28 +00:00
let insertPromptAsRichText = false;
let promptAutocomplete = false;
let largeTextAsFile = false;
2024-06-13 04:18:53 +00:00
2025-07-18 13:49:24 +00:00
let keepFollowUpPrompts = false;
let insertFollowUpPrompt = false;
2024-10-06 18:45:13 +00:00
let landingPageMode = '';
2024-05-15 22:39:41 +00:00
let chatBubble = true;
2025-04-07 00:02:39 +00:00
let chatDirection: 'LTR' | 'RTL' | 'auto' = 'auto';
2025-03-04 04:53:14 +00:00
let ctrlEnterToSend = false;
let copyFormatted = false;
2025-07-18 12:45:56 +00:00
let chatFadeStreamingText = true;
2025-03-15 02:01:59 +00:00
let collapseCodeBlocks = false;
2025-03-15 01:56:04 +00:00
let expandDetails = false;
let showFloatingActionButtons = true;
let floatingActionButtons = null;
2024-12-25 06:28:14 +00:00
let imageCompression = false;
let imageCompressionSize = {
width: '',
height: ''
};
2025-08-06 11:11:43 +00:00
let imageCompressionInChannels = true;
2024-12-25 06:28:14 +00:00
2025-05-10 14:59:04 +00:00
// chat export
let stylizedPdfExport = true;
// Admin - Show Update Available Toast
2024-10-21 07:30:29 +00:00
let showUpdateToast = true;
let showChangelog = true;
2024-06-13 04:18:53 +00:00
let showEmojiInCall = false;
2024-06-22 20:21:36 +00:00
let voiceInterruption = false;
let hapticFeedback = false;
2024-06-13 04:18:53 +00:00
2025-02-05 08:37:47 +00:00
let webSearch = null;
2025-04-13 00:09:39 +00:00
let iframeSandboxAllowSameOrigin = false;
let iframeSandboxAllowForms = false;
2025-08-06 22:11:16 +00:00
let showManageFloatingActionButtonsModal = false;
2024-05-15 22:39:41 +00:00
2024-10-06 18:45:13 +00:00
const toggleLandingPageMode = async () => {
landingPageMode = landingPageMode === '' ? 'chat' : '';
saveSettings({ landingPageMode: landingPageMode });
};
2024-06-16 22:32:26 +00:00
const toggleUserLocation = async () => {
if (userLocation) {
const position = await getUserPosition().catch((error) => {
toast.error(error.message);
return null;
});
if (position) {
await updateUserInfo(localStorage.token, { location: position });
2024-06-24 16:09:45 +00:00
toast.success($i18n.t('User location successfully retrieved.'));
2024-06-16 22:32:26 +00:00
} else {
userLocation = false;
}
}
saveSettings({ userLocation });
};
const toggleTitleAutoGenerate = async () => {
saveSettings({
title: {
...$settings.title,
auto: titleAutoGenerate
}
});
};
const toggleResponseAutoCopy = async () => {
const permission = await navigator.clipboard
.readText()
.then(() => {
return 'granted';
})
.catch(() => {
return '';
});
if (permission === 'granted') {
saveSettings({ responseAutoCopy: responseAutoCopy });
} else {
2025-08-06 22:11:16 +00:00
responseAutoCopy = false;
toast.error(
$i18n.t(
2024-06-24 16:21:29 +00:00
'Clipboard write permission denied. Please check your browser settings to grant the necessary access.'
)
);
}
};
const toggleChangeChatDirection = async () => {
2025-04-06 23:57:36 +00:00
if (chatDirection === 'auto') {
chatDirection = 'LTR';
} else if (chatDirection === 'LTR') {
chatDirection = 'RTL';
} else if (chatDirection === 'RTL') {
chatDirection = 'auto';
}
2024-05-18 22:02:47 +00:00
saveSettings({ chatDirection });
};
2025-03-04 04:53:14 +00:00
const togglectrlEnterToSend = async () => {
ctrlEnterToSend = !ctrlEnterToSend;
saveSettings({ ctrlEnterToSend });
};
2024-02-05 09:58:54 +00:00
const updateInterfaceHandler = async () => {
saveSettings({
2024-12-25 06:28:14 +00:00
models: [defaultModelId],
imageCompressionSize: imageCompressionSize
});
2024-02-05 09:58:54 +00:00
};
2025-02-05 08:37:47 +00:00
const toggleWebSearch = async () => {
webSearch = webSearch === null ? 'always' : null;
saveSettings({ webSearch: webSearch });
};
2024-02-05 09:58:54 +00:00
onMount(async () => {
2024-05-27 05:47:42 +00:00
titleAutoGenerate = $settings?.title?.auto ?? true;
2025-06-10 09:39:41 +00:00
autoTags = $settings?.autoTags ?? true;
autoFollowUps = $settings?.autoFollowUps ?? true;
2024-06-13 04:18:53 +00:00
2025-06-10 09:39:41 +00:00
highContrastMode = $settings?.highContrastMode ?? false;
2025-05-16 13:07:52 +00:00
2025-06-10 09:39:41 +00:00
detectArtifacts = $settings?.detectArtifacts ?? true;
responseAutoCopy = $settings?.responseAutoCopy ?? false;
2024-10-21 07:30:29 +00:00
2025-06-10 09:39:41 +00:00
showUsername = $settings?.showUsername ?? false;
showUpdateToast = $settings?.showUpdateToast ?? true;
showChangelog = $settings?.showChangelog ?? true;
2024-06-13 04:18:53 +00:00
2025-06-10 09:39:41 +00:00
showEmojiInCall = $settings?.showEmojiInCall ?? false;
voiceInterruption = $settings?.voiceInterruption ?? false;
2024-06-13 04:18:53 +00:00
displayMultiModelResponsesInTabs = $settings?.displayMultiModelResponsesInTabs ?? false;
2025-07-18 12:45:56 +00:00
chatFadeStreamingText = $settings?.chatFadeStreamingText ?? true;
2025-06-10 09:39:41 +00:00
richTextInput = $settings?.richTextInput ?? true;
2025-08-06 08:21:18 +00:00
showFormattingToolbar = $settings?.showFormattingToolbar ?? false;
2025-07-07 17:43:28 +00:00
insertPromptAsRichText = $settings?.insertPromptAsRichText ?? false;
2025-06-10 09:39:41 +00:00
promptAutocomplete = $settings?.promptAutocomplete ?? false;
2025-07-07 17:43:28 +00:00
2025-07-18 13:49:24 +00:00
keepFollowUpPrompts = $settings?.keepFollowUpPrompts ?? false;
insertFollowUpPrompt = $settings?.insertFollowUpPrompt ?? false;
2025-06-10 09:39:41 +00:00
largeTextAsFile = $settings?.largeTextAsFile ?? false;
copyFormatted = $settings?.copyFormatted ?? false;
2025-06-10 09:39:41 +00:00
collapseCodeBlocks = $settings?.collapseCodeBlocks ?? false;
expandDetails = $settings?.expandDetails ?? false;
2025-03-15 02:01:59 +00:00
2025-06-10 09:39:41 +00:00
landingPageMode = $settings?.landingPageMode ?? '';
chatBubble = $settings?.chatBubble ?? true;
widescreenMode = $settings?.widescreenMode ?? false;
splitLargeChunks = $settings?.splitLargeChunks ?? false;
scrollOnBranchChange = $settings?.scrollOnBranchChange ?? true;
chatDirection = $settings?.chatDirection ?? 'auto';
userLocation = $settings?.userLocation ?? false;
2025-04-20 06:13:19 +00:00
notificationSound = $settings?.notificationSound ?? true;
notificationSoundAlways = $settings?.notificationSoundAlways ?? false;
2025-05-10 14:59:04 +00:00
iframeSandboxAllowSameOrigin = $settings?.iframeSandboxAllowSameOrigin ?? false;
iframeSandboxAllowForms = $settings?.iframeSandboxAllowForms ?? false;
stylizedPdfExport = $settings?.stylizedPdfExport ?? true;
2025-06-10 09:39:41 +00:00
hapticFeedback = $settings?.hapticFeedback ?? false;
ctrlEnterToSend = $settings?.ctrlEnterToSend ?? false;
showFloatingActionButtons = $settings?.showFloatingActionButtons ?? true;
floatingActionButtons = $settings?.floatingActionButtons ?? null;
2025-06-10 09:39:41 +00:00
imageCompression = $settings?.imageCompression ?? false;
imageCompressionSize = $settings?.imageCompressionSize ?? { width: '', height: '' };
2025-08-06 11:11:43 +00:00
imageCompressionInChannels = $settings?.imageCompressionInChannels ?? true;
2024-12-25 06:28:14 +00:00
defaultModelId = $settings?.models?.at(0) ?? '';
if ($config?.default_models) {
defaultModelId = $config.default_models.split(',')[0];
}
2024-06-17 08:31:22 +00:00
2025-06-10 09:39:41 +00:00
backgroundImageUrl = $settings?.backgroundImageUrl ?? null;
webSearch = $settings?.webSearch ?? null;
2024-02-05 09:58:54 +00:00
});
</script>
2025-08-06 22:11:16 +00:00
<ManageFloatingActionButtonsModal
bind:show={showManageFloatingActionButtonsModal}
{floatingActionButtons}
onSave={(buttons) => {
floatingActionButtons = buttons;
saveSettings({ floatingActionButtons });
}}
/>
2024-02-05 09:58:54 +00:00
<form
id="tab-interface"
2024-02-05 09:58:54 +00:00
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={() => {
updateInterfaceHandler();
dispatch('save');
}}
>
2024-06-17 08:31:22 +00:00
<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.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;
}
}}
/>
2024-11-13 06:43:18 +00:00
<div class=" space-y-3 overflow-y-scroll max-h-[28rem] lg:max-h-full">
<div>
2025-08-06 22:25:52 +00:00
<h1 class=" mb-2 text-sm font-medium">{$i18n.t('UI')}</h1>
2025-05-16 13:06:21 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="high-contrast-mode-label" class=" self-center text-xs">
2025-05-16 13:06:21 +00:00
{$i18n.t('High Contrast Mode')} ({$i18n.t('Beta')})
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="high-contrast-mode-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={highContrastMode}
on:change={() => {
saveSettings({ highContrastMode });
}}
/>
</div>
</div>
</div>
<div>
<div class="py-0.5 flex w-full justify-between">
<div id="notification-sound-label" class=" self-center text-xs">
{$i18n.t('Notification Sound')}
</div>
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="notification-sound-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={notificationSound}
on:change={() => {
saveSettings({ notificationSound });
}}
/>
</div>
</div>
</div>
{#if notificationSound}
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="play-notification-sound-label" class=" self-center text-xs">
{$i18n.t('Always Play Notification Sound')}
</div>
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="play-notification-sound-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={notificationSoundAlways}
on:change={() => {
saveSettings({ notificationSoundAlways });
}}
/>
</div>
</div>
</div>
{/if}
<div>
<div id="allow-user-location-label" class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs">{$i18n.t('Allow User Location')}</div>
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="allow-user-location-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={userLocation}
on:change={() => {
toggleUserLocation();
}}
/>
</div>
2025-05-16 13:06:21 +00:00
</div>
</div>
2024-10-06 18:45:13 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="haptic-feedback-label" class=" self-center text-xs">
{$i18n.t('Haptic Feedback')} ({$i18n.t('Android')})
2025-06-16 11:05:35 +00:00
</div>
2024-10-06 18:45:13 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="haptic-feedback-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={hapticFeedback}
on:change={() => {
saveSettings({ hapticFeedback });
}}
/>
</div>
2024-10-06 18:45:13 +00:00
</div>
</div>
2024-05-15 22:39:41 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="copy-formatted-label" class=" self-center text-xs">
{$i18n.t('Copy Formatted Text')}
2025-06-16 11:05:35 +00:00
</div>
2024-05-15 22:39:41 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="copy-formatted-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={copyFormatted}
on:change={() => {
saveSettings({ copyFormatted });
}}
/>
</div>
2024-05-15 22:39:41 +00:00
</div>
</div>
2025-08-06 22:11:16 +00:00
{#if $user?.role === 'admin'}
2024-06-17 08:31:22 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="toast-notifications-label" class=" self-center text-xs">
{$i18n.t('Toast notifications for new updates')}
2024-06-17 08:31:22 +00:00
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="toast-notifications-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={showUpdateToast}
on:change={() => {
saveSettings({ showUpdateToast });
}}
/>
</div>
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="whats-new-label" class=" self-center text-xs">
{$i18n.t(`Show "What's New" modal on login`)}
</div>
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="whats-new-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={showChangelog}
on:change={() => {
saveSettings({ showChangelog });
}}
/>
</div>
2024-06-17 08:31:22 +00:00
</div>
</div>
{/if}
2025-08-06 22:11:16 +00:00
<div class=" my-2 text-sm font-medium">{$i18n.t('Chat')}</div>
2024-06-16 22:32:26 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 18:55:47 +00:00
<div id="chat-direction-label" class=" self-center text-xs">
{$i18n.t('Chat direction')}
2025-06-16 11:05:35 +00:00
</div>
2024-06-16 22:32:26 +00:00
<button
aria-labelledby="chat-direction-label chat-direction-mode"
2025-08-06 18:55:47 +00:00
class="p-1 px-3 text-xs flex rounded-sm transition"
on:click={toggleChangeChatDirection}
type="button"
>
<span class="ml-2 self-center" id="chat-direction-mode">
{chatDirection === 'LTR'
? $i18n.t('LTR')
: chatDirection === 'RTL'
? $i18n.t('RTL')
: $i18n.t('Auto')}
</span>
2025-08-06 18:55:47 +00:00
</button>
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="landing-page-mode-label" class=" self-center text-xs">
{$i18n.t('Landing Page Mode')}
2025-08-06 18:55:47 +00:00
</div>
<button
aria-labelledby="landing-page-mode-label notification-sound-state"
2025-02-16 03:27:25 +00:00
class="p-1 px-3 text-xs flex rounded-sm transition"
2024-06-16 22:32:26 +00:00
on:click={() => {
2025-08-06 22:11:16 +00:00
toggleLandingPageMode();
2024-06-16 22:32:26 +00:00
}}
type="button"
>
<span class="ml-2 self-center" id="notification-sound-state"
>{notificationSound === true ? $i18n.t('On') : $i18n.t('Off')}</span
>
2024-06-16 22:32:26 +00:00
</button>
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="chat-background-label" class=" self-center text-xs">
{$i18n.t('Chat Background Image')}
2025-06-16 11:05:35 +00:00
</div>
2024-06-17 08:31:22 +00:00
<button
aria-labelledby="chat-background-label background-image-url-state"
2025-02-16 03:27:25 +00:00
class="p-1 px-3 text-xs flex rounded-sm transition"
2025-08-06 18:55:47 +00:00
on:click={() => {
2025-08-06 22:11:16 +00:00
if (backgroundImageUrl !== null) {
backgroundImageUrl = null;
saveSettings({ backgroundImageUrl });
} else {
filesInputElement.click();
}
2025-08-06 18:55:47 +00:00
}}
2024-06-17 08:31:22 +00:00
type="button"
>
<span class="ml-2 self-center" id="background-image-url-state"
>{backgroundImageUrl !== null ? $i18n.t('Reset') : $i18n.t('Upload')}</span
>
2024-06-17 08:31:22 +00:00
</button>
</div>
</div>
<div>
2025-08-06 22:11:16 +00:00
<div class=" py-0.5 flex w-full justify-between">
<div id="chat-bubble-ui-label" class=" self-center text-xs">
{$i18n.t('Chat Bubble UI')}
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
tooltip={true}
ariaLabelledbyId="chat-bubble-ui-label"
2025-08-06 22:11:16 +00:00
bind:state={chatBubble}
on:change={() => {
saveSettings({ chatBubble });
}}
/>
</div>
</div>
</div>
2025-08-06 22:11:16 +00:00
{#if !$settings.chatBubble}
2025-04-20 06:13:19 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="chat-bubble-username-label" class=" self-center text-xs">
{$i18n.t('Display the username instead of You in the Chat')}
2025-04-20 06:13:19 +00:00
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="chat-bubble-username-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={showUsername}
on:change={() => {
saveSettings({ showUsername });
}}
/>
</div>
2025-04-20 06:13:19 +00:00
</div>
</div>
{/if}
<div>
2025-08-06 22:11:16 +00:00
<div class=" py-0.5 flex w-full justify-between">
<div id="widescreen-mode-label" class=" self-center text-xs">
{$i18n.t('Widescreen Mode')}
</div>
2025-06-03 14:47:49 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="widescreen-mode-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={widescreenMode}
on:change={() => {
saveSettings({ widescreenMode });
}}
/>
</div>
2025-06-03 14:47:49 +00:00
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="fade-streaming-label" class=" self-center text-xs">
{$i18n.t('Fade Effect for Streaming Text')}
2025-06-16 11:05:35 +00:00
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="fade-streaming-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={chatFadeStreamingText}
on:change={() => {
saveSettings({ chatFadeStreamingText });
}}
/>
</div>
2025-04-10 17:36:57 +00:00
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="auto-generation-label" class=" self-center text-xs">
{$i18n.t('Title Auto-Generation')}
2025-04-10 17:36:57 +00:00
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="auto-generation-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={titleAutoGenerate}
on:change={() => {
toggleTitleAutoGenerate();
2025-08-06 18:55:47 +00:00
}}
2025-08-06 22:11:16 +00:00
/>
2025-07-18 12:45:56 +00:00
</div>
2025-07-18 13:49:24 +00:00
</div>
2025-08-06 22:11:16 +00:00
</div>
2025-07-18 13:49:24 +00:00
2025-08-06 22:11:16 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs" id="follow-up-auto-generation-label">
{$i18n.t('Follow-Up Auto-Generation')}
</div>
2025-07-18 13:49:24 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="follow-up-auto-generation-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={autoFollowUps}
on:change={() => {
saveSettings({ autoFollowUps });
2025-08-06 18:55:47 +00:00
}}
2025-08-06 22:11:16 +00:00
/>
2025-07-18 13:49:24 +00:00
</div>
</div>
2025-08-06 22:11:16 +00:00
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="chat-tags-label" class=" self-center text-xs">
{$i18n.t('Chat Tags Auto-Generation')}
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="chat-tags-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={autoTags}
on:change={() => {
saveSettings({ autoTags });
}}
/>
</div>
</div>
</div>
2024-10-26 04:31:18 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="auto-copy-label" class=" self-center text-xs">
{$i18n.t('Auto-Copy Response to Clipboard')}
2025-08-06 08:21:18 +00:00
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="auto-copy-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={responseAutoCopy}
on:change={() => {
toggleResponseAutoCopy();
}}
2025-08-06 22:11:16 +00:00
/>
</div>
</div>
2025-08-06 22:11:16 +00:00
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="keep-follow-up-prompts-label" class=" self-center text-xs">
2025-08-06 22:11:16 +00:00
{$i18n.t('Keep Follow-Up Prompts in Chat')}
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="keep-follow-up-prompts-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={keepFollowUpPrompts}
on:change={() => {
saveSettings({ keepFollowUpPrompts });
}}
/>
</div>
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="insert-follow-up-prompt-label" class=" self-center text-xs">
2025-08-06 22:11:16 +00:00
{$i18n.t('Insert Follow-Up Prompt to Input')}
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="insert-follow-up-prompt-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={insertFollowUpPrompt}
on:change={() => {
saveSettings({ insertFollowUpPrompt });
}}
/>
</div>
</div>
</div>
2025-03-15 02:01:59 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="always-collapse-label" class=" self-center text-xs">
{$i18n.t('Always Collapse Code Blocks')}
</div>
2025-08-06 18:55:47 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="always-collapse-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={collapseCodeBlocks}
on:change={() => {
saveSettings({ collapseCodeBlocks });
}}
/>
</div>
2025-08-06 18:55:47 +00:00
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="always-expand-label" class=" self-center text-xs">
{$i18n.t('Always Expand Details')}
2025-06-16 11:05:35 +00:00
</div>
2025-03-15 02:01:59 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="always-expand-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={expandDetails}
on:change={() => {
saveSettings({ expandDetails });
}}
/>
</div>
2025-03-15 02:01:59 +00:00
</div>
</div>
2025-03-15 01:56:04 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="keep-followup-prompts-label" class=" self-center text-xs">
{$i18n.t('Display Multi-model Responses in Tabs')}
2025-06-16 11:05:35 +00:00
</div>
2025-03-15 01:56:04 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="keep-followup-prompts-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={displayMultiModelResponsesInTabs}
on:change={() => {
saveSettings({ displayMultiModelResponsesInTabs });
}}
/>
</div>
2025-08-06 18:55:47 +00:00
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="scroll-on-branch-change-label" class=" self-center text-xs">
{$i18n.t('Scroll On Branch Change')}
2025-08-06 18:55:47 +00:00
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="scroll-on-branch-change-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={scrollOnBranchChange}
on:change={() => {
saveSettings({ scrollOnBranchChange });
}}
/>
</div>
2025-03-15 01:56:04 +00:00
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="stylized-pdf-export-label" class=" self-center text-xs">
{$i18n.t('Stylized PDF Export')}
2024-03-02 21:53:41 +00:00
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="stylized-pdf-export-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={stylizedPdfExport}
on:change={() => {
saveSettings({ stylizedPdfExport });
}}
/>
</div>
</div>
</div>
2024-02-10 00:00:39 +00:00
2024-02-15 08:34:55 +00:00
<div>
2025-08-06 18:55:47 +00:00
<div class=" py-0.5 flex w-full justify-between">
<label id="floating-action-buttons-label" class=" self-center text-xs">
2025-08-06 22:11:16 +00:00
{$i18n.t('Floating Action Buttons')}
</label>
2024-02-15 08:34:55 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-3 p-1">
{#if showFloatingActionButtons}
<button
2025-08-06 22:20:32 +00:00
class="text-xs text-gray-700 dark:text-gray-400 underline"
2025-08-06 22:11:16 +00:00
type="button"
aria-label={$i18n.t('Open Modal To Manage Floating Action Buttons')}
2025-08-06 22:11:16 +00:00
on:click={() => {
showManageFloatingActionButtonsModal = true;
}}
>
{$i18n.t('Manage')}
</button>
2024-10-20 03:34:17 +00:00
{/if}
2025-08-06 22:11:16 +00:00
<Switch
ariaLabelledbyId="floating-action-buttons-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={showFloatingActionButtons}
on:change={() => {
saveSettings({ showFloatingActionButtons });
}}
/>
2025-04-13 00:09:39 +00:00
</div>
2024-02-15 08:34:55 +00:00
</div>
</div>
2025-08-06 18:55:47 +00:00
<div>
2024-06-13 04:18:53 +00:00
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="web-search-in-chat-label" class=" self-center text-xs">
{$i18n.t('Web Search in Chat')}
2024-06-17 08:31:22 +00:00
</div>
2024-06-13 04:18:53 +00:00
<button
aria-labelledby="web-search-in-chat-label web-search-state"
2025-02-16 03:27:25 +00:00
class="p-1 px-3 text-xs flex rounded-sm transition"
2024-06-13 04:18:53 +00:00
on:click={() => {
2025-08-06 22:11:16 +00:00
toggleWebSearch();
}}
type="button"
>
<span class="ml-2 self-center" id="web-search-state"
>{webSearch === 'always' ? $i18n.t('Always') : $i18n.t('Default')}</span
>
</button>
</div>
2025-08-06 18:55:47 +00:00
</div>
2024-02-05 09:58:54 +00:00
2025-08-06 22:11:16 +00:00
<div class=" my-2 text-sm font-medium">{$i18n.t('Input')}</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="enter-key-behavior-label ctrl-enter-to-send-state" class=" self-center text-xs">
2025-08-06 22:11:16 +00:00
{$i18n.t('Enter Key Behavior')}
</div>
<button
2025-08-06 22:11:16 +00:00
aria-labelledby="enter-key-behavior-label"
class="p-1 px-3 text-xs flex rounded transition"
on:click={() => {
2025-08-06 22:11:16 +00:00
togglectrlEnterToSend();
}}
type="button"
>
<span class="ml-2 self-center" id="ctrl-enter-to-send-state"
>{ctrlEnterToSend === true
? $i18n.t('Ctrl+Enter to Send')
: $i18n.t('Enter to Send')}</span
>
</button>
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="rich-input-label" class=" self-center text-xs">
{$i18n.t('Rich Text Input for Chat')}
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
tooltip={true}
ariaLabelledbyId="rich-input-label"
2025-08-06 22:11:16 +00:00
bind:state={richTextInput}
on:change={() => {
saveSettings({ richTextInput });
}}
/>
</div>
</div>
</div>
2025-08-06 22:11:16 +00:00
{#if richTextInput}
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="show-formatting-toolbar-label" class=" self-center text-xs">
2025-08-06 22:11:16 +00:00
{$i18n.t('Show Formatting Toolbar')}
</div>
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="show-formatting-toolbar-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={showFormattingToolbar}
on:change={() => {
saveSettings({ showFormattingToolbar });
}}
/>
</div>
2025-06-16 11:05:35 +00:00
</div>
2025-08-06 22:11:16 +00:00
</div>
2025-08-06 22:11:16 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="insert-prompt-as-rich-text-label" class=" self-center text-xs">
2025-08-06 22:11:16 +00:00
{$i18n.t('Insert Prompt as Rich Text')}
</div>
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="insert-prompt-as-rich-text-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={insertPromptAsRichText}
on:change={() => {
saveSettings({ insertPromptAsRichText });
}}
/>
</div>
</div>
2025-08-06 18:55:47 +00:00
</div>
2025-08-06 22:11:16 +00:00
{#if $config?.features?.enable_autocomplete_generation}
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="prompt-autocompletion-label" class=" self-center text-xs">
{$i18n.t('Prompt Autocompletion')}
</div>
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="prompt-autocompletion-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={promptAutocomplete}
on:change={() => {
saveSettings({ promptAutocomplete });
}}
/>
</div>
</div>
</div>
{/if}
{/if}
2025-08-06 18:55:47 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="paste-large-label" class=" self-center text-xs">
{$i18n.t('Paste Large Text as File')}
2025-08-06 18:55:47 +00:00
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
tooltip={true}
ariaLabelledbyId="paste-large-label"
2025-08-06 22:11:16 +00:00
bind:state={largeTextAsFile}
on:change={() => {
saveSettings({ largeTextAsFile });
}}
/>
</div>
</div>
</div>
2025-08-06 22:11:16 +00:00
<div class=" my-2 text-sm font-medium">{$i18n.t('Artifacts')}</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-08-06 22:11:16 +00:00
<div id="detect-artifacts-label" class=" self-center text-xs">
{$i18n.t('Detect Artifacts Automatically')}
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="detect-artifacts-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={detectArtifacts}
on:change={() => {
saveSettings({ detectArtifacts });
}}
/>
</div>
</div>
</div>
2025-04-13 00:09:39 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-06-16 11:05:35 +00:00
<div id="iframe-sandbox-allow-same-origin-label" class=" self-center text-xs">
{$i18n.t('iframe Sandbox Allow Same Origin')}
</div>
2025-04-13 00:09:39 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="iframe-sandbox-allow-same-origin-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={iframeSandboxAllowSameOrigin}
on:change={() => {
saveSettings({ iframeSandboxAllowSameOrigin });
}}
/>
</div>
2025-04-13 00:09:39 +00:00
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-06-16 11:05:35 +00:00
<div id="iframe-sandbox-allow-forms-label" class=" self-center text-xs">
{$i18n.t('iframe Sandbox Allow Forms')}
</div>
2025-04-13 00:09:39 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="iframe-sandbox-allow-forms-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={iframeSandboxAllowForms}
on:change={() => {
saveSettings({ iframeSandboxAllowForms });
}}
/>
2025-05-10 14:59:04 +00:00
</div>
</div>
</div>
2025-08-06 22:11:16 +00:00
<div class=" my-2 text-sm font-medium">{$i18n.t('Voice')}</div>
2024-06-22 20:21:36 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs" id="allow-voice-interruption-in-call-label">
{$i18n.t('Allow Voice Interruption in Call')}
</div>
2024-06-22 20:21:36 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="allow-voice-interruption-in-call-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={voiceInterruption}
on:change={() => {
saveSettings({ voiceInterruption });
}}
/>
</div>
2024-06-22 20:21:36 +00:00
</div>
</div>
2024-06-17 08:31:22 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-06-16 11:05:35 +00:00
<div id="display-emoji-label" class=" self-center text-xs">
{$i18n.t('Display Emoji in Call')}
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="display-emoji-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={showEmojiInCall}
on:change={() => {
saveSettings({ showEmojiInCall });
}}
/>
</div>
</div>
</div>
2024-12-25 06:28:14 +00:00
2025-08-06 22:11:16 +00:00
<div class=" my-2 text-sm font-medium">{$i18n.t('File')}</div>
2024-12-25 06:28:14 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
2025-06-16 11:05:35 +00:00
<div id="image-compression-label" class=" self-center text-xs">
{$i18n.t('Image Compression')}
</div>
2024-12-25 06:28:14 +00:00
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="image-compression-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={imageCompression}
on:change={() => {
saveSettings({ imageCompression });
}}
/>
</div>
2024-12-25 06:28:14 +00:00
</div>
</div>
{#if imageCompression}
<div>
<div class=" py-0.5 flex w-full justify-between text-xs">
2025-06-16 11:05:35 +00:00
<div id="image-compression-size-label" class=" self-center text-xs">
{$i18n.t('Image Max Compression Size')}
</div>
2024-12-25 06:28:14 +00:00
2025-08-06 22:11:16 +00:00
<div class="p-1">
2025-06-16 11:05:35 +00:00
<label class="sr-only" for="image-comp-width"
>{$i18n.t('Image Max Compression Size width')}</label
>
2024-12-25 06:28:14 +00:00
<input
bind:value={imageCompressionSize.width}
type="number"
aria-labelledby="image-comp-width"
2025-02-16 03:27:25 +00:00
class="w-20 bg-transparent outline-hidden text-center"
2024-12-25 06:28:14 +00:00
min="0"
placeholder="Width"
/>x
2025-06-16 11:05:35 +00:00
<label class="sr-only" for="image-comp-height"
>{$i18n.t('Image Max Compression Size height')}</label
>
2024-12-25 06:28:14 +00:00
<input
bind:value={imageCompressionSize.height}
type="number"
aria-labelledby="image-comp-height"
2025-02-16 03:27:25 +00:00
class="w-20 bg-transparent outline-hidden text-center"
2024-12-25 06:28:14 +00:00
min="0"
placeholder="Height"
/>
</div>
</div>
</div>
2025-08-06 11:11:43 +00:00
<div>
<div class=" py-0.5 flex w-full justify-between">
<div id="image-compression-in-channels-label" class=" self-center text-xs">
2025-08-06 11:11:43 +00:00
{$i18n.t('Compress Images in Channels')}
</div>
2025-08-06 22:11:16 +00:00
<div class="flex items-center gap-2 p-1">
<Switch
ariaLabelledbyId="image-compression-in-channels-label"
2025-08-06 22:11:16 +00:00
tooltip={true}
bind:state={imageCompressionInChannels}
on:change={() => {
saveSettings({ imageCompressionInChannels });
}}
/>
</div>
2025-08-06 11:11:43 +00:00
</div>
</div>
2024-12-25 06:28:14 +00:00
{/if}
</div>
2024-02-05 09:58:54 +00:00
</div>
2024-03-16 09:24:32 +00:00
<div class="flex justify-end text-sm font-medium">
2024-02-05 09:58:54 +00:00
<button
2024-10-21 07:05:27 +00:00
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
2024-02-05 09:58:54 +00:00
type="submit"
>
2024-03-04 08:53:56 +00:00
{$i18n.t('Save')}
2024-02-05 09:58:54 +00:00
</button>
</div>
</form>