Commit 1bacd5d9 authored by Jun Siang Cheah's avatar Jun Siang Cheah
Browse files

Merge branch 'dev' into feat/model-config

parents 5d3eddf7 4edef531
<script lang="ts"> <script lang="ts">
import Spinner from '$lib/components/common/Spinner.svelte';
import { copyToClipboard } from '$lib/utils'; import { copyToClipboard } from '$lib/utils';
import hljs from 'highlight.js'; import hljs from 'highlight.js';
import 'highlight.js/styles/github-dark.min.css'; import 'highlight.js/styles/github-dark.min.css';
import { loadPyodide } from 'pyodide';
import { tick } from 'svelte';
import PyodideWorker from '../../../workers/pyodide.worker?worker';
export let id = '';
export let lang = ''; export let lang = '';
export let code = ''; export let code = '';
let executing = false;
let stdout = null;
let stderr = null;
let result = null;
let copied = false; let copied = false;
const copyCode = async () => { const copyCode = async () => {
...@@ -17,24 +29,306 @@ ...@@ -17,24 +29,306 @@
}, 1000); }, 1000);
}; };
const checkPythonCode = (str) => {
// Check if the string contains typical Python keywords, syntax, or functions
const pythonKeywords = [
'def',
'class',
'import',
'from',
'if',
'else',
'elif',
'for',
'while',
'try',
'except',
'finally',
'return',
'yield',
'lambda',
'assert',
'pass',
'break',
'continue',
'global',
'nonlocal',
'del',
'True',
'False',
'None',
'and',
'or',
'not',
'in',
'is',
'as',
'with'
];
for (let keyword of pythonKeywords) {
if (str.includes(keyword)) {
return true;
}
}
// Check if the string contains typical Python syntax characters
const pythonSyntax = [
'def ',
'class ',
'import ',
'from ',
'if ',
'else:',
'elif ',
'for ',
'while ',
'try:',
'except:',
'finally:',
'return ',
'yield ',
'lambda ',
'assert ',
'pass',
'break',
'continue',
'global ',
'nonlocal ',
'del ',
'True',
'False',
'None',
' and ',
' or ',
' not ',
' in ',
' is ',
' as ',
' with ',
':',
'=',
'==',
'!=',
'>',
'<',
'>=',
'<=',
'+',
'-',
'*',
'/',
'%',
'**',
'//',
'(',
')',
'[',
']',
'{',
'}'
];
for (let syntax of pythonSyntax) {
if (str.includes(syntax)) {
return true;
}
}
// If none of the above conditions met, it's probably not Python code
return false;
};
const executePython = async (code) => {
if (!code.includes('input') && !code.includes('matplotlib')) {
executePythonAsWorker(code);
} else {
result = null;
stdout = null;
stderr = null;
executing = true;
document.pyodideMplTarget = document.getElementById(`plt-canvas-${id}`);
let pyodide = await loadPyodide({
indexURL: '/pyodide/',
stdout: (text) => {
console.log('Python output:', text);
if (stdout) {
stdout += `${text}\n`;
} else {
stdout = `${text}\n`;
}
},
stderr: (text) => {
console.log('An error occured:', text);
if (stderr) {
stderr += `${text}\n`;
} else {
stderr = `${text}\n`;
}
},
packages: ['micropip']
});
try {
const micropip = pyodide.pyimport('micropip');
await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
let packages = [
code.includes('requests') ? 'requests' : null,
code.includes('bs4') ? 'beautifulsoup4' : null,
code.includes('numpy') ? 'numpy' : null,
code.includes('pandas') ? 'pandas' : null,
code.includes('matplotlib') ? 'matplotlib' : null,
code.includes('sklearn') ? 'scikit-learn' : null,
code.includes('scipy') ? 'scipy' : null,
code.includes('re') ? 'regex' : null
].filter(Boolean);
console.log(packages);
await micropip.install(packages);
result = await pyodide.runPythonAsync(`from js import prompt
def input(p):
return prompt(p)
__builtins__.input = input`);
result = await pyodide.runPython(code);
if (!result) {
result = '[NO OUTPUT]';
}
console.log(result);
console.log(stdout);
console.log(stderr);
const pltCanvasElement = document.getElementById(`plt-canvas-${id}`);
if (pltCanvasElement?.innerHTML !== '') {
pltCanvasElement.classList.add('pt-4');
}
} catch (error) {
console.error('Error:', error);
stderr = error;
}
executing = false;
}
};
const executePythonAsWorker = async (code) => {
result = null;
stdout = null;
stderr = null;
executing = true;
let packages = [
code.includes('requests') ? 'requests' : null,
code.includes('bs4') ? 'beautifulsoup4' : null,
code.includes('numpy') ? 'numpy' : null,
code.includes('pandas') ? 'pandas' : null,
code.includes('sklearn') ? 'scikit-learn' : null,
code.includes('scipy') ? 'scipy' : null,
code.includes('re') ? 'regex' : null
].filter(Boolean);
console.log(packages);
const pyodideWorker = new PyodideWorker();
pyodideWorker.postMessage({
id: id,
code: code,
packages: packages
});
setTimeout(() => {
if (executing) {
executing = false;
stderr = 'Execution Time Limit Exceeded';
pyodideWorker.terminate();
}
}, 60000);
pyodideWorker.onmessage = (event) => {
console.log('pyodideWorker.onmessage', event);
const { id, ...data } = event.data;
console.log(id, data);
data['stdout'] && (stdout = data['stdout']);
data['stderr'] && (stderr = data['stderr']);
data['result'] && (result = data['result']);
executing = false;
};
pyodideWorker.onerror = (event) => {
console.log('pyodideWorker.onerror', event);
executing = false;
};
};
$: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : ''; $: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : '';
</script> </script>
{#if code} {#if code}
<div class="mb-4"> <div class="mb-4" dir="ltr">
<div <div
class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto" class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
> >
<div class="p-1">{@html lang}</div> <div class="p-1">{@html lang}</div>
<div class="flex items-center">
{#if lang === 'python' || checkPythonCode(code)}
{#if executing}
<div class="copy-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
{:else}
<button
class="copy-code-button bg-none border-none p-1"
on:click={() => {
executePython(code);
}}>Run</button
>
{/if}
{/if}
<button class="copy-code-button bg-none border-none p-1" on:click={copyCode} <button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
>{copied ? 'Copied' : 'Copy Code'}</button >{copied ? 'Copied' : 'Copy Code'}</button
> >
</div> </div>
</div>
<pre <pre
class=" hljs p-4 px-5 overflow-x-auto" class=" hljs p-4 px-5 overflow-x-auto"
style="border-top-left-radius: 0px; border-top-right-radius: 0px;"><code style="border-top-left-radius: 0px; border-top-right-radius: 0px; {(executing ||
stdout ||
stderr ||
result) &&
'border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;'}"><code
class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code
></pre> ></pre>
<div
id="plt-canvas-{id}"
class="bg-[#202123] text-white max-w-full overflow-x-auto scrollbar-hidden"
/>
{#if executing}
<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div class="text-sm">Running...</div>
</div>
{:else if stdout || stderr || result}
<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div class="text-sm">{stdout || stderr || result}</div>
</div>
{/if}
</div> </div>
{/if} {/if}
<div class=" self-center font-bold mb-0.5 line-clamp-1"> <div class=" self-center font-bold mb-0.5 line-clamp-1 contents">
<slot /> <slot />
</div> </div>
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
> >
{#if model in modelfiles} {#if model in modelfiles}
<img <img
crossorigin="anonymous"
src={modelfiles[model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`} src={modelfiles[model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`}
alt="modelfile" alt="modelfile"
class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none" class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
...@@ -50,6 +51,7 @@ ...@@ -50,6 +51,7 @@
/> />
{:else} {:else}
<img <img
crossorigin="anonymous"
src={$i18n.language === 'dg-DG' src={$i18n.language === 'dg-DG'
? `/doge.png` ? `/doge.png`
: `${WEBUI_BASE_URL}/static/favicon.png`} : `${WEBUI_BASE_URL}/static/favicon.png`}
......
<script lang="ts"> <script lang="ts">
import { settings } from '$lib/stores';
import { WEBUI_BASE_URL } from '$lib/constants';
export let src = '/user.png'; export let src = '/user.png';
</script> </script>
<div class=" mr-3"> <div class={$settings?.chatDirection === 'LTR' ? 'mr-3' : 'ml-3'}>
<img {src} class=" w-8 object-cover rounded-full" alt="profile" draggable="false" /> <img
crossorigin="anonymous"
src={src.startsWith(WEBUI_BASE_URL) ||
src.startsWith('https://www.gravatar.com/avatar/') ||
src.startsWith('data:')
? src
: `/user.png`}
class=" w-8 object-cover rounded-full"
alt="profile"
draggable="false"
/>
</div> </div>
...@@ -332,7 +332,11 @@ ...@@ -332,7 +332,11 @@
<CitationsModal bind:show={showCitationModal} citation={selectedCitation} /> <CitationsModal bind:show={showCitationModal} citation={selectedCitation} />
{#key message.id} {#key message.id}
<div class=" flex w-full message-{message.id}" id="message-{message.id}"> <div
class=" flex w-full message-{message.id}"
id="message-{message.id}"
dir={$settings.chatDirection}
>
<ProfileImage <ProfileImage
src={modelfiles[message.model]?.imageUrl ?? src={modelfiles[message.model]?.imageUrl ??
($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)} ($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
...@@ -434,9 +438,10 @@ ...@@ -434,9 +438,10 @@
{:else if message.content === ''} {:else if message.content === ''}
<Skeleton /> <Skeleton />
{:else} {:else}
{#each tokens as token} {#each tokens as token, tokenIdx}
{#if token.type === 'code'} {#if token.type === 'code'}
<CodeBlock <CodeBlock
id={`${message.id}-${tokenIdx}`}
lang={token.lang} lang={token.lang}
code={revertSanitizedResponseContent(token.text)} code={revertSanitizedResponseContent(token.text)}
/> />
...@@ -494,7 +499,7 @@ ...@@ -494,7 +499,7 @@
class=" flex justify-start overflow-x-auto buttons text-gray-600 dark:text-gray-500" class=" flex justify-start overflow-x-auto buttons text-gray-600 dark:text-gray-500"
> >
{#if siblings.length > 1} {#if siblings.length > 1}
<div class="flex self-center"> <div class="flex self-center" dir="ltr">
<button <button
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition" class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
on:click={() => { on:click={() => {
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
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';
import { user as _user } from '$lib/stores';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
...@@ -54,7 +56,7 @@ ...@@ -54,7 +56,7 @@
}; };
</script> </script>
<div class=" flex w-full user-message"> <div class=" flex w-full user-message" dir={$settings.chatDirection}>
{#if !($settings?.chatBubble ?? true)} {#if !($settings?.chatBubble ?? true)}
<ProfileImage <ProfileImage
src={message.user src={message.user
...@@ -74,7 +76,7 @@ ...@@ -74,7 +76,7 @@
{$i18n.t('You')} {$i18n.t('You')}
<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span> <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}
{user.name} {user.name}
{:else} {:else}
{$i18n.t('You')} {$i18n.t('You')}
...@@ -239,7 +241,7 @@ ...@@ -239,7 +241,7 @@
> >
{#if !($settings?.chatBubble ?? true)} {#if !($settings?.chatBubble ?? true)}
{#if siblings.length > 1} {#if siblings.length > 1}
<div class="flex self-center"> <div class="flex self-center" dir="ltr">
<button <button
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition" class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
on:click={() => { on:click={() => {
...@@ -368,7 +370,7 @@ ...@@ -368,7 +370,7 @@
{#if $settings?.chatBubble ?? true} {#if $settings?.chatBubble ?? true}
{#if siblings.length > 1} {#if siblings.length > 1}
<div class="flex self-center"> <div class="flex self-center" dir="ltr">
<button <button
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition" class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
on:click={() => { on:click={() => {
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
let ollamaVersion = null; let ollamaVersion = null;
$: filteredItems = searchValue $: filteredItems = searchValue
? items.filter((item) => item.value.includes(searchValue.toLowerCase())) ? items.filter((item) => item.value.toLowerCase().includes(searchValue.toLowerCase()))
: items; : items;
const pullModelHandler = async () => { const pullModelHandler = async () => {
......
...@@ -5,28 +5,27 @@ ...@@ -5,28 +5,27 @@
import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama'; import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
import { import {
getOpenAIConfig,
getOpenAIKeys, getOpenAIKeys,
getOpenAIUrls, getOpenAIUrls,
updateOpenAIConfig,
updateOpenAIKeys, updateOpenAIKeys,
updateOpenAIUrls updateOpenAIUrls
} from '$lib/apis/openai'; } from '$lib/apis/openai';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import Switch from '$lib/components/common/Switch.svelte';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
export let getModels: Function; export let getModels: Function;
// External // External
let OLLAMA_BASE_URL = '';
let OLLAMA_BASE_URLS = ['']; let OLLAMA_BASE_URLS = [''];
let OPENAI_API_KEY = '';
let OPENAI_API_BASE_URL = '';
let OPENAI_API_KEYS = ['']; let OPENAI_API_KEYS = [''];
let OPENAI_API_BASE_URLS = ['']; let OPENAI_API_BASE_URLS = [''];
let showOpenAI = false; let ENABLE_OPENAI_API = false;
const updateOpenAIHandler = async () => { const updateOpenAIHandler = async () => {
OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS); OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS);
...@@ -52,6 +51,10 @@ ...@@ -52,6 +51,10 @@
onMount(async () => { onMount(async () => {
if ($user.role === 'admin') { if ($user.role === 'admin') {
OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token); OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token);
const config = await getOpenAIConfig(localStorage.token);
ENABLE_OPENAI_API = config.ENABLE_OPENAI_API;
OPENAI_API_BASE_URLS = await getOpenAIUrls(localStorage.token); OPENAI_API_BASE_URLS = await getOpenAIUrls(localStorage.token);
OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token); OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token);
} }
...@@ -70,16 +73,18 @@ ...@@ -70,16 +73,18 @@
<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">{$i18n.t('OpenAI API')}</div> <div class=" font-medium">{$i18n.t('OpenAI API')}</div>
<button
class=" text-xs font-medium text-gray-500" <div class="mt-1">
type="button" <Switch
on:click={() => { bind:state={ENABLE_OPENAI_API}
showOpenAI = !showOpenAI; on:change={async () => {
}}>{showOpenAI ? $i18n.t('Hide') : $i18n.t('Show')}</button updateOpenAIConfig(localStorage.token, ENABLE_OPENAI_API);
> }}
/>
</div>
</div> </div>
{#if showOpenAI} {#if ENABLE_OPENAI_API}
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
{#each OPENAI_API_BASE_URLS as url, idx} {#each OPENAI_API_BASE_URLS as url, idx}
<div class="flex w-full gap-2"> <div class="flex w-full gap-2">
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
let promptSuggestions = []; let promptSuggestions = [];
let showUsername = false; let showUsername = false;
let chatBubble = true; let chatBubble = true;
let chatDirection: 'LTR' | 'RTL' = 'LTR';
const toggleSplitLargeChunks = async () => { const toggleSplitLargeChunks = async () => {
splitLargeChunks = !splitLargeChunks; splitLargeChunks = !splitLargeChunks;
...@@ -76,6 +77,11 @@ ...@@ -76,6 +77,11 @@
} }
}; };
const toggleChangeChatDirection = async () => {
chatDirection = chatDirection === 'LTR' ? 'RTL' : 'LTR';
saveSettings({ chatDirection });
};
const updateInterfaceHandler = async () => { const updateInterfaceHandler = async () => {
if ($user.role === 'admin') { if ($user.role === 'admin') {
promptSuggestions = await setDefaultPromptSuggestions(localStorage.token, promptSuggestions); promptSuggestions = await setDefaultPromptSuggestions(localStorage.token, promptSuggestions);
...@@ -114,6 +120,7 @@ ...@@ -114,6 +120,7 @@
chatBubble = settings.chatBubble ?? true; chatBubble = settings.chatBubble ?? true;
fullScreenMode = settings.fullScreenMode ?? false; fullScreenMode = settings.fullScreenMode ?? false;
splitLargeChunks = settings.splitLargeChunks ?? false; splitLargeChunks = settings.splitLargeChunks ?? false;
chatDirection = settings.chatDirection ?? 'LTR';
}); });
</script> </script>
...@@ -210,6 +217,7 @@ ...@@ -210,6 +217,7 @@
</div> </div>
</div> </div>
{#if !$settings.chatBubble}
<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"> <div class=" self-center text-xs font-medium">
...@@ -231,6 +239,7 @@ ...@@ -231,6 +239,7 @@
</button> </button>
</div> </div>
</div> </div>
{/if}
<div> <div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
...@@ -255,6 +264,24 @@ ...@@ -255,6 +264,24 @@
</div> </div>
</div> </div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Chat direction')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
on:click={toggleChangeChatDirection}
type="button"
>
{#if chatDirection === 'LTR'}
<span class="ml-2 self-center">{$i18n.t('LTR')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('RTL')}</span>
{/if}
</button>
</div>
</div>
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
<div> <div>
......
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
<div class="mt-3 mb-1 ml-1"> <div class="mt-3 mb-1 ml-1">
<button <button
type="button" type="button"
class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-gray-300 dark:outline-gray-800 rounded-3xl" class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
> >
Manage Manage
</button> </button>
......
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
{$i18n.t('and create a new shared link.')} {$i18n.t('and create a new shared link.')}
{:else} {:else}
{$i18n.t( {$i18n.t(
"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat." "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat."
)} )}
{/if} {/if}
</div> </div>
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
} else if (size === 'md') { } else if (size === 'md') {
return 'w-[48rem]'; return 'w-[48rem]';
} else { } else {
return 'w-[50rem]'; return 'w-[52rem]';
} }
}; };
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
let searchValue = ''; let searchValue = '';
$: filteredItems = searchValue $: filteredItems = searchValue
? items.filter((item) => item.value.includes(searchValue.toLowerCase())) ? items.filter((item) => item.value.toLowerCase().includes(searchValue.toLowerCase()))
: items; : items;
</script> </script>
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
}} }}
class="flex h-5 min-h-5 w-9 shrink-0 cursor-pointer items-center rounded-full px-[3px] transition {state class="flex h-5 min-h-5 w-9 shrink-0 cursor-pointer items-center rounded-full px-[3px] transition {state
? ' bg-emerald-600' ? ' bg-emerald-600'
: 'bg-gray-200 dark:bg-transparent'} outline outline-gray-100 dark:outline-gray-800" : 'bg-gray-200 dark:bg-transparent'} outline outline-1 outline-gray-100 dark:outline-gray-800"
> >
<Switch.Thumb <Switch.Thumb
class="pointer-events-none block size-4 shrink-0 rounded-full bg-white transition-transform data-[state=checked]:translate-x-3.5 data-[state=unchecked]:translate-x-0 data-[state=unchecked]:shadow-mini " class="pointer-events-none block size-4 shrink-0 rounded-full bg-white transition-transform data-[state=checked]:translate-x-3.5 data-[state=unchecked]:translate-x-0 data-[state=unchecked]:shadow-mini "
......
...@@ -141,14 +141,15 @@ ...@@ -141,14 +141,15 @@
}} }}
> >
<button <button
class=" flex rounded-xl p-1.5 w-full hover:bg-gray-100 dark:hover:bg-gray-850 transition" class="select-none flex rounded-xl p-1.5 w-full hover:bg-gray-100 dark:hover:bg-gray-850 transition"
aria-label="User Menu" aria-label="User Menu"
> >
<div class=" self-center"> <div class=" self-center">
<img <img
src={$user.profile_image_url} src={$user.profile_image_url}
class=" size-6 object-cover rounded-full" class="size-6 object-cover rounded-full"
alt="User profile" alt="User profile"
draggable="false"
/> />
</div> </div>
</button> </button>
......
...@@ -248,6 +248,7 @@ ...@@ -248,6 +248,7 @@
> >
<div class="self-center mx-1.5"> <div class="self-center mx-1.5">
<img <img
crossorigin="anonymous"
src="{WEBUI_BASE_URL}/static/favicon.png" src="{WEBUI_BASE_URL}/static/favicon.png"
class=" size-6 -translate-x-1.5 rounded-full" class=" size-6 -translate-x-1.5 rounded-full"
alt="logo" alt="logo"
......
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
"Change Password": "تغير الباسورد", "Change Password": "تغير الباسورد",
"Chat": "المحادثة", "Chat": "المحادثة",
"Chat Bubble UI": "", "Chat Bubble UI": "",
"Chat direction": "",
"Chat History": "تاريخ المحادثة", "Chat History": "تاريخ المحادثة",
"Chat History is off for this browser.": "سجل الدردشة معطل لهذا المتصفح", "Chat History is off for this browser.": "سجل الدردشة معطل لهذا المتصفح",
"Chats": "المحادثات", "Chats": "المحادثات",
...@@ -249,6 +250,7 @@ ...@@ -249,6 +250,7 @@
"Light": "فاتح", "Light": "فاتح",
"Listening...": "جاري الاستماع", "Listening...": "جاري الاستماع",
"LLMs can make mistakes. Verify important information.": "يمكن أن تصدر بعض الأخطاء. لذلك يجب التحقق من المعلومات المهمة", "LLMs can make mistakes. Verify important information.": "يمكن أن تصدر بعض الأخطاء. لذلك يجب التحقق من المعلومات المهمة",
"LTR": "",
"Made by OpenWebUI Community": "OpenWebUI تم إنشاؤه بواسطة مجتمع ", "Made by OpenWebUI Community": "OpenWebUI تم إنشاؤه بواسطة مجتمع ",
"Make sure to enclose them with": "تأكد من إرفاقها", "Make sure to enclose them with": "تأكد من إرفاقها",
"Manage LiteLLM Models": "LiteLLM إدارة نماذج ", "Manage LiteLLM Models": "LiteLLM إدارة نماذج ",
...@@ -260,7 +262,7 @@ ...@@ -260,7 +262,7 @@
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "يمكن تنزيل 3 نماذج كحد أقصى في وقت واحد. الرجاء معاودة المحاولة في وقت لاحق.", "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "يمكن تنزيل 3 نماذج كحد أقصى في وقت واحد. الرجاء معاودة المحاولة في وقت لاحق.",
"May": "", "May": "",
"Memory": "", "Memory": "",
"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "لن تتم مشاركة الرسائل التي ترسلها بعد إنشاء الرابط الخاص بك. سيتمكن المستخدمون الذين لديهم عنوان URL من عرض الدردشة المشتركة.", "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "",
"Minimum Score": "الحد الأدنى من النقاط", "Minimum Score": "الحد الأدنى من النقاط",
"Mirostat": "Mirostat", "Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta", "Mirostat Eta": "Mirostat Eta",
...@@ -371,6 +373,7 @@ ...@@ -371,6 +373,7 @@
"Role": "منصب", "Role": "منصب",
"Rosé Pine": "Rosé Pine", "Rosé Pine": "Rosé Pine",
"Rosé Pine Dawn": "Rosé Pine Dawn", "Rosé Pine Dawn": "Rosé Pine Dawn",
"RTL": "",
"Save": "حفظ", "Save": "حفظ",
"Save & Create": "حفظ وإنشاء", "Save & Create": "حفظ وإنشاء",
"Save & Update": "حفظ وتحديث", "Save & Update": "حفظ وتحديث",
......
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
"Change Password": "Промяна на Парола", "Change Password": "Промяна на Парола",
"Chat": "Чат", "Chat": "Чат",
"Chat Bubble UI": "", "Chat Bubble UI": "",
"Chat direction": "",
"Chat History": "Чат История", "Chat History": "Чат История",
"Chat History is off for this browser.": "Чат История е изключен за този браузър.", "Chat History is off for this browser.": "Чат История е изключен за този браузър.",
"Chats": "Чатове", "Chats": "Чатове",
...@@ -249,6 +250,7 @@ ...@@ -249,6 +250,7 @@
"Light": "Светъл", "Light": "Светъл",
"Listening...": "Слушам...", "Listening...": "Слушам...",
"LLMs can make mistakes. Verify important information.": "LLMs могат да правят грешки. Проверете важните данни.", "LLMs can make mistakes. Verify important information.": "LLMs могат да правят грешки. Проверете важните данни.",
"LTR": "",
"Made by OpenWebUI Community": "Направено от OpenWebUI общността", "Made by OpenWebUI Community": "Направено от OpenWebUI общността",
"Make sure to enclose them with": "Уверете се, че са заключени с", "Make sure to enclose them with": "Уверете се, че са заключени с",
"Manage LiteLLM Models": "Управление на LiteLLM Моделите", "Manage LiteLLM Models": "Управление на LiteLLM Моделите",
...@@ -260,7 +262,7 @@ ...@@ -260,7 +262,7 @@
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимум 3 модели могат да бъдат сваляни едновременно. Моля, опитайте отново по-късно.", "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимум 3 модели могат да бъдат сваляни едновременно. Моля, опитайте отново по-късно.",
"May": "", "May": "",
"Memory": "", "Memory": "",
"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "", "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "",
"Minimum Score": "", "Minimum Score": "",
"Mirostat": "Mirostat", "Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta", "Mirostat Eta": "Mirostat Eta",
...@@ -371,6 +373,7 @@ ...@@ -371,6 +373,7 @@
"Role": "Роля", "Role": "Роля",
"Rosé Pine": "Rosé Pine", "Rosé Pine": "Rosé Pine",
"Rosé Pine Dawn": "Rosé Pine Dawn", "Rosé Pine Dawn": "Rosé Pine Dawn",
"RTL": "",
"Save": "Запис", "Save": "Запис",
"Save & Create": "Запис & Създаване", "Save & Create": "Запис & Създаване",
"Save & Update": "Запис & Актуализиране", "Save & Update": "Запис & Актуализиране",
......
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
"Change Password": "পাসওয়ার্ড পরিবর্তন করুন", "Change Password": "পাসওয়ার্ড পরিবর্তন করুন",
"Chat": "চ্যাট", "Chat": "চ্যাট",
"Chat Bubble UI": "", "Chat Bubble UI": "",
"Chat direction": "",
"Chat History": "চ্যাট হিস্টোরি", "Chat History": "চ্যাট হিস্টোরি",
"Chat History is off for this browser.": "এই ব্রাউজারের জন্য চ্যাট হিস্টোরি বন্ধ আছে", "Chat History is off for this browser.": "এই ব্রাউজারের জন্য চ্যাট হিস্টোরি বন্ধ আছে",
"Chats": "চ্যাটসমূহ", "Chats": "চ্যাটসমূহ",
...@@ -249,6 +250,7 @@ ...@@ -249,6 +250,7 @@
"Light": "লাইট", "Light": "লাইট",
"Listening...": "শুনছে...", "Listening...": "শুনছে...",
"LLMs can make mistakes. Verify important information.": "LLM ভুল করতে পারে। গুরুত্বপূর্ণ তথ্য যাচাই করে নিন।", "LLMs can make mistakes. Verify important information.": "LLM ভুল করতে পারে। গুরুত্বপূর্ণ তথ্য যাচাই করে নিন।",
"LTR": "",
"Made by OpenWebUI Community": "OpenWebUI কমিউনিটিকর্তৃক নির্মিত", "Made by OpenWebUI Community": "OpenWebUI কমিউনিটিকর্তৃক নির্মিত",
"Make sure to enclose them with": "এটা দিয়ে বন্ধনী দিতে ভুলবেন না", "Make sure to enclose them with": "এটা দিয়ে বন্ধনী দিতে ভুলবেন না",
"Manage LiteLLM Models": "LiteLLM মডেল ব্যবস্থাপনা করুন", "Manage LiteLLM Models": "LiteLLM মডেল ব্যবস্থাপনা করুন",
...@@ -260,7 +262,7 @@ ...@@ -260,7 +262,7 @@
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "একসঙ্গে সর্বোচ্চ তিনটি মডেল ডাউনলোড করা যায়। দয়া করে পরে আবার চেষ্টা করুন।", "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "একসঙ্গে সর্বোচ্চ তিনটি মডেল ডাউনলোড করা যায়। দয়া করে পরে আবার চেষ্টা করুন।",
"May": "", "May": "",
"Memory": "", "Memory": "",
"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "", "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "",
"Minimum Score": "", "Minimum Score": "",
"Mirostat": "Mirostat", "Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta", "Mirostat Eta": "Mirostat Eta",
...@@ -371,6 +373,7 @@ ...@@ -371,6 +373,7 @@
"Role": "পদবি", "Role": "পদবি",
"Rosé Pine": "রোজ পাইন", "Rosé Pine": "রোজ পাইন",
"Rosé Pine Dawn": "ভোরের রোজ পাইন", "Rosé Pine Dawn": "ভোরের রোজ পাইন",
"RTL": "",
"Save": "সংরক্ষণ", "Save": "সংরক্ষণ",
"Save & Create": "সংরক্ষণ এবং তৈরি করুন", "Save & Create": "সংরক্ষণ এবং তৈরি করুন",
"Save & Update": "সংরক্ষণ এবং আপডেট করুন", "Save & Update": "সংরক্ষণ এবং আপডেট করুন",
......
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
"Change Password": "Canvia la Contrasenya", "Change Password": "Canvia la Contrasenya",
"Chat": "Xat", "Chat": "Xat",
"Chat Bubble UI": "", "Chat Bubble UI": "",
"Chat direction": "",
"Chat History": "Històric del Xat", "Chat History": "Històric del Xat",
"Chat History is off for this browser.": "L'historial de xat està desactivat per a aquest navegador.", "Chat History is off for this browser.": "L'historial de xat està desactivat per a aquest navegador.",
"Chats": "Xats", "Chats": "Xats",
...@@ -249,6 +250,7 @@ ...@@ -249,6 +250,7 @@
"Light": "Clar", "Light": "Clar",
"Listening...": "Escoltant...", "Listening...": "Escoltant...",
"LLMs can make mistakes. Verify important information.": "Els LLMs poden cometre errors. Verifica la informació important.", "LLMs can make mistakes. Verify important information.": "Els LLMs poden cometre errors. Verifica la informació important.",
"LTR": "",
"Made by OpenWebUI Community": "Creat per la Comunitat OpenWebUI", "Made by OpenWebUI Community": "Creat per la Comunitat OpenWebUI",
"Make sure to enclose them with": "Assegura't d'envoltar-los amb", "Make sure to enclose them with": "Assegura't d'envoltar-los amb",
"Manage LiteLLM Models": "Gestiona Models LiteLLM", "Manage LiteLLM Models": "Gestiona Models LiteLLM",
...@@ -260,7 +262,7 @@ ...@@ -260,7 +262,7 @@
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Es poden descarregar un màxim de 3 models simultàniament. Si us plau, prova-ho més tard.", "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Es poden descarregar un màxim de 3 models simultàniament. Si us plau, prova-ho més tard.",
"May": "", "May": "",
"Memory": "", "Memory": "",
"Messages you send after creating your link won't be shared. Users with the URL will beable to view the shared chat.": "", "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "",
"Minimum Score": "", "Minimum Score": "",
"Mirostat": "Mirostat", "Mirostat": "Mirostat",
"Mirostat Eta": "Eta de Mirostat", "Mirostat Eta": "Eta de Mirostat",
...@@ -371,6 +373,7 @@ ...@@ -371,6 +373,7 @@
"Role": "Rol", "Role": "Rol",
"Rosé Pine": "Rosé Pine", "Rosé Pine": "Rosé Pine",
"Rosé Pine Dawn": "Albada Rosé Pine", "Rosé Pine Dawn": "Albada Rosé Pine",
"RTL": "",
"Save": "Guarda", "Save": "Guarda",
"Save & Create": "Guarda i Crea", "Save & Create": "Guarda i Crea",
"Save & Update": "Guarda i Actualitza", "Save & Update": "Guarda i Actualitza",
......
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