Unverified Commit 621719c6 authored by Timothy Jaeryang Baek's avatar Timothy Jaeryang Baek Committed by GitHub
Browse files

Merge pull request #1187 from open-webui/dev

0.1.113
parents 5ce421e7 ec9895a1
<script> <script>
import { getContext } from 'svelte';
import Modal from '../common/Modal.svelte'; import Modal from '../common/Modal.svelte';
import Database from './Settings/Database.svelte'; import Database from './Settings/Database.svelte';
import General from './Settings/General.svelte'; import General from './Settings/General.svelte';
import Users from './Settings/Users.svelte'; import Users from './Settings/Users.svelte';
const i18n = getContext('i18n');
export let show = false; export let show = false;
let selectedTab = 'general'; let selectedTab = 'general';
...@@ -13,7 +16,7 @@ ...@@ -13,7 +16,7 @@
<Modal bind:show> <Modal bind:show>
<div> <div>
<div class=" flex justify-between dark:text-gray-300 px-5 py-4"> <div class=" flex justify-between dark:text-gray-300 px-5 py-4">
<div class=" text-lg font-medium self-center">Admin Settings</div> <div class=" text-lg font-medium self-center">{$i18n.t('Admin Settings')}</div>
<button <button
class="self-center" class="self-center"
on:click={() => { on:click={() => {
...@@ -61,7 +64,7 @@ ...@@ -61,7 +64,7 @@
/> />
</svg> </svg>
</div> </div>
<div class=" self-center">General</div> <div class=" self-center">{$i18n.t('General')}</div>
</button> </button>
<button <button
...@@ -85,7 +88,7 @@ ...@@ -85,7 +88,7 @@
/> />
</svg> </svg>
</div> </div>
<div class=" self-center">Users</div> <div class=" self-center">{$i18n.t('Users')}</div>
</button> </button>
<button <button
...@@ -113,7 +116,7 @@ ...@@ -113,7 +116,7 @@
/> />
</svg> </svg>
</div> </div>
<div class=" self-center">Database</div> <div class=" self-center">{$i18n.t('Database')}</div>
</button> </button>
</div> </div>
<div class="flex-1 md:min-h-[380px]"> <div class="flex-1 md:min-h-[380px]">
......
<script lang="ts"> <script lang="ts">
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { onMount, tick } from 'svelte'; import { onMount, tick, getContext } from 'svelte';
import { settings } from '$lib/stores'; import { settings } from '$lib/stores';
import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils'; import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
import { transcribeAudio } from '$lib/apis/audio'; import { transcribeAudio } from '$lib/apis/audio';
import Tooltip from '../common/Tooltip.svelte'; import Tooltip from '../common/Tooltip.svelte';
const i18n = getContext('i18n');
export let submitPrompt: Function; export let submitPrompt: Function;
export let stopResponse: Function; export let stopResponse: Function;
...@@ -209,11 +211,11 @@ ...@@ -209,11 +211,11 @@
// Event triggered when an error occurs // Event triggered when an error occurs
speechRecognition.onerror = function (event) { speechRecognition.onerror = function (event) {
console.log(event); console.log(event);
toast.error(`Speech recognition error: ${event.error}`); toast.error($i18n.t(`Speech recognition error: {{error}}`, { error: event.error }));
isRecording = false; isRecording = false;
}; };
} else { } else {
toast.error('SpeechRecognition API is not supported in this browser.'); toast.error($i18n.t('SpeechRecognition API is not supported in this browser.'));
} }
} }
} }
...@@ -333,12 +335,15 @@ ...@@ -333,12 +335,15 @@
uploadDoc(file); uploadDoc(file);
} else { } else {
toast.error( toast.error(
`Unknown File Type '${file['type']}', but accepting and treating as plain text` $i18n.t(
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
{ file_type: file['type'] }
)
); );
uploadDoc(file); uploadDoc(file);
} }
} else { } else {
toast.error(`File not found.`); toast.error($i18n.t(`File not found.`));
} }
} }
...@@ -477,13 +482,16 @@ ...@@ -477,13 +482,16 @@
filesInputElement.value = ''; filesInputElement.value = '';
} else { } else {
toast.error( toast.error(
`Unknown File Type '${file['type']}', but accepting and treating as plain text` $i18n.t(
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
{ file_type: file['type'] }
)
); );
uploadDoc(file); uploadDoc(file);
filesInputElement.value = ''; filesInputElement.value = '';
} }
} else { } else {
toast.error(`File not found.`); toast.error($i18n.t(`File not found.`));
} }
}} }}
/> />
...@@ -570,7 +578,7 @@ ...@@ -570,7 +578,7 @@
{file.name} {file.name}
</div> </div>
<div class=" text-gray-500 text-sm">Document</div> <div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
</div> </div>
</div> </div>
{:else if file.type === 'collection'} {:else if file.type === 'collection'}
...@@ -598,7 +606,7 @@ ...@@ -598,7 +606,7 @@
{file?.title ?? `#${file.name}`} {file?.title ?? `#${file.name}`}
</div> </div>
<div class=" text-gray-500 text-sm">Collection</div> <div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
</div> </div>
</div> </div>
{/if} {/if}
...@@ -632,7 +640,7 @@ ...@@ -632,7 +640,7 @@
<div class=" flex"> <div class=" flex">
{#if fileUploadEnabled} {#if fileUploadEnabled}
<div class=" self-end mb-2 ml-1"> <div class=" self-end mb-2 ml-1">
<Tooltip content="Upload files"> <Tooltip content={$i18n.t('Upload files')}>
<button <button
class="bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5" class="bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
type="button" type="button"
...@@ -664,8 +672,8 @@ ...@@ -664,8 +672,8 @@
placeholder={chatInputPlaceholder !== '' placeholder={chatInputPlaceholder !== ''
? chatInputPlaceholder ? chatInputPlaceholder
: isRecording : isRecording
? 'Listening...' ? $i18n.t('Listening...')
: 'Send a message'} : $i18n.t('Send a Messsage')}
bind:value={prompt} bind:value={prompt}
on:keypress={(e) => { on:keypress={(e) => {
if (e.keyCode == 13 && !e.shiftKey) { if (e.keyCode == 13 && !e.shiftKey) {
...@@ -804,7 +812,7 @@ ...@@ -804,7 +812,7 @@
<div class="self-end mb-2 flex space-x-1 mr-1"> <div class="self-end mb-2 flex space-x-1 mr-1">
{#if messages.length == 0 || messages.at(-1).done == true} {#if messages.length == 0 || messages.at(-1).done == true}
<Tooltip content="Record voice"> <Tooltip content={$i18n.t('Record voice')}>
{#if speechRecognitionEnabled} {#if speechRecognitionEnabled}
<button <button
id="voice-input-button" id="voice-input-button"
...@@ -873,7 +881,7 @@ ...@@ -873,7 +881,7 @@
{/if} {/if}
</Tooltip> </Tooltip>
<Tooltip content="Send message"> <Tooltip content={$i18n.t('Send message')}>
<button <button
class="{prompt !== '' class="{prompt !== ''
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 ' ? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
...@@ -919,7 +927,7 @@ ...@@ -919,7 +927,7 @@
</form> </form>
<div class="mt-1.5 text-xs text-gray-500 text-center"> <div class="mt-1.5 text-xs text-gray-500 text-center">
LLMs can make mistakes. Verify important information. {$i18n.t('LLMs can make mistakes. Verify important information.')}
</div> </div>
</div> </div>
</div> </div>
......
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
import { documents } from '$lib/stores'; import { documents } from '$lib/stores';
import { removeFirstHashWord, isValidHttpUrl } from '$lib/utils'; import { removeFirstHashWord, isValidHttpUrl } from '$lib/utils';
import { tick } from 'svelte'; import { tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let prompt = ''; export let prompt = '';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
...@@ -117,7 +119,7 @@ ...@@ -117,7 +119,7 @@
{doc?.title ?? `#${doc.name}`} {doc?.title ?? `#${doc.name}`}
</div> </div>
<div class=" text-xs text-gray-600 line-clamp-1">Collection</div> <div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Collection')}</div>
{:else} {:else}
<div class=" font-medium text-black line-clamp-1"> <div class=" font-medium text-black line-clamp-1">
#{doc.name} ({doc.filename}) #{doc.name} ({doc.filename})
...@@ -140,7 +142,9 @@ ...@@ -140,7 +142,9 @@
confirmSelectWeb(url); confirmSelectWeb(url);
} else { } else {
toast.error( toast.error(
'Oops! Looks like the URL is invalid. Please double-check and try again.' $i18n.t(
'Oops! Looks like the URL is invalid. Please double-check and try again.'
)
); );
} }
}} }}
...@@ -149,7 +153,7 @@ ...@@ -149,7 +153,7 @@
{prompt.split(' ')?.at(0)?.substring(1)} {prompt.split(' ')?.at(0)?.substring(1)}
</div> </div>
<div class=" text-xs text-gray-600 line-clamp-1">Web</div> <div class=" text-xs text-gray-600 line-clamp-1">{$i18n.t('Web')}</div>
</button> </button>
{/if} {/if}
</div> </div>
......
...@@ -2,9 +2,11 @@ ...@@ -2,9 +2,11 @@
import { generatePrompt } from '$lib/apis/ollama'; import { generatePrompt } from '$lib/apis/ollama';
import { models } from '$lib/stores'; import { models } from '$lib/stores';
import { splitStream } from '$lib/utils'; import { splitStream } from '$lib/utils';
import { tick } from 'svelte'; import { tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let prompt = ''; export let prompt = '';
export let user = null; export let user = null;
...@@ -41,7 +43,7 @@ ...@@ -41,7 +43,7 @@
user = JSON.parse(JSON.stringify(model.name)); user = JSON.parse(JSON.stringify(model.name));
await tick(); await tick();
chatInputPlaceholder = `'${model.name}' is thinking...`; chatInputPlaceholder = $i18n.t('{{modelName}} is thinking...', { modelName: model.name });
const chatInputElement = document.getElementById('chat-textarea'); const chatInputElement = document.getElementById('chat-textarea');
...@@ -113,7 +115,9 @@ ...@@ -113,7 +115,9 @@
toast.error(error.error); toast.error(error.error);
} }
} else { } else {
toast.error(`Uh-oh! There was an issue connecting to Ollama.`); toast.error(
$i18n.t('Uh-oh! There was an issue connecting to {{provider}}.', { provider: 'llama' })
);
} }
} }
......
<script lang="ts"> <script lang="ts">
import { prompts } from '$lib/stores'; import { prompts } from '$lib/stores';
import { findWordIndices } from '$lib/utils'; import { findWordIndices } from '$lib/utils';
import { tick } from 'svelte'; import { tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let prompt = ''; export let prompt = '';
let selectedCommandIdx = 0; let selectedCommandIdx = 0;
let filteredPromptCommands = []; let filteredPromptCommands = [];
...@@ -29,7 +31,7 @@ ...@@ -29,7 +31,7 @@
if (command.content.includes('{{CLIPBOARD}}')) { if (command.content.includes('{{CLIPBOARD}}')) {
const clipboardText = await navigator.clipboard.readText().catch((err) => { const clipboardText = await navigator.clipboard.readText().catch((err) => {
toast.error('Failed to read clipboard contents'); toast.error($i18n.t('Failed to read clipboard contents'));
return '{{CLIPBOARD}}'; return '{{CLIPBOARD}}';
}); });
...@@ -113,8 +115,9 @@ ...@@ -113,8 +115,9 @@
</div> </div>
<div class="line-clamp-1"> <div class="line-clamp-1">
Tip: Update multiple variable slots consecutively by pressing the tab key in the chat {$i18n.t(
input after each replacement. 'Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.'
)}
</div> </div>
</div> </div>
</div> </div>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { chats, config, modelfiles, settings, user } from '$lib/stores'; import { chats, config, modelfiles, settings, user } from '$lib/stores';
import { tick } from 'svelte'; import { tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { getChatList, updateChatById } from '$lib/apis/chats'; import { getChatList, updateChatById } from '$lib/apis/chats';
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
import Spinner from '../common/Spinner.svelte'; import Spinner from '../common/Spinner.svelte';
import { imageGenerations } from '$lib/apis/images'; import { imageGenerations } from '$lib/apis/images';
const i18n = getContext('i18n');
export let chatId = ''; export let chatId = '';
export let sendPrompt: Function; export let sendPrompt: Function;
export let continueGeneration: Function; export let continueGeneration: Function;
...@@ -67,7 +69,7 @@ ...@@ -67,7 +69,7 @@
navigator.clipboard.writeText(text).then( navigator.clipboard.writeText(text).then(
function () { function () {
console.log('Async: Copying to clipboard was successful!'); console.log('Async: Copying to clipboard was successful!');
toast.success('Copying to clipboard was successful!'); toast.success($i18n.t('Copying to clipboard was successful!'));
}, },
function (err) { function (err) {
console.error('Async: Could not copy text: ', err); console.error('Async: Could not copy text: ', err);
......
<script lang="ts"> <script lang="ts">
import { WEBUI_BASE_URL } from '$lib/constants'; import { WEBUI_BASE_URL } from '$lib/constants';
import { user } from '$lib/stores'; import { user } from '$lib/stores';
import { onMount } from 'svelte'; import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
export let models = []; export let models = [];
export let modelfiles = []; export let modelfiles = [];
...@@ -64,9 +66,9 @@ ...@@ -64,9 +66,9 @@
</div> </div>
{/if} {/if}
{:else} {:else}
<div class=" line-clamp-1">Hello, {$user.name}</div> <div class=" line-clamp-1">{$i18n.t('Hello, {{name}}', { name: $user.name })}</div>
<div>How can I help you today?</div> <div>{$i18n.t('How can I help you today?')}</div>
{/if} {/if}
</div> </div>
</div> </div>
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { onMount, tick } from 'svelte'; import { onMount, tick, getContext } from 'svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
...@@ -316,7 +318,7 @@ ...@@ -316,7 +318,7 @@
{#if message.timestamp} {#if message.timestamp}
<span class=" invisible group-hover:visible text-gray-400 text-xs font-medium"> <span class=" invisible group-hover:visible text-gray-400 text-xs font-medium">
{dayjs(message.timestamp * 1000).format('DD/MM/YYYY HH:mm')} {dayjs(message.timestamp * 1000).format($i18n.t('DD/MM/YYYY HH:mm'))}
</span> </span>
{/if} {/if}
</Name> </Name>
...@@ -360,7 +362,7 @@ ...@@ -360,7 +362,7 @@
editMessageConfirmHandler(); editMessageConfirmHandler();
}} }}
> >
Save {$i18n.t('Save')}
</button> </button>
<button <button
...@@ -369,7 +371,7 @@ ...@@ -369,7 +371,7 @@
cancelEditMessage(); cancelEditMessage();
}} }}
> >
Cancel {$i18n.t('Cancel')}
</button> </button>
</div> </div>
</div> </div>
......
<script lang="ts"> <script lang="ts">
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { tick, createEventDispatcher } from 'svelte'; import { tick, createEventDispatcher, getContext } from 'svelte';
import Name from './Name.svelte'; import Name from './Name.svelte';
import ProfileImage from './ProfileImage.svelte'; import ProfileImage from './ProfileImage.svelte';
import { modelfiles, settings } from '$lib/stores'; import { modelfiles, settings } from '$lib/stores';
import Tooltip from '$lib/components/common/Tooltip.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
export let user; export let user;
...@@ -65,17 +67,18 @@ ...@@ -65,17 +67,18 @@
{#if $modelfiles.map((modelfile) => modelfile.tagName).includes(message.user)} {#if $modelfiles.map((modelfile) => modelfile.tagName).includes(message.user)}
{$modelfiles.find((modelfile) => modelfile.tagName === message.user)?.title} {$modelfiles.find((modelfile) => modelfile.tagName === message.user)?.title}
{:else} {:else}
You <span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span> {$i18n.t('You')}
<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
{/if} {/if}
{:else if $settings.showUsername} {:else if $settings.showUsername}
{user.name} {user.name}
{:else} {:else}
You {$i18n.t('You')}
{/if} {/if}
{#if message.timestamp} {#if message.timestamp}
<span class=" invisible group-hover:visible text-gray-400 text-xs font-medium"> <span class=" invisible group-hover:visible text-gray-400 text-xs font-medium">
{dayjs(message.timestamp * 1000).format('DD/MM/YYYY HH:mm')} {dayjs(message.timestamp * 1000).format($i18n.t('DD/MM/YYYY HH:mm'))}
</span> </span>
{/if} {/if}
</Name> </Name>
...@@ -123,7 +126,7 @@ ...@@ -123,7 +126,7 @@
{file.name} {file.name}
</div> </div>
<div class=" text-gray-500 text-sm">Document</div> <div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
</div> </div>
</button> </button>
{:else if file.type === 'collection'} {:else if file.type === 'collection'}
...@@ -152,7 +155,7 @@ ...@@ -152,7 +155,7 @@
{file?.title ?? `#${file.name}`} {file?.title ?? `#${file.name}`}
</div> </div>
<div class=" text-gray-500 text-sm">Collection</div> <div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
</div> </div>
</button> </button>
{/if} {/if}
...@@ -181,7 +184,7 @@ ...@@ -181,7 +184,7 @@
editMessageConfirmHandler(); editMessageConfirmHandler();
}} }}
> >
Save & Submit {$i18n.t('Save & Submit')}
</button> </button>
<button <button
...@@ -190,7 +193,7 @@ ...@@ -190,7 +193,7 @@
cancelEditMessage(); cancelEditMessage();
}} }}
> >
Cancel {$i18n.t('Cancel')}
</button> </button>
</div> </div>
</div> </div>
......
<script lang="ts"> <script lang="ts">
import { setDefaultModels } from '$lib/apis/configs'; import { setDefaultModels } from '$lib/apis/configs';
import { models, showSettings, settings, user } from '$lib/stores'; import { models, showSettings, settings, user } from '$lib/stores';
import { onMount, tick } from 'svelte'; import { onMount, tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let selectedModels = ['']; export let selectedModels = [''];
export let disabled = false; export let disabled = false;
const saveDefaultModel = async () => { const saveDefaultModel = async () => {
const hasEmptyModel = selectedModels.filter((it) => it === ''); const hasEmptyModel = selectedModels.filter((it) => it === '');
if (hasEmptyModel.length) { if (hasEmptyModel.length) {
toast.error('Choose a model before saving...'); toast.error($i18n.t('Choose a model before saving...'));
return; return;
} }
settings.set({ ...$settings, models: selectedModels }); settings.set({ ...$settings, models: selectedModels });
...@@ -20,7 +22,7 @@ ...@@ -20,7 +22,7 @@
console.log('setting default models globally'); console.log('setting default models globally');
await setDefaultModels(localStorage.token, selectedModels.join(',')); await setDefaultModels(localStorage.token, selectedModels.join(','));
} }
toast.success('Default model updated'); toast.success($i18n.t('Default model updated'));
}; };
$: if (selectedModels.length > 0 && $models.length > 0) { $: if (selectedModels.length > 0 && $models.length > 0) {
...@@ -39,7 +41,9 @@ ...@@ -39,7 +41,9 @@
bind:value={selectedModel} bind:value={selectedModel}
{disabled} {disabled}
> >
<option class=" text-gray-700" value="" selected disabled>Select a model</option> <option class=" text-gray-700" value="" selected disabled
>{$i18n.t('Select a model')}</option
>
{#each $models as model} {#each $models as model}
{#if model.name === 'hr'} {#if model.name === 'hr'}
...@@ -133,5 +137,5 @@ ...@@ -133,5 +137,5 @@
</div> </div>
<div class="text-left mt-1.5 text-xs text-gray-500"> <div class="text-left mt-1.5 text-xs text-gray-500">
<button on:click={saveDefaultModel}> Set as default</button> <button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
</div> </div>
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
import { WEBUI_VERSION } from '$lib/constants'; import { WEBUI_VERSION } from '$lib/constants';
import { WEBUI_NAME, config, showChangelog } from '$lib/stores'; import { WEBUI_NAME, config, showChangelog } from '$lib/stores';
import { compareVersion } from '$lib/utils'; import { compareVersion } from '$lib/utils';
import { onMount } from 'svelte'; import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
let ollamaVersion = ''; let ollamaVersion = '';
...@@ -43,7 +45,8 @@ ...@@ -43,7 +45,8 @@
<div> <div>
<div class=" mb-2.5 text-sm font-medium flex space-x-2 items-center"> <div class=" mb-2.5 text-sm font-medium flex space-x-2 items-center">
<div> <div>
{$WEBUI_NAME} Version {$WEBUI_NAME}
{$i18n.t('Version')}
</div> </div>
</div> </div>
<div class="flex w-full justify-between items-center"> <div class="flex w-full justify-between items-center">
...@@ -56,10 +59,10 @@ ...@@ -56,10 +59,10 @@
target="_blank" target="_blank"
> >
{updateAvailable === null {updateAvailable === null
? 'Checking for updates...' ? $i18n.t('Checking for updates...')
: updateAvailable : updateAvailable
? `(v${version.latest} available!)` ? `(v${version.latest} ${$i18n.t('available!')})`
: '(latest)'} : $i18n.t('(latest)')}
</a> </a>
</div> </div>
...@@ -69,7 +72,7 @@ ...@@ -69,7 +72,7 @@
showChangelog.set(true); showChangelog.set(true);
}} }}
> >
<div>See what's new</div> <div>{$i18n.t("See what's new")}</div>
</button> </button>
</div> </div>
...@@ -79,7 +82,7 @@ ...@@ -79,7 +82,7 @@
checkForVersionUpdates(); checkForVersionUpdates();
}} }}
> >
Check for updates {$i18n.t('Check for updates')}
</button> </button>
</div> </div>
</div> </div>
...@@ -88,7 +91,7 @@ ...@@ -88,7 +91,7 @@
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
<div> <div>
<div class=" mb-2.5 text-sm font-medium">Ollama Version</div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Version')}</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1 text-xs text-gray-700 dark:text-gray-200"> <div class="flex-1 text-xs text-gray-700 dark:text-gray-200">
{ollamaVersion ?? 'N/A'} {ollamaVersion ?? 'N/A'}
...@@ -123,7 +126,8 @@ ...@@ -123,7 +126,8 @@
</div> </div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> <div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Created by <a {$i18n.t('Created by')}
<a
class=" text-gray-500 dark:text-gray-300 font-medium" class=" text-gray-500 dark:text-gray-300 font-medium"
href="https://github.com/tjbck" href="https://github.com/tjbck"
target="_blank">Timothy J. Baek</a target="_blank">Timothy J. Baek</a
......
<script lang="ts"> <script lang="ts">
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { onMount } from 'svelte'; import { onMount, getContext } from 'svelte';
import { user } from '$lib/stores'; import { user } from '$lib/stores';
import { updateUserProfile } from '$lib/apis/auths'; import { updateUserProfile } from '$lib/apis/auths';
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
import { getGravatarUrl } from '$lib/apis/utils'; import { getGravatarUrl } from '$lib/apis/utils';
import { copyToClipboard } from '$lib/utils'; import { copyToClipboard } from '$lib/utils';
const i18n = getContext('i18n');
export let saveHandler: Function; export let saveHandler: Function;
let profileImageUrl = ''; let profileImageUrl = '';
...@@ -38,7 +40,7 @@ ...@@ -38,7 +40,7 @@
</script> </script>
<div class="flex flex-col h-full justify-between text-sm"> <div class="flex flex-col h-full justify-between text-sm">
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80"> <div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[22rem]">
<input <input
id="profile-image-input" id="profile-image-input"
bind:this={profileImageInputElement} bind:this={profileImageInputElement}
...@@ -101,7 +103,7 @@ ...@@ -101,7 +103,7 @@
}} }}
/> />
<div class=" mb-2.5 text-sm font-medium">Profile</div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Profile')}</div>
<div class="flex space-x-5"> <div class="flex space-x-5">
<div class="flex flex-col"> <div class="flex flex-col">
...@@ -143,13 +145,13 @@ ...@@ -143,13 +145,13 @@
const url = await getGravatarUrl($user.email); const url = await getGravatarUrl($user.email);
profileImageUrl = url; profileImageUrl = url;
}}>Use Gravatar</button }}>{$i18n.t('Use Gravatar')}</button
> >
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">
<div class=" mb-1 text-xs text-gray-500">Name</div> <div class=" mb-1 text-xs text-gray-500">{$i18n.t('Name')}</div>
<div class="flex-1"> <div class="flex-1">
<input <input
...@@ -170,7 +172,7 @@ ...@@ -170,7 +172,7 @@
<div class=" w-full justify-between"> <div class=" w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">JWT Token</div> <div class=" self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
</div> </div>
<div class="flex mt-2"> <div class="flex mt-2">
...@@ -280,7 +282,7 @@ ...@@ -280,7 +282,7 @@
} }
}} }}
> >
Save {$i18n.t('Save')}
</button> </button>
</div> </div>
</div> </div>
<script lang="ts"> <script lang="ts">
import { getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { updateUserPassword } from '$lib/apis/auths'; import { updateUserPassword } from '$lib/apis/auths';
const i18n = getContext('i18n');
let show = false; let show = false;
let currentPassword = ''; let currentPassword = '';
let newPassword = ''; let newPassword = '';
...@@ -17,7 +20,7 @@ ...@@ -17,7 +20,7 @@
); );
if (res) { if (res) {
toast.success('Successfully updated.'); toast.success($i18n.t('Successfully updated.'));
} }
currentPassword = ''; currentPassword = '';
...@@ -40,20 +43,20 @@ ...@@ -40,20 +43,20 @@
}} }}
> >
<div class="flex justify-between items-center text-sm"> <div class="flex justify-between items-center text-sm">
<div class=" font-medium">Change Password</div> <div class=" font-medium">{$i18n.t('Change Password')}</div>
<button <button
class=" text-xs font-medium text-gray-500" class=" text-xs font-medium text-gray-500"
type="button" type="button"
on:click={() => { on:click={() => {
show = !show; show = !show;
}}>{show ? 'Hide' : 'Show'}</button }}>{show ? $i18n.t('Hide') : $i18n.t('Show')}</button
> >
</div> </div>
{#if show} {#if show}
<div class=" py-2.5 space-y-1.5"> <div class=" py-2.5 space-y-1.5">
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">
<div class=" mb-1 text-xs text-gray-500">Current Password</div> <div class=" mb-1 text-xs text-gray-500">{$i18n.t('Current Password')}</div>
<div class="flex-1"> <div class="flex-1">
<input <input
...@@ -67,7 +70,7 @@ ...@@ -67,7 +70,7 @@
</div> </div>
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">
<div class=" mb-1 text-xs text-gray-500">New Password</div> <div class=" mb-1 text-xs text-gray-500">{$i18n.t('New Password')}</div>
<div class="flex-1"> <div class="flex-1">
<input <input
...@@ -81,7 +84,7 @@ ...@@ -81,7 +84,7 @@
</div> </div>
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">
<div class=" mb-1 text-xs text-gray-500">Confirm Password</div> <div class=" mb-1 text-xs text-gray-500">{$i18n.t('Confirm Password')}</div>
<div class="flex-1"> <div class="flex-1">
<input <input
...@@ -99,7 +102,7 @@ ...@@ -99,7 +102,7 @@
<button <button
class=" px-4 py-2 text-xs bg-gray-800 hover:bg-gray-900 dark:bg-gray-700 dark:hover:bg-gray-800 text-gray-100 transition rounded-md font-medium" class=" px-4 py-2 text-xs bg-gray-800 hover:bg-gray-900 dark:bg-gray-700 dark:hover:bg-gray-800 text-gray-100 transition rounded-md font-medium"
> >
Update password {$i18n.t('Update password')}
</button> </button>
</div> </div>
{/if} {/if}
......
<script lang="ts"> <script lang="ts">
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount, getContext } from 'svelte';
import AdvancedParams from './Advanced/AdvancedParams.svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import AdvancedParams from './Advanced/AdvancedParams.svelte';
export let saveSettings: Function; export let saveSettings: Function;
// Advanced // Advanced
...@@ -55,14 +57,14 @@ ...@@ -55,14 +57,14 @@
<div class="flex flex-col h-full justify-between text-sm"> <div class="flex flex-col h-full justify-between text-sm">
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80"> <div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
<div class=" text-sm font-medium">Parameters</div> <div class=" text-sm font-medium">{$i18n.t('Parameters')}</div>
<AdvancedParams bind:options /> <AdvancedParams bind:options />
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
<div class=" py-1 w-full justify-between"> <div class=" py-1 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Keep Alive</div> <div class=" self-center text-xs font-medium">{$i18n.t('Keep Alive')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -72,9 +74,9 @@ ...@@ -72,9 +74,9 @@
}} }}
> >
{#if keepAlive === null} {#if keepAlive === null}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -84,7 +86,7 @@ ...@@ -84,7 +86,7 @@
<input <input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600" class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="text" type="text"
placeholder={`e.g.) "30s","10m". Valid time units are "s", "m", "h".`} placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
bind:value={keepAlive} bind:value={keepAlive}
/> />
</div> </div>
...@@ -93,7 +95,7 @@ ...@@ -93,7 +95,7 @@
<div> <div>
<div class=" py-1 flex w-full justify-between"> <div class=" py-1 flex w-full justify-between">
<div class=" self-center text-sm font-medium">Request Mode</div> <div class=" self-center text-sm font-medium">{$i18n.t('Request Mode')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -102,7 +104,7 @@ ...@@ -102,7 +104,7 @@
}} }}
> >
{#if requestFormat === ''} {#if requestFormat === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center"> {$i18n.t('Default')} </span>
{:else if requestFormat === 'json'} {:else if requestFormat === 'json'}
<!-- <svg <!-- <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
...@@ -114,7 +116,7 @@ ...@@ -114,7 +116,7 @@
d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z" d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
/> />
</svg> --> </svg> -->
<span class="ml-2 self-center"> JSON </span> <span class="ml-2 self-center">{$i18n.t('JSON')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -147,7 +149,7 @@ ...@@ -147,7 +149,7 @@
dispatch('save'); dispatch('save');
}} }}
> >
Save {$i18n.t('Save')}
</button> </button>
</div> </div>
</div> </div>
<script lang="ts"> <script lang="ts">
import { getContext } from 'svelte';
const i18n = getContext('i18n');
export let options = { export let options = {
// Advanced // Advanced
seed: 0, seed: 0,
...@@ -20,7 +24,7 @@ ...@@ -20,7 +24,7 @@
<div class=" space-y-3 text-xs"> <div class=" space-y-3 text-xs">
<div> <div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" w-20 text-xs font-medium self-center">Seed</div> <div class=" w-20 text-xs font-medium self-center">{$i18n.t('Seed')}</div>
<div class=" flex-1 self-center"> <div class=" flex-1 self-center">
<input <input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600" class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
...@@ -36,12 +40,12 @@ ...@@ -36,12 +40,12 @@
<div> <div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" w-20 text-xs font-medium self-center">Stop Sequence</div> <div class=" w-20 text-xs font-medium self-center">{$i18n.t('Stop Sequence')}</div>
<div class=" flex-1 self-center"> <div class=" flex-1 self-center">
<input <input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600" class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="text" type="text"
placeholder="Enter Stop Sequence" placeholder={$i18n.t('Enter stop sequence')}
bind:value={options.stop} bind:value={options.stop}
autocomplete="off" autocomplete="off"
/> />
...@@ -51,7 +55,7 @@ ...@@ -51,7 +55,7 @@
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Temperature</div> <div class=" self-center text-xs font-medium">{$i18n.t('Temperature')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -61,9 +65,9 @@ ...@@ -61,9 +65,9 @@
}} }}
> >
{#if options.temperature === ''} {#if options.temperature === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center"> {$i18n.t('Default')} </span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -97,7 +101,7 @@ ...@@ -97,7 +101,7 @@
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Mirostat</div> <div class=" self-center text-xs font-medium">{$i18n.t('Mirostat')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -107,9 +111,9 @@ ...@@ -107,9 +111,9 @@
}} }}
> >
{#if options.mirostat === ''} {#if options.mirostat === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -143,7 +147,7 @@ ...@@ -143,7 +147,7 @@
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Mirostat Eta</div> <div class=" self-center text-xs font-medium">{$i18n.t('Mirostat Eta')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -153,9 +157,9 @@ ...@@ -153,9 +157,9 @@
}} }}
> >
{#if options.mirostat_eta === ''} {#if options.mirostat_eta === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -189,7 +193,7 @@ ...@@ -189,7 +193,7 @@
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Mirostat Tau</div> <div class=" self-center text-xs font-medium">{$i18n.t('Mirostat Tau')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -199,9 +203,9 @@ ...@@ -199,9 +203,9 @@
}} }}
> >
{#if options.mirostat_tau === ''} {#if options.mirostat_tau === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -235,7 +239,7 @@ ...@@ -235,7 +239,7 @@
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Top K</div> <div class=" self-center text-xs font-medium">{$i18n.t('Top K')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -245,9 +249,9 @@ ...@@ -245,9 +249,9 @@
}} }}
> >
{#if options.top_k === ''} {#if options.top_k === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -281,7 +285,7 @@ ...@@ -281,7 +285,7 @@
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Top P</div> <div class=" self-center text-xs font-medium">{$i18n.t('Top P')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -291,9 +295,9 @@ ...@@ -291,9 +295,9 @@
}} }}
> >
{#if options.top_p === ''} {#if options.top_p === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -327,7 +331,7 @@ ...@@ -327,7 +331,7 @@
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Repeat Penalty</div> <div class=" self-center text-xs font-medium">{$i18n.t('Repeat Penalty')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -337,9 +341,9 @@ ...@@ -337,9 +341,9 @@
}} }}
> >
{#if options.repeat_penalty === ''} {#if options.repeat_penalty === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -373,7 +377,7 @@ ...@@ -373,7 +377,7 @@
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Repeat Last N</div> <div class=" self-center text-xs font-medium">{$i18n.t('Repeat Last N')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -383,9 +387,9 @@ ...@@ -383,9 +387,9 @@
}} }}
> >
{#if options.repeat_last_n === ''} {#if options.repeat_last_n === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -419,7 +423,7 @@ ...@@ -419,7 +423,7 @@
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Tfs Z</div> <div class=" self-center text-xs font-medium">{$i18n.t('Tfs Z')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -429,9 +433,9 @@ ...@@ -429,9 +433,9 @@
}} }}
> >
{#if options.tfs_z === ''} {#if options.tfs_z === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -465,7 +469,7 @@ ...@@ -465,7 +469,7 @@
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Context Length</div> <div class=" self-center text-xs font-medium">{$i18n.t('Context Length')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -475,9 +479,9 @@ ...@@ -475,9 +479,9 @@
}} }}
> >
{#if options.num_ctx === ''} {#if options.num_ctx === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -510,7 +514,7 @@ ...@@ -510,7 +514,7 @@
</div> </div>
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Max Tokens</div> <div class=" self-center text-xs font-medium">{$i18n.t('Max Tokens')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -520,9 +524,9 @@ ...@@ -520,9 +524,9 @@
}} }}
> >
{#if options.num_predict === ''} {#if options.num_predict === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center">{$i18n.t('Default')}</span>
{/if} {/if}
</button> </button>
</div> </div>
......
<script lang="ts"> <script lang="ts">
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
export let saveSettings: Function; export let saveSettings: Function;
// Audio // Audio
...@@ -101,32 +103,36 @@ ...@@ -101,32 +103,36 @@
> >
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80"> <div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
<div> <div>
<div class=" mb-1 text-sm font-medium">STT Settings</div> <div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Speech-to-Text Engine</div> <div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<select <select
class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right" class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind:value={STTEngine} bind:value={STTEngine}
placeholder="Select a mode" placeholder="Select a mode"
on:change={(e) => { on:change={(e) => {
if (e.target.value !== '') { if (e.target.value !== '') {
navigator.mediaDevices.getUserMedia({ audio: true }).catch(function (err) { navigator.mediaDevices.getUserMedia({ audio: true }).catch(function (err) {
toast.error(`Permission denied when accessing microphone: ${err}`); toast.error(
$i18n.t(`Permission denied when accessing microphone: {{error}}`, {
error: err
})
);
STTEngine = ''; STTEngine = '';
}); });
} }
}} }}
> >
<option value="">Default (Web API)</option> <option value="">{$i18n.t('Default (Web API)')}</option>
<option value="whisper-local">Whisper (Local)</option> <option value="whisper-local">{$i18n.t('Whisper (Local)')}</option>
</select> </select>
</div> </div>
</div> </div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Conversation Mode</div> <div class=" self-center text-xs font-medium">{$i18n.t('Conversation Mode')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -136,15 +142,17 @@ ...@@ -136,15 +142,17 @@
type="button" type="button"
> >
{#if conversationMode === true} {#if conversationMode === true}
<span class="ml-2 self-center">On</span> <span class="ml-2 self-center">{$i18n.t('On')}</span>
{:else} {:else}
<span class="ml-2 self-center">Off</span> <span class="ml-2 self-center">{$i18n.t('Off')}</span>
{/if} {/if}
</button> </button>
</div> </div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Auto-send input after 3 sec.</div> <div class=" self-center text-xs font-medium">
{$i18n.t('Auto-send input after 3 sec.')}
</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -154,22 +162,22 @@ ...@@ -154,22 +162,22 @@
type="button" type="button"
> >
{#if speechAutoSend === true} {#if speechAutoSend === true}
<span class="ml-2 self-center">On</span> <span class="ml-2 self-center">{$i18n.t('On')}</span>
{:else} {:else}
<span class="ml-2 self-center">Off</span> <span class="ml-2 self-center">{$i18n.t('Off')}</span>
{/if} {/if}
</button> </button>
</div> </div>
</div> </div>
<div> <div>
<div class=" mb-1 text-sm font-medium">TTS Settings</div> <div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Text-to-Speech Engine</div> <div class=" self-center text-xs font-medium">{$i18n.t('Text-to-Speech Engine')}</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<select <select
class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right" class=" dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind:value={TTSEngine} bind:value={TTSEngine}
placeholder="Select a mode" placeholder="Select a mode"
on:change={(e) => { on:change={(e) => {
...@@ -182,14 +190,14 @@ ...@@ -182,14 +190,14 @@
} }
}} }}
> >
<option value="">Default (Web API)</option> <option value="">{$i18n.t('Default (Web API)')}</option>
<option value="openai">Open AI</option> <option value="openai">{$i18n.t('Open AI')}</option>
</select> </select>
</div> </div>
</div> </div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Auto-playback response</div> <div class=" self-center text-xs font-medium">{$i18n.t('Auto-playback response')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -199,9 +207,9 @@ ...@@ -199,9 +207,9 @@
type="button" type="button"
> >
{#if responseAutoPlayback === true} {#if responseAutoPlayback === true}
<span class="ml-2 self-center">On</span> <span class="ml-2 self-center">{$i18n.t('On')}</span>
{:else} {:else}
<span class="ml-2 self-center">Off</span> <span class="ml-2 self-center">{$i18n.t('Off')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -211,7 +219,7 @@ ...@@ -211,7 +219,7 @@
{#if TTSEngine === ''} {#if TTSEngine === ''}
<div> <div>
<div class=" mb-2.5 text-sm font-medium">Set Voice</div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1"> <div class="flex-1">
<select <select
...@@ -219,7 +227,7 @@ ...@@ -219,7 +227,7 @@
bind:value={speaker} bind:value={speaker}
placeholder="Select a voice" placeholder="Select a voice"
> >
<option value="" selected>Default</option> <option value="" selected>{$i18n.t('Default')}</option>
{#each voices.filter((v) => v.localService === true) as voice} {#each voices.filter((v) => v.localService === true) as voice}
<option value={voice.name} class="bg-gray-100 dark:bg-gray-700">{voice.name}</option <option value={voice.name} class="bg-gray-100 dark:bg-gray-700">{voice.name}</option
> >
...@@ -230,7 +238,7 @@ ...@@ -230,7 +238,7 @@
</div> </div>
{:else if TTSEngine === 'openai'} {:else if TTSEngine === 'openai'}
<div> <div>
<div class=" mb-2.5 text-sm font-medium">Set Voice</div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1"> <div class="flex-1">
<select <select
...@@ -254,7 +262,7 @@ ...@@ -254,7 +262,7 @@
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit" type="submit"
> >
Save {$i18n.t('Save')}
</button> </button>
</div> </div>
</form> </form>
...@@ -13,10 +13,12 @@ ...@@ -13,10 +13,12 @@
getChatList getChatList
} from '$lib/apis/chats'; } from '$lib/apis/chats';
import { getImportOrigin, convertOpenAIChats } from '$lib/utils'; import { getImportOrigin, convertOpenAIChats } from '$lib/utils';
import { onMount } from 'svelte'; import { onMount, getContext } from 'svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let saveSettings: Function; export let saveSettings: Function;
// Chats // Chats
let saveChatHistory = true; let saveChatHistory = true;
...@@ -99,13 +101,13 @@ ...@@ -99,13 +101,13 @@
}); });
</script> </script>
<div class="flex flex-col h-full justify-between space-y-3 text-sm"> <div class="flex flex-col h-full justify-between space-y-3 text-sm max-h-[22rem]">
<div class=" space-y-2"> <div class=" space-y-2">
<div <div
class="flex flex-col justify-between rounded-md items-center py-2 px-3.5 w-full transition" class="flex flex-col justify-between rounded-md items-center py-2 px-3.5 w-full transition"
> >
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-sm font-medium">Chat History</div> <div class=" self-center text-sm font-medium">{$i18n.t('Chat History')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -129,7 +131,7 @@ ...@@ -129,7 +131,7 @@
/> />
</svg> </svg>
<span class="ml-2 self-center"> On </span> <span class="ml-2 self-center"> {$i18n.t('On')} </span>
{:else} {:else}
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
...@@ -147,13 +149,13 @@ ...@@ -147,13 +149,13 @@
/> />
</svg> </svg>
<span class="ml-2 self-center">Off</span> <span class="ml-2 self-center">{$i18n.t('Off')}</span>
{/if} {/if}
</button> </button>
</div> </div>
<div class="text-xs text-left w-full font-medium mt-0.5"> <div class="text-xs text-left w-full font-medium mt-0.5">
This setting does not sync across browsers or devices. {$i18n.t('This setting does not sync across browsers or devices.')}
</div> </div>
</div> </div>
...@@ -188,7 +190,7 @@ ...@@ -188,7 +190,7 @@
/> />
</svg> </svg>
</div> </div>
<div class=" self-center text-sm font-medium">Import Chats</div> <div class=" self-center text-sm font-medium">{$i18n.t('Import Chats')}</div>
</button> </button>
<button <button
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition" class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
...@@ -210,7 +212,7 @@ ...@@ -210,7 +212,7 @@
/> />
</svg> </svg>
</div> </div>
<div class=" self-center text-sm font-medium">Export Chats</div> <div class=" self-center text-sm font-medium">{$i18n.t('Export Chats')}</div>
</button> </button>
</div> </div>
...@@ -232,7 +234,7 @@ ...@@ -232,7 +234,7 @@
clip-rule="evenodd" clip-rule="evenodd"
/> />
</svg> </svg>
<span>Are you sure?</span> <span>{$i18n.t('Are you sure?')}</span>
</div> </div>
<div class="flex space-x-1.5 items-center"> <div class="flex space-x-1.5 items-center">
...@@ -296,7 +298,7 @@ ...@@ -296,7 +298,7 @@
/> />
</svg> </svg>
</div> </div>
<div class=" self-center text-sm font-medium">Delete Chats</div> <div class=" self-center text-sm font-medium">{$i18n.t('Delete Chats')}</div>
</button> </button>
{/if} {/if}
...@@ -324,7 +326,9 @@ ...@@ -324,7 +326,9 @@
/> />
</svg> </svg>
</div> </div>
<div class=" self-center text-sm font-medium">Export All Chats (All Users)</div> <div class=" self-center text-sm font-medium">
{$i18n.t('Export All Chats (All Users)')}
</div>
</button> </button>
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
...@@ -338,7 +342,7 @@ ...@@ -338,7 +342,7 @@
}); });
if (res) { if (res) {
toast.success('Success'); toast.success($i18n.t('Success'));
} }
}} }}
> >
...@@ -356,7 +360,7 @@ ...@@ -356,7 +360,7 @@
/> />
</svg> </svg>
</div> </div>
<div class=" self-center text-sm font-medium">Reset Vector Storage</div> <div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
</button> </button>
{/if} {/if}
</div> </div>
......
<script lang="ts"> <script lang="ts">
import { models, user } from '$lib/stores'; import { models, user } from '$lib/stores';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount, getContext } from 'svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama'; import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
} from '$lib/apis/openai'; } from '$lib/apis/openai';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let getModels: Function; export let getModels: Function;
// External // External
...@@ -42,7 +44,7 @@ ...@@ -42,7 +44,7 @@
}); });
if (ollamaVersion) { if (ollamaVersion) {
toast.success('Server connection verified'); toast.success($i18n.t('Server connection verified'));
await models.set(await getModels()); await models.set(await getModels());
} }
}; };
...@@ -63,17 +65,17 @@ ...@@ -63,17 +65,17 @@
dispatch('save'); dispatch('save');
}} }}
> >
<div class=" pr-1.5 overflow-y-scroll max-h-[20.5rem] space-y-3"> <div class=" pr-1.5 overflow-y-scroll max-h-[22rem] space-y-3">
<div class=" space-y-3"> <div class=" space-y-3">
<div class="mt-2 space-y-2 pr-1.5"> <div class="mt-2 space-y-2 pr-1.5">
<div class="flex justify-between items-center text-sm"> <div class="flex justify-between items-center text-sm">
<div class=" font-medium">OpenAI API</div> <div class=" font-medium">{$i18n.t('OpenAI API')}</div>
<button <button
class=" text-xs font-medium text-gray-500" class=" text-xs font-medium text-gray-500"
type="button" type="button"
on:click={() => { on:click={() => {
showOpenAI = !showOpenAI; showOpenAI = !showOpenAI;
}}>{showOpenAI ? 'Hide' : 'Show'}</button }}>{showOpenAI ? $i18n.t('Hide') : $i18n.t('Show')}</button
> >
</div> </div>
...@@ -84,7 +86,7 @@ ...@@ -84,7 +86,7 @@
<div class="flex-1"> <div class="flex-1">
<input <input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="API Base URL" placeholder={$i18n.t('API Base URL')}
bind:value={url} bind:value={url}
autocomplete="off" autocomplete="off"
/> />
...@@ -93,7 +95,7 @@ ...@@ -93,7 +95,7 @@
<div class="flex-1"> <div class="flex-1">
<input <input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="API Key" placeholder={$i18n.t('API Key')}
bind:value={OPENAI_API_KEYS[idx]} bind:value={OPENAI_API_KEYS[idx]}
autocomplete="off" autocomplete="off"
/> />
...@@ -143,7 +145,8 @@ ...@@ -143,7 +145,8 @@
</div> </div>
</div> </div>
<div class=" mb-1 text-xs text-gray-400 dark:text-gray-500"> <div class=" mb-1 text-xs text-gray-400 dark:text-gray-500">
WebUI will make requests to <span class=" text-gray-200">'{url}/models'</span> {$i18n.t('WebUI will make requests to')}
<span class=" text-gray-200">'{url}/models'</span>
</div> </div>
{/each} {/each}
</div> </div>
...@@ -154,7 +157,7 @@ ...@@ -154,7 +157,7 @@
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
<div> <div>
<div class=" mb-2.5 text-sm font-medium">Ollama Base URL</div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Ollama Base URL')}</div>
<div class="flex w-full gap-1.5"> <div class="flex w-full gap-1.5">
<div class="flex-1 flex flex-col gap-2"> <div class="flex-1 flex flex-col gap-2">
{#each OLLAMA_BASE_URLS as url, idx} {#each OLLAMA_BASE_URLS as url, idx}
...@@ -233,13 +236,13 @@ ...@@ -233,13 +236,13 @@
</div> </div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> <div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Trouble accessing Ollama? {$i18n.t('Trouble accessing Ollama?')}
<a <a
class=" text-gray-300 font-medium" class=" text-gray-300 font-medium underline"
href="https://github.com/open-webui/open-webui#troubleshooting" href="https://github.com/open-webui/open-webui#troubleshooting"
target="_blank" target="_blank"
> >
Click here for help. {$i18n.t('Click here for help.')}
</a> </a>
</div> </div>
</div> </div>
...@@ -250,7 +253,7 @@ ...@@ -250,7 +253,7 @@
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit" type="submit"
> >
Save {$i18n.t('Save')}
</button> </button>
</div> </div>
</form> </form>
<script lang="ts"> <script lang="ts">
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount, getContext } from 'svelte';
import { getLanguages } from '$lib/i18n';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import { models, user } from '$lib/stores'; import { models, user, theme } from '$lib/stores';
const i18n = getContext('i18n');
import AdvancedParams from './Advanced/AdvancedParams.svelte'; import AdvancedParams from './Advanced/AdvancedParams.svelte';
...@@ -12,7 +15,10 @@ ...@@ -12,7 +15,10 @@
// General // General
let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light']; let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light'];
let theme = 'dark'; let selectedTheme = 'system';
let languages = [];
let lang = $i18n.language;
let notificationEnabled = false; let notificationEnabled = false;
let system = ''; let system = '';
...@@ -63,9 +69,11 @@ ...@@ -63,9 +69,11 @@
}; };
onMount(async () => { onMount(async () => {
selectedTheme = localStorage.theme ?? 'system';
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}'); let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
languages = await getLanguages();
theme = localStorage.theme ?? 'dark';
notificationEnabled = settings.notificationEnabled ?? false; notificationEnabled = settings.notificationEnabled ?? false;
system = settings.system ?? ''; system = settings.system ?? '';
...@@ -81,77 +89,93 @@ ...@@ -81,77 +89,93 @@
options = { ...options, ...settings.options }; options = { ...options, ...settings.options };
options.stop = (settings?.options?.stop ?? []).join(','); options.stop = (settings?.options?.stop ?? []).join(',');
}); });
const applyTheme = (_theme: string) => {
let themeToApply = _theme;
if (_theme === 'system') {
themeToApply = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
themes
.filter((e) => e !== themeToApply)
.forEach((e) => {
e.split(' ').forEach((e) => {
document.documentElement.classList.remove(e);
});
});
themeToApply.split(' ').forEach((e) => {
document.documentElement.classList.add(e);
});
console.log(_theme);
};
const themeChangeHandler = (_theme: string) => {
theme.set(_theme);
localStorage.setItem('theme', _theme);
applyTheme(_theme);
};
</script> </script>
<div class="flex flex-col h-full justify-between text-sm"> <div class="flex flex-col h-full justify-between text-sm">
<div class=" pr-1.5 overflow-y-scroll max-h-[20.5rem]"> <div class=" pr-1.5 overflow-y-scroll max-h-[22rem]">
<div class=""> <div class="">
<div class=" mb-1 text-sm font-medium">WebUI Settings</div> <div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Settings')}</div>
<div class=" py-0.5 flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Theme</div> <div class=" self-center text-xs font-medium">{$i18n.t('Theme')}</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<div class=" absolute right-16">
{#if theme === 'dark'}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M7.455 2.004a.75.75 0 01.26.77 7 7 0 009.958 7.967.75.75 0 011.067.853A8.5 8.5 0 116.647 1.921a.75.75 0 01.808.083z"
clip-rule="evenodd"
/>
</svg>
{:else if theme === 'light'}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4 self-center"
>
<path
d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
/>
</svg>
{/if}
</div>
<select <select
class="w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right" class=" dark:bg-gray-900 w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
bind:value={theme} bind:value={selectedTheme}
placeholder="Select a theme" placeholder="Select a theme"
on:change={(e) => { on:change={() => themeChangeHandler(selectedTheme)}
localStorage.theme = theme; >
<option value="system">⚙️ {$i18n.t('System')}</option>
themes <option value="dark">🌑 {$i18n.t('Dark')}</option>
.filter((e) => e !== theme) <option value="light">☀️ {$i18n.t('Light')}</option>
.forEach((e) => { <option value="rose-pine dark">🪻 {$i18n.t('Rosé Pine')}</option>
e.split(' ').forEach((e) => { <option value="rose-pine-dawn light">🌷 {$i18n.t('Rosé Pine Dawn')}</option>
document.documentElement.classList.remove(e); </select>
}); </div>
}); </div>
theme.split(' ').forEach((e) => {
document.documentElement.classList.add(e);
});
console.log(theme); <div class=" flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Language')}</div>
<div class="flex items-center relative">
<select
class=" dark:bg-gray-900 w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
bind:value={lang}
placeholder="Select a language"
on:change={(e) => {
$i18n.changeLanguage(lang);
}} }}
> >
<option value="dark">Dark</option> {#each languages as language}
<option value="light">Light</option> <option value={language['code']}>{language['title']}</option>
<option value="rose-pine dark">Rosé Pine</option> {/each}
<option value="rose-pine-dawn light">Rosé Pine Dawn</option>
</select> </select>
</div> </div>
</div> </div>
{#if $i18n.language === 'en-US'}
<div class="mb-2 text-xs text-gray-400 dark:text-gray-500">
Couldn't find your language?
<a
class=" text-gray-300 font-medium underline"
href="https://github.com/open-webui/open-webui/blob/main/docs/CONTRIBUTING.md#-translations-and-internationalization"
target="_blank"
>
Help us translate Open WebUI!
</a>
</div>
{/if}
<div> <div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Notification</div> <div class=" self-center text-xs font-medium">{$i18n.t('Desktop Notifications')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -161,9 +185,9 @@ ...@@ -161,9 +185,9 @@
type="button" type="button"
> >
{#if notificationEnabled === true} {#if notificationEnabled === true}
<span class="ml-2 self-center">On</span> <span class="ml-2 self-center">{$i18n.t('On')}</span>
{:else} {:else}
<span class="ml-2 self-center">Off</span> <span class="ml-2 self-center">{$i18n.t('Off')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -173,7 +197,7 @@ ...@@ -173,7 +197,7 @@
<hr class=" dark:border-gray-700 my-3" /> <hr class=" dark:border-gray-700 my-3" />
<div> <div>
<div class=" my-2.5 text-sm font-medium">System Prompt</div> <div class=" my-2.5 text-sm font-medium">{$i18n.t('System Prompt')}</div>
<textarea <textarea
bind:value={system} bind:value={system}
class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none" class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
...@@ -183,13 +207,13 @@ ...@@ -183,13 +207,13 @@
<div class="mt-2 space-y-3 pr-1.5"> <div class="mt-2 space-y-3 pr-1.5">
<div class="flex justify-between items-center text-sm"> <div class="flex justify-between items-center text-sm">
<div class=" font-medium">Advanced Parameters</div> <div class=" font-medium">{$i18n.t('Advanced Parameters')}</div>
<button <button
class=" text-xs font-medium text-gray-500" class=" text-xs font-medium text-gray-500"
type="button" type="button"
on:click={() => { on:click={() => {
showAdvanced = !showAdvanced; showAdvanced = !showAdvanced;
}}>{showAdvanced ? 'Hide' : 'Show'}</button }}>{showAdvanced ? $i18n.t('Hide') : $i18n.t('Show')}</button
> >
</div> </div>
...@@ -199,7 +223,7 @@ ...@@ -199,7 +223,7 @@
<div class=" py-1 w-full justify-between"> <div class=" py-1 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">Keep Alive</div> <div class=" self-center text-xs font-medium">{$i18n.t('Keep Alive')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -209,9 +233,9 @@ ...@@ -209,9 +233,9 @@
}} }}
> >
{#if keepAlive === null} {#if keepAlive === null}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center"> {$i18n.t('Default')} </span>
{:else} {:else}
<span class="ml-2 self-center"> Custom </span> <span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -221,7 +245,7 @@ ...@@ -221,7 +245,7 @@
<input <input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600" class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="text" type="text"
placeholder={`e.g.) "30s","10m". Valid time units are "s", "m", "h".`} placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
bind:value={keepAlive} bind:value={keepAlive}
/> />
</div> </div>
...@@ -230,7 +254,7 @@ ...@@ -230,7 +254,7 @@
<div> <div>
<div class=" py-1 flex w-full justify-between"> <div class=" py-1 flex w-full justify-between">
<div class=" self-center text-sm font-medium">Request Mode</div> <div class=" self-center text-sm font-medium">{$i18n.t('Request Mode')}</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
...@@ -239,7 +263,7 @@ ...@@ -239,7 +263,7 @@
}} }}
> >
{#if requestFormat === ''} {#if requestFormat === ''}
<span class="ml-2 self-center"> Default </span> <span class="ml-2 self-center"> {$i18n.t('Default')} </span>
{:else if requestFormat === 'json'} {:else if requestFormat === 'json'}
<!-- <svg <!-- <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
...@@ -251,7 +275,7 @@ ...@@ -251,7 +275,7 @@
d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z" d="M10 2a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 2zM10 15a.75.75 0 01.75.75v1.5a.75.75 0 01-1.5 0v-1.5A.75.75 0 0110 15zM10 7a3 3 0 100 6 3 3 0 000-6zM15.657 5.404a.75.75 0 10-1.06-1.06l-1.061 1.06a.75.75 0 001.06 1.06l1.06-1.06zM6.464 14.596a.75.75 0 10-1.06-1.06l-1.06 1.06a.75.75 0 001.06 1.06l1.06-1.06zM18 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 0118 10zM5 10a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5A.75.75 0 015 10zM14.596 15.657a.75.75 0 001.06-1.06l-1.06-1.061a.75.75 0 10-1.06 1.06l1.06 1.06zM5.404 6.464a.75.75 0 001.06-1.06l-1.06-1.06a.75.75 0 10-1.061 1.06l1.06 1.06z"
/> />
</svg> --> </svg> -->
<span class="ml-2 self-center"> JSON </span> <span class="ml-2 self-center"> {$i18n.t('JSON')} </span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -286,7 +310,7 @@ ...@@ -286,7 +310,7 @@
dispatch('save'); dispatch('save');
}} }}
> >
Save {$i18n.t('Save')}
</button> </button>
</div> </div>
</div> </div>
<script lang="ts"> <script lang="ts">
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount, getContext } from 'svelte';
import { config, user } from '$lib/stores'; import { config, user } from '$lib/stores';
import { import {
getAUTOMATIC1111Url, getAUTOMATIC1111Url,
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
import { getBackendConfig } from '$lib/apis'; import { getBackendConfig } from '$lib/apis';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
export let saveSettings: Function; export let saveSettings: Function;
let loading = false; let loading = false;
...@@ -61,7 +63,7 @@ ...@@ -61,7 +63,7 @@
await getModels(); await getModels();
if (models) { if (models) {
toast.success('Server connection verified'); toast.success($i18n.t('Server connection verified'));
} }
} else { } else {
AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token); AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
...@@ -136,39 +138,41 @@ ...@@ -136,39 +138,41 @@
loading = false; loading = false;
}} }}
> >
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[20.5rem]"> <div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[24rem]">
<div> <div>
<div class=" mb-1 text-sm font-medium">Image Settings</div> <div class=" mb-1 text-sm font-medium">{$i18n.t('Image Settings')}</div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Image Generation Engine</div> <div class=" self-center text-xs font-medium">{$i18n.t('Image Generation Engine')}</div>
<div class="flex items-center relative"> <div class="flex items-center relative">
<select <select
class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right" class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind:value={imageGenerationEngine} bind:value={imageGenerationEngine}
placeholder="Select a mode" placeholder={$i18n.t('Select a mode')}
on:change={async () => { on:change={async () => {
await updateImageGeneration(); await updateImageGeneration();
}} }}
> >
<option value="">Default (Automatic1111)</option> <option value="">{$i18n.t('Default (Automatic1111)')}</option>
<option value="openai">Open AI (Dall-E)</option> <option value="openai">{$i18n.t('Open AI (Dall-E)')}</option>
</select> </select>
</div> </div>
</div> </div>
<div> <div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Image Generation (Experimental)</div> <div class=" self-center text-xs font-medium">
{$i18n.t('Image Generation (Experimental)')}
</div>
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
on:click={() => { on:click={() => {
if (imageGenerationEngine === '' && AUTOMATIC1111_BASE_URL === '') { if (imageGenerationEngine === '' && AUTOMATIC1111_BASE_URL === '') {
toast.error('AUTOMATIC1111 Base URL is required.'); toast.error($i18n.t('AUTOMATIC1111 Base URL is required.'));
enableImageGeneration = false; enableImageGeneration = false;
} else if (imageGenerationEngine === 'openai' && OPENAI_API_KEY === '') { } else if (imageGenerationEngine === 'openai' && OPENAI_API_KEY === '') {
toast.error('OpenAI API Key is required.'); toast.error($i18n.t('OpenAI API Key is required.'));
enableImageGeneration = false; enableImageGeneration = false;
} else { } else {
enableImageGeneration = !enableImageGeneration; enableImageGeneration = !enableImageGeneration;
...@@ -179,9 +183,9 @@ ...@@ -179,9 +183,9 @@
type="button" type="button"
> >
{#if enableImageGeneration === true} {#if enableImageGeneration === true}
<span class="ml-2 self-center">On</span> <span class="ml-2 self-center">{$i18n.t('On')}</span>
{:else} {:else}
<span class="ml-2 self-center">Off</span> <span class="ml-2 self-center">{$i18n.t('Off')}</span>
{/if} {/if}
</button> </button>
</div> </div>
...@@ -190,12 +194,12 @@ ...@@ -190,12 +194,12 @@
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
{#if imageGenerationEngine === ''} {#if imageGenerationEngine === ''}
<div class=" mb-2.5 text-sm font-medium">AUTOMATIC1111 Base URL</div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('AUTOMATIC1111 Base URL')}</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<input <input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter URL (e.g. http://127.0.0.1:7860/)" placeholder={$i18n.t('Enter URL (e.g. http://127.0.0.1:7860/)')}
bind:value={AUTOMATIC1111_BASE_URL} bind:value={AUTOMATIC1111_BASE_URL}
/> />
</div> </div>
...@@ -224,22 +228,22 @@ ...@@ -224,22 +228,22 @@
</div> </div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> <div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Include `--api` flag when running stable-diffusion-webui {$i18n.t('Include `--api` flag when running stable-diffusion-webui')}
<a <a
class=" text-gray-300 font-medium" class=" text-gray-300 font-medium"
href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/3734" href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/3734"
target="_blank" target="_blank"
> >
(e.g. `sh webui.sh --api`) {$i18n.t('(e.g. `sh webui.sh --api`)')}
</a> </a>
</div> </div>
{:else if imageGenerationEngine === 'openai'} {:else if imageGenerationEngine === 'openai'}
<div class=" mb-2.5 text-sm font-medium">OpenAI API Key</div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('OpenAI API Key')}</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<input <input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter API Key" placeholder={$i18n.t('Enter API Key')}
bind:value={OPENAI_API_KEY} bind:value={OPENAI_API_KEY}
/> />
</div> </div>
...@@ -250,16 +254,16 @@ ...@@ -250,16 +254,16 @@
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
<div> <div>
<div class=" mb-2.5 text-sm font-medium">Set Default Model</div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Default Model')}</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<select <select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={selectedModel} bind:value={selectedModel}
placeholder="Select a model" placeholder={$i18n.t('Select a model')}
> >
{#if !selectedModel} {#if !selectedModel}
<option value="" disabled selected>Select a model</option> <option value="" disabled selected>{$i18n.t('Select a model')}</option>
{/if} {/if}
{#each models ?? [] as model} {#each models ?? [] as model}
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option> <option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
...@@ -270,12 +274,12 @@ ...@@ -270,12 +274,12 @@
</div> </div>
<div> <div>
<div class=" mb-2.5 text-sm font-medium">Set Image Size</div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Image Size')}</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<input <input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter Image Size (e.g. 512x512)" placeholder={$i18n.t('Enter Image Size (e.g. 512x512)')}
bind:value={imageSize} bind:value={imageSize}
/> />
</div> </div>
...@@ -283,12 +287,12 @@ ...@@ -283,12 +287,12 @@
</div> </div>
<div> <div>
<div class=" mb-2.5 text-sm font-medium">Set Steps</div> <div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Steps')}</div>
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1 mr-2"> <div class="flex-1 mr-2">
<input <input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter Number of Steps (e.g. 50)" placeholder={$i18n.t('Enter Number of Steps (e.g. 50)')}
bind:value={steps} bind:value={steps}
/> />
</div> </div>
...@@ -305,7 +309,7 @@ ...@@ -305,7 +309,7 @@
type="submit" type="submit"
disabled={loading} disabled={loading}
> >
Save {$i18n.t('Save')}
{#if loading} {#if loading}
<div class="ml-2 self-center"> <div class="ml-2 self-center">
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment