Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
9763d885
Unverified
Commit
9763d885
authored
Apr 02, 2024
by
lainedfles
Committed by
GitHub
Apr 02, 2024
Browse files
Merge Updates & Dockerfile improvements
parent
fdef2abd
Changes
155
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
797 additions
and
372 deletions
+797
-372
src/lib/components/admin/SettingsModal.svelte
src/lib/components/admin/SettingsModal.svelte
+7
-4
src/lib/components/chat/MessageInput.svelte
src/lib/components/chat/MessageInput.svelte
+23
-15
src/lib/components/chat/MessageInput/Documents.svelte
src/lib/components/chat/MessageInput/Documents.svelte
+8
-4
src/lib/components/chat/MessageInput/Models.svelte
src/lib/components/chat/MessageInput/Models.svelte
+7
-3
src/lib/components/chat/MessageInput/PromptCommands.svelte
src/lib/components/chat/MessageInput/PromptCommands.svelte
+7
-4
src/lib/components/chat/Messages.svelte
src/lib/components/chat/Messages.svelte
+4
-2
src/lib/components/chat/Messages/Placeholder.svelte
src/lib/components/chat/Messages/Placeholder.svelte
+7
-5
src/lib/components/chat/Messages/ResponseMessage.svelte
src/lib/components/chat/Messages/ResponseMessage.svelte
+7
-5
src/lib/components/chat/Messages/UserMessage.svelte
src/lib/components/chat/Messages/UserMessage.svelte
+12
-9
src/lib/components/chat/ModelSelector.svelte
src/lib/components/chat/ModelSelector.svelte
+72
-98
src/lib/components/chat/ModelSelector/Selector.svelte
src/lib/components/chat/ModelSelector/Selector.svelte
+389
-0
src/lib/components/chat/Settings/About.svelte
src/lib/components/chat/Settings/About.svelte
+13
-9
src/lib/components/chat/Settings/Account.svelte
src/lib/components/chat/Settings/Account.svelte
+9
-7
src/lib/components/chat/Settings/Account/UpdatePassword.svelte
...ib/components/chat/Settings/Account/UpdatePassword.svelte
+10
-7
src/lib/components/chat/Settings/Advanced.svelte
src/lib/components/chat/Settings/Advanced.svelte
+13
-11
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
...b/components/chat/Settings/Advanced/AdvancedParams.svelte
+40
-36
src/lib/components/chat/Settings/Audio.svelte
src/lib/components/chat/Settings/Audio.svelte
+33
-25
src/lib/components/chat/Settings/Chats.svelte
src/lib/components/chat/Settings/Chats.svelte
+14
-43
src/lib/components/chat/Settings/Connections.svelte
src/lib/components/chat/Settings/Connections.svelte
+16
-13
src/lib/components/chat/Settings/General.svelte
src/lib/components/chat/Settings/General.svelte
+106
-72
No files found.
src/lib/components/admin/SettingsModal.svelte
View file @
9763d885
<script>
import { getContext } from 'svelte';
import Modal from '../common/Modal.svelte';
import Database from './Settings/Database.svelte';
import General from './Settings/General.svelte';
import Users from './Settings/Users.svelte';
const i18n = getContext('i18n');
export let show = false;
let selectedTab = 'general';
...
...
@@ -13,7 +16,7 @@
<Modal bind:show>
<div>
<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
class="self-center"
on:click={() => {
...
...
@@ -61,7 +64,7 @@
/>
</svg>
</div>
<div class=" self-center">General</div>
<div class=" self-center">
{$i18n.t('
General
')}
</div>
</button>
<button
...
...
@@ -85,7 +88,7 @@
/>
</svg>
</div>
<div class=" self-center">Users</div>
<div class=" self-center">
{$i18n.t('
Users
')}
</div>
</button>
<button
...
...
@@ -113,7 +116,7 @@
/>
</svg>
</div>
<div class=" self-center">Database</div>
<div class=" self-center">
{$i18n.t('
Database
')}
</div>
</button>
</div>
<div class="flex-1 md:min-h-[380px]">
...
...
src/lib/components/chat/MessageInput.svelte
View file @
9763d885
<script lang="ts">
import { toast } from 'svelte-sonner';
import { onMount, tick } from 'svelte';
import { onMount, tick
, getContext
} from 'svelte';
import { settings } from '$lib/stores';
import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
...
...
@@ -14,6 +14,8 @@
import { transcribeAudio } from '$lib/apis/audio';
import Tooltip from '../common/Tooltip.svelte';
const i18n = getContext('i18n');
export let submitPrompt: Function;
export let stopResponse: Function;
...
...
@@ -209,11 +211,11 @@
// Event triggered when an error occurs
speechRecognition.onerror = function (event) {
console.log(event);
toast.error(`Speech recognition error:
$
{event.error
}`
);
toast.error(
$i18n.t(
`Speech recognition error:
{
{
error}}`, { error:
event.error
})
);
isRecording = false;
};
} 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 @@
uploadDoc(file);
} else {
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);
}
} else {
toast.error(`File not found.`);
toast.error(
$i18n.t(
`File not found.`)
)
;
}
}
...
...
@@ -477,13 +482,16 @@
filesInputElement.value = '';
} else {
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);
filesInputElement.value = '';
}
} else {
toast.error(`File not found.`);
toast.error(
$i18n.t(
`File not found.`)
)
;
}
}}
/>
...
...
@@ -570,7 +578,7 @@
{file.name}
</div>
<div class=" text-gray-500 text-sm">Document</div>
<div class=" text-gray-500 text-sm">
{$i18n.t('
Document
')}
</div>
</div>
</div>
{:else if file.type === 'collection'}
...
...
@@ -598,7 +606,7 @@
{file?.title ?? `#${file.name}`}
</div>
<div class=" text-gray-500 text-sm">Collection</div>
<div class=" text-gray-500 text-sm">
{$i18n.t('
Collection
')}
</div>
</div>
</div>
{/if}
...
...
@@ -632,7 +640,7 @@
<div class=" flex">
{#if fileUploadEnabled}
<div class=" self-end mb-2 ml-1">
<Tooltip content=
"
Upload files
"
>
<Tooltip content=
{$i18n.t('
Upload files
')}
>
<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"
type="button"
...
...
@@ -664,8 +672,8 @@
placeholder={chatInputPlaceholder !== ''
? chatInputPlaceholder
: isRecording
? 'Listening...'
: 'Send a
m
essage'}
?
$i18n.t(
'Listening...'
)
:
$i18n.t(
'Send a
M
essage'
)
}
bind:value={prompt}
on:keypress={(e) => {
if (e.keyCode == 13 && !e.shiftKey) {
...
...
@@ -804,7 +812,7 @@
<div class="self-end mb-2 flex space-x-1 mr-1">
{#if messages.length == 0 || messages.at(-1).done == true}
<Tooltip content=
"
Record voice
"
>
<Tooltip content=
{$i18n.t('
Record voice
')}
>
{#if speechRecognitionEnabled}
<button
id="voice-input-button"
...
...
@@ -873,7 +881,7 @@
{/if}
</Tooltip>
<Tooltip content=
"
Send message
"
>
<Tooltip content=
{$i18n.t('
Send message
')}
>
<button
class="{prompt !== ''
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
...
...
@@ -919,7 +927,7 @@
</form>
<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>
...
...
src/lib/components/chat/MessageInput/Documents.svelte
View file @
9763d885
...
...
@@ -3,9 +3,11 @@
import { documents } from '$lib/stores';
import { removeFirstHashWord, isValidHttpUrl } from '$lib/utils';
import { tick } from 'svelte';
import { tick
, getContext
} from 'svelte';
import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let prompt = '';
const dispatch = createEventDispatcher();
...
...
@@ -117,7 +119,7 @@
{doc?.title ?? `#${doc.name}`}
</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}
<div class=" font-medium text-black line-clamp-1">
#{doc.name} ({doc.filename})
...
...
@@ -140,7 +142,9 @@
confirmSelectWeb(url);
} else {
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 @@
{prompt.split(' ')?.at(0)?.substring(1)}
</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>
{/if}
</div>
...
...
src/lib/components/chat/MessageInput/Models.svelte
View file @
9763d885
...
...
@@ -2,9 +2,11 @@
import { generatePrompt } from '$lib/apis/ollama';
import { models } from '$lib/stores';
import { splitStream } from '$lib/utils';
import { tick } from 'svelte';
import { tick
, getContext
} from 'svelte';
import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let prompt = '';
export let user = null;
...
...
@@ -41,7 +43,7 @@
user = JSON.parse(JSON.stringify(model.name));
await tick();
chatInputPlaceholder =
`'$
{model
.n
ame}
'
is thinking...
`
;
chatInputPlaceholder =
$i18n.t('{
{model
N
ame}
}
is thinking...
', { modelName: model.name })
;
const chatInputElement = document.getElementById('chat-textarea');
...
...
@@ -113,7 +115,9 @@
toast.error(error.error);
}
} 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' })
);
}
}
...
...
src/lib/components/chat/MessageInput/PromptCommands.svelte
View file @
9763d885
<script lang="ts">
import { prompts } from '$lib/stores';
import { findWordIndices } from '$lib/utils';
import { tick } from 'svelte';
import { tick
, getContext
} from 'svelte';
import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let prompt = '';
let selectedCommandIdx = 0;
let filteredPromptCommands = [];
...
...
@@ -29,7 +31,7 @@
if (command.content.includes('{{CLIPBOARD}}')) {
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}}';
});
...
...
@@ -113,8 +115,9 @@
</div>
<div class="line-clamp-1">
Tip: Update multiple variable slots consecutively by pressing the tab key in the chat
input after each replacement.
{$i18n.t(
'Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.'
)}
</div>
</div>
</div>
...
...
src/lib/components/chat/Messages.svelte
View file @
9763d885
...
...
@@ -2,7 +2,7 @@
import { v4 as uuidv4 } from 'uuid';
import { chats, config, modelfiles, settings, user } from '$lib/stores';
import { tick } from 'svelte';
import { tick
, getContext
} from 'svelte';
import { toast } from 'svelte-sonner';
import { getChatList, updateChatById } from '$lib/apis/chats';
...
...
@@ -13,6 +13,8 @@
import Spinner from '../common/Spinner.svelte';
import { imageGenerations } from '$lib/apis/images';
const i18n = getContext('i18n');
export let chatId = '';
export let sendPrompt: Function;
export let continueGeneration: Function;
...
...
@@ -67,7 +69,7 @@
navigator.clipboard.writeText(text).then(
function () {
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) {
console.error('Async: Could not copy text: ', err);
...
...
src/lib/components/chat/Messages/Placeholder.svelte
View file @
9763d885
<script lang="ts">
import { WEBUI_BASE_URL } from '$lib/constants';
import { user } from '$lib/stores';
import { onMount } from 'svelte';
import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
export let models = [];
export let modelfiles = [];
...
...
@@ -31,7 +33,7 @@
<img
src={modelfiles[model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`}
alt="modelfile"
class="
w
-1
4
rounded-full border-[1px] border-gray-200 dark:border-none"
class="
size
-1
2
rounded-full border-[1px] border-gray-200 dark:border-none"
draggable="false"
/>
{:else}
...
...
@@ -39,7 +41,7 @@
src={models.length === 1
? `${WEBUI_BASE_URL}/static/favicon.png`
: `${WEBUI_BASE_URL}/static/favicon.png`}
class="
w
-1
4
rounded-full border-[1px] border-gray-200 dark:border-none"
class="
size
-1
2
rounded-full border-[1px] border-gray-200 dark:border-none"
alt="logo"
draggable="false"
/>
...
...
@@ -64,9 +66,9 @@
</div>
{/if}
{: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}
</div>
</div>
...
...
src/lib/components/chat/Messages/ResponseMessage.svelte
View file @
9763d885
...
...
@@ -8,7 +8,9 @@
import { fade } from 'svelte/transition';
import { createEventDispatcher } from 'svelte';
import { onMount, tick } from 'svelte';
import { onMount, tick, getContext } from 'svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
...
...
@@ -316,7 +318,7 @@
{#if message.timestamp}
<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>
{/if}
</Name>
...
...
@@ -360,7 +362,7 @@
editMessageConfirmHandler();
}}
>
Save
{$i18n.t('
Save
')}
</button>
<button
...
...
@@ -369,7 +371,7 @@
cancelEditMessage();
}}
>
Cancel
{$i18n.t('
Cancel
')}
</button>
</div>
</div>
...
...
@@ -420,7 +422,7 @@
class=" flex justify-start space-x-1 overflow-x-auto buttons text-gray-700 dark:text-gray-500"
>
{#if siblings.length > 1}
<div class="flex self-center min-w-fit">
<div class="flex self-center min-w-fit
-mt-1
">
<button
class="self-center dark:hover:text-white hover:text-black transition"
on:click={() => {
...
...
src/lib/components/chat/Messages/UserMessage.svelte
View file @
9763d885
<script lang="ts">
import dayjs from 'dayjs';
import { tick, createEventDispatcher } from 'svelte';
import { tick, createEventDispatcher
, getContext
} from 'svelte';
import Name from './Name.svelte';
import ProfileImage from './ProfileImage.svelte';
import { modelfiles, settings } from '$lib/stores';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let user;
...
...
@@ -65,17 +67,18 @@
{#if $modelfiles.map((modelfile) => modelfile.tagName).includes(message.user)}
{$modelfiles.find((modelfile) => modelfile.tagName === message.user)?.title}
{: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}
{:else if $settings.showUsername}
{user.name}
{:else}
You
{$i18n.t('You')}
{/if}
{#if message.timestamp}
<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>
{/if}
</Name>
...
...
@@ -123,7 +126,7 @@
{file.name}
</div>
<div class=" text-gray-500 text-sm">Document</div>
<div class=" text-gray-500 text-sm">
{$i18n.t('
Document
')}
</div>
</div>
</button>
{:else if file.type === 'collection'}
...
...
@@ -152,7 +155,7 @@
{file?.title ?? `#${file.name}`}
</div>
<div class=" text-gray-500 text-sm">Collection</div>
<div class=" text-gray-500 text-sm">
{$i18n.t('
Collection
')}
</div>
</div>
</button>
{/if}
...
...
@@ -181,7 +184,7 @@
editMessageConfirmHandler();
}}
>
Save & Submit
{$i18n.t('
Save & Submit
')}
</button>
<button
...
...
@@ -190,7 +193,7 @@
cancelEditMessage();
}}
>
Cancel
{$i18n.t('
Cancel
')}
</button>
</div>
</div>
...
...
@@ -200,7 +203,7 @@
<div class=" flex justify-start space-x-1 text-gray-700 dark:text-gray-500">
{#if siblings.length > 1}
<div class="flex self-center">
<div class="flex self-center
-mt-1
">
<button
class="self-center dark:hover:text-white hover:text-black transition"
on:click={() => {
...
...
src/lib/components/chat/ModelSelector.svelte
View file @
9763d885
<script lang="ts">
import { Collapsible } from 'bits-ui';
import { setDefaultModels } from '$lib/apis/configs';
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 Selector from './ModelSelector/Selector.svelte';
import Tooltip from '../common/Tooltip.svelte';
const i18n = getContext('i18n');
export let selectedModels = [''];
export let disabled = false;
...
...
@@ -10,7 +16,7 @@
const saveDefaultModel = async () => {
const hasEmptyModel = selectedModels.filter((it) => it === '');
if (hasEmptyModel.length) {
toast.error('Choose a model before saving...');
toast.error(
$i18n.t(
'Choose a model before saving...')
)
;
return;
}
settings.set({ ...$settings, models: selectedModels });
...
...
@@ -20,7 +26,7 @@
console.log('setting default models globally');
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) {
...
...
@@ -30,108 +36,76 @@
}
</script>
<div class="flex flex-col m
y-2
">
<div class="flex flex-col m
t-0.5 w-full
">
{#each selectedModels as selectedModel, selectedModelIdx}
<div class="flex">
<select
id="models"
class="outline-none bg-transparent text-lg font-semibold rounded-lg block w-full placeholder-gray-400"
bind:value={selectedModel}
{disabled}
>
<option class=" text-gray-700" value="" selected disabled>Select a model</option>
{#each $models as model}
{#if model.name === 'hr'}
<hr />
{:else}
<option value={model.id} class="text-gray-700 text-lg"
>{model.name +
`${model.size ? ` (${(model.size / 1024 ** 3).toFixed(1)}GB)` : ''}`}</option
>
{/if}
{/each}
</select>
<div class="flex w-full">
<div class="overflow-hidden w-full">
<div class="mr-0.5 max-w-full">
<Selector
placeholder={$i18n.t('Select a model')}
items={$models
.filter((model) => model.name !== 'hr')
.map((model) => ({
value: model.id,
label: model.name,
info: model
}))}
bind:value={selectedModel}
/>
</div>
</div>
{#if selectedModelIdx === 0}
<button
class=" self-center {selectedModelIdx === 0
? 'mr-3'
: 'mr-7'} disabled:text-gray-600 disabled:hover:text-gray-600"
{disabled}
on:click={() => {
selectedModels = [...selectedModels, ''];
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6" />
</svg>
</button>
<div class=" self-center mr-2 disabled:text-gray-600 disabled:hover:text-gray-600">
<Tooltip content="Add Model">
<button
class=" "
{disabled}
on:click={() => {
selectedModels = [...selectedModels, ''];
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6" />
</svg>
</button>
</Tooltip>
</div>
{:else}
<button
class=" self-center disabled:text-gray-600 disabled:hover:text-gray-600 {selectedModelIdx ===
0
? 'mr-3'
: 'mr-7'}"
{disabled}
on:click={() => {
selectedModels.splice(selectedModelIdx, 1);
selectedModels = selectedModels;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" />
</svg>
</button>
{/if}
{#if selectedModelIdx === 0}
<button
class=" self-center dark:hover:text-gray-300"
id="open-settings-button"
on:click={async () => {
await showSettings.set(!$showSettings);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</button>
<div class=" self-center disabled:text-gray-600 disabled:hover:text-gray-600 mr-2">
<Tooltip content="Remove Model">
<button
{disabled}
on:click={() => {
selectedModels.splice(selectedModelIdx, 1);
selectedModels = selectedModels;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15" />
</svg>
</button>
</Tooltip>
</div>
{/if}
</div>
{/each}
</div>
<div class="text-left mt-
1
.5
text-xs
text-gray-500">
<button on:click={saveDefaultModel}> Set as default</button>
<div class="text-left mt-
0
.5
ml-1 text-[0.7rem]
text-gray-500">
<button on:click={saveDefaultModel}>
{$i18n.t('
Set as default
')}
</button>
</div>
src/lib/components/chat/ModelSelector/Selector.svelte
0 → 100644
View file @
9763d885
<script lang="ts">
import { Select } from 'bits-ui';
import { flyAndScale } from '$lib/utils/transitions';
import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
import Check from '$lib/components/icons/Check.svelte';
import Search from '$lib/components/icons/Search.svelte';
import { cancelOllamaRequest, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
import { user, MODEL_DOWNLOAD_POOL, models } from '$lib/stores';
import { toast } from 'svelte-sonner';
import { capitalizeFirstLetter, getModels, splitStream } from '$lib/utils';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let value = '';
export let placeholder = 'Select a model';
export let searchEnabled = true;
export let searchPlaceholder = 'Search a model';
export let items = [{ value: 'mango', label: 'Mango' }];
let searchValue = '';
let ollamaVersion = null;
$: filteredItems = searchValue
? items.filter((item) => item.value.includes(searchValue.toLowerCase()))
: items;
const pullModelHandler = async () => {
const sanitizedModelTag = searchValue.trim();
console.log($MODEL_DOWNLOAD_POOL);
if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag]) {
toast.error(
$i18n.t(`Model '{{modelTag}}' is already in queue for downloading.`, {
modelTag: sanitizedModelTag
})
);
return;
}
if (Object.keys($MODEL_DOWNLOAD_POOL).length === 3) {
toast.error(
$i18n.t('Maximum of 3 models can be downloaded simultaneously. Please try again later.')
);
return;
}
const res = await pullModel(localStorage.token, sanitizedModelTag, '0').catch((error) => {
toast.error(error);
return null;
});
if (res) {
const reader = res.body
.pipeThrough(new TextDecoderStream())
.pipeThrough(splitStream('\n'))
.getReader();
while (true) {
try {
const { value, done } = await reader.read();
if (done) break;
let lines = value.split('\n');
for (const line of lines) {
if (line !== '') {
let data = JSON.parse(line);
console.log(data);
if (data.error) {
throw data.error;
}
if (data.detail) {
throw data.detail;
}
if (data.id) {
MODEL_DOWNLOAD_POOL.set({
...$MODEL_DOWNLOAD_POOL,
[sanitizedModelTag]: {
...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
requestId: data.id,
reader,
done: false
}
});
console.log(data);
}
if (data.status) {
if (data.digest) {
let downloadProgress = 0;
if (data.completed) {
downloadProgress = Math.round((data.completed / data.total) * 1000) / 10;
} else {
downloadProgress = 100;
}
MODEL_DOWNLOAD_POOL.set({
...$MODEL_DOWNLOAD_POOL,
[sanitizedModelTag]: {
...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
pullProgress: downloadProgress,
digest: data.digest
}
});
} else {
toast.success(data.status);
MODEL_DOWNLOAD_POOL.set({
...$MODEL_DOWNLOAD_POOL,
[sanitizedModelTag]: {
...$MODEL_DOWNLOAD_POOL[sanitizedModelTag],
done: data.status === 'success'
}
});
}
}
}
}
} catch (error) {
console.log(error);
if (typeof error !== 'string') {
error = error.message;
}
toast.error(error);
// opts.callback({ success: false, error, modelName: opts.modelName });
}
}
if ($MODEL_DOWNLOAD_POOL[sanitizedModelTag].done) {
toast.success(
$i18n.t(`Model '{{modelName}}' has been successfully downloaded.`, {
modelName: sanitizedModelTag
})
);
models.set(await getModels(localStorage.token));
} else {
toast.error('Download canceled');
}
delete $MODEL_DOWNLOAD_POOL[sanitizedModelTag];
MODEL_DOWNLOAD_POOL.set({
...$MODEL_DOWNLOAD_POOL
});
}
};
onMount(async () => {
ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
});
const cancelModelPullHandler = async (model: string) => {
const { reader, requestId } = $MODEL_DOWNLOAD_POOL[model];
if (reader) {
await reader.cancel();
await cancelOllamaRequest(localStorage.token, requestId);
delete $MODEL_DOWNLOAD_POOL[model];
MODEL_DOWNLOAD_POOL.set({
...$MODEL_DOWNLOAD_POOL
});
await deleteModel(localStorage.token, model);
toast.success(`${model} download has been canceled`);
}
};
</script>
<Select.Root
{items}
onOpenChange={async () => {
searchValue = '';
window.setTimeout(() => document.getElementById('model-search-input')?.focus(), 0);
}}
selected={items.find((item) => item.value === value) ?? ''}
onSelectedChange={(selectedItem) => {
value = selectedItem.value;
}}
>
<Select.Trigger class="relative w-full" aria-label={placeholder}>
<Select.Value
class="flex text-left px-0.5 outline-none bg-transparent truncate text-lg font-semibold placeholder-gray-400 focus:outline-none"
{placeholder}
/>
<ChevronDown className="absolute end-2 top-1/2 -translate-y-[45%] size-3.5" strokeWidth="2.5" />
</Select.Trigger>
<Select.Content
class=" z-40 w-full rounded-lg bg-white dark:bg-gray-900 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-700/50 outline-none"
transition={flyAndScale}
sideOffset={4}
>
<slot>
{#if searchEnabled}
<div class="flex items-center gap-2.5 px-5 mt-3.5 mb-3">
<Search className="size-4" strokeWidth="2.5" />
<input
id="model-search-input"
bind:value={searchValue}
class="w-full text-sm bg-transparent outline-none"
placeholder={searchPlaceholder}
/>
</div>
<hr class="border-gray-100 dark:border-gray-800" />
{/if}
<div class="px-3 my-2 max-h-72 overflow-y-auto">
{#each filteredItems as item}
<Select.Item
class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
value={item.value}
label={item.label}
>
<div class="flex items-center gap-2">
<div class="line-clamp-1">
{item.label}
<span class=" text-xs font-medium text-gray-600 dark:text-gray-400"
>{item.info?.details?.parameter_size ?? ''}</span
>
</div>
<!-- {JSON.stringify(item.info)} -->
{#if item.info.external}
<Tooltip content={item.info?.source ?? 'External'}>
<div class=" mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="size-3"
>
<path
fill-rule="evenodd"
d="M8.914 6.025a.75.75 0 0 1 1.06 0 3.5 3.5 0 0 1 0 4.95l-2 2a3.5 3.5 0 0 1-5.396-4.402.75.75 0 0 1 1.251.827 2 2 0 0 0 3.085 2.514l2-2a2 2 0 0 0 0-2.828.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
<path
fill-rule="evenodd"
d="M7.086 9.975a.75.75 0 0 1-1.06 0 3.5 3.5 0 0 1 0-4.95l2-2a3.5 3.5 0 0 1 5.396 4.402.75.75 0 0 1-1.251-.827 2 2 0 0 0-3.085-2.514l-2 2a2 2 0 0 0 0 2.828.75.75 0 0 1 0 1.06Z"
clip-rule="evenodd"
/>
</svg>
</div>
</Tooltip>
{:else}
<Tooltip
content={`${
item.info?.details?.quantization_level
? item.info?.details?.quantization_level + ' '
: ''
}${item.info.size ? `(${(item.info.size / 1024 ** 3).toFixed(1)}GB)` : ''}`}
>
<div class=" mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
/>
</svg>
</div>
</Tooltip>
{/if}
</div>
{#if value === item.value}
<div class="ml-auto">
<Check />
</div>
{/if}
</Select.Item>
{:else}
<div>
<div class="block px-3 py-2 text-sm text-gray-700 dark:text-gray-100">
No results found
</div>
</div>
{/each}
{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user.role === 'admin'}
<button
class="flex w-full font-medium line-clamp-1 select-none items-center rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 hover:bg-gray-100 dark:hover:bg-gray-850 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
on:click={() => {
pullModelHandler();
}}
>
Pull "{searchValue}" from Ollama.com
</button>
{/if}
{#each Object.keys($MODEL_DOWNLOAD_POOL) as model}
<div
class="flex w-full justify-between font-medium select-none rounded-button py-2 pl-3 pr-1.5 text-sm text-gray-700 dark:text-gray-100 outline-none transition-all duration-75 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
>
<div class="flex">
<div class="-ml-2 mr-2.5 translate-y-0.5">
<svg
class="size-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
>
</div>
<div class="flex flex-col self-start">
<div class="line-clamp-1">
Downloading "{model}" {'pullProgress' in $MODEL_DOWNLOAD_POOL[model]
? `(${$MODEL_DOWNLOAD_POOL[model].pullProgress}%)`
: ''}
</div>
{#if 'digest' in $MODEL_DOWNLOAD_POOL[model] && $MODEL_DOWNLOAD_POOL[model].digest}
<div class="-mt-1 h-fit text-[0.7rem] dark:text-gray-500 line-clamp-1">
{$MODEL_DOWNLOAD_POOL[model].digest}
</div>
{/if}
</div>
</div>
<div class="mr-2 translate-y-0.5">
<Tooltip content="Cancel">
<button
class="text-gray-800 dark:text-gray-100"
on:click={() => {
cancelModelPullHandler(model);
}}
>
<svg
class="w-4 h-4 text-gray-800 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18 17.94 6M18 18 6.06 6"
/>
</svg>
</button>
</Tooltip>
</div>
</div>
{/each}
</div>
</slot>
</Select.Content>
</Select.Root>
src/lib/components/chat/Settings/About.svelte
View file @
9763d885
...
...
@@ -4,7 +4,9 @@
import { WEBUI_VERSION } from '$lib/constants';
import { WEBUI_NAME, config, showChangelog } from '$lib/stores';
import { compareVersion } from '$lib/utils';
import { onMount } from 'svelte';
import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
let ollamaVersion = '';
...
...
@@ -43,7 +45,8 @@
<div>
<div class=" mb-2.5 text-sm font-medium flex space-x-2 items-center">
<div>
{$WEBUI_NAME} Version
{$WEBUI_NAME}
{$i18n.t('Version')}
</div>
</div>
<div class="flex w-full justify-between items-center">
...
...
@@ -56,10 +59,10 @@
target="_blank"
>
{updateAvailable === null
? 'Checking for updates...'
?
$i18n.t(
'Checking for updates...'
)
: updateAvailable
? `(v${version.latest} available!)`
: '(latest)'}
? `(v${version.latest}
${$i18n.t('
available!
')}
)`
:
$i18n.t(
'(latest)'
)
}
</a>
</div>
...
...
@@ -69,7 +72,7 @@
showChangelog.set(true);
}}
>
<div>See what's new</div>
<div>
{$i18n.t("
See what's new
")}
</div>
</button>
</div>
...
...
@@ -79,7 +82,7 @@
checkForVersionUpdates();
}}
>
Check for updates
{$i18n.t('
Check for updates
')}
</button>
</div>
</div>
...
...
@@ -88,7 +91,7 @@
<hr class=" dark:border-gray-700" />
<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-1 text-xs text-gray-700 dark:text-gray-200">
{ollamaVersion ?? 'N/A'}
...
...
@@ -123,7 +126,8 @@
</div>
<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"
href="https://github.com/tjbck"
target="_blank">Timothy J. Baek</a
...
...
src/lib/components/chat/Settings/Account.svelte
View file @
9763d885
<script lang="ts">
import { toast } from 'svelte-sonner';
import { onMount } from 'svelte';
import { onMount
, getContext
} from 'svelte';
import { user } from '$lib/stores';
import { updateUserProfile } from '$lib/apis/auths';
...
...
@@ -9,6 +9,8 @@
import { getGravatarUrl } from '$lib/apis/utils';
import { copyToClipboard } from '$lib/utils';
const i18n = getContext('i18n');
export let saveHandler: Function;
let profileImageUrl = '';
...
...
@@ -38,7 +40,7 @@
</script>
<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
id="profile-image-input"
bind:this={profileImageInputElement}
...
...
@@ -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 flex-col">
...
...
@@ -143,13 +145,13 @@
const url = await getGravatarUrl($user.email);
profileImageUrl = url;
}}>Use Gravatar</button
}}>
{$i18n.t('
Use Gravatar
')}
</button
>
</div>
<div class="flex-1">
<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">
<input
...
...
@@ -170,7 +172,7 @@
<div class=" 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 class="flex mt-2">
...
...
@@ -280,7 +282,7 @@
}
}}
>
Save
{$i18n.t('
Save
')}
</button>
</div>
</div>
src/lib/components/chat/Settings/Account/UpdatePassword.svelte
View file @
9763d885
<script lang="ts">
import { getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { updateUserPassword } from '$lib/apis/auths';
const i18n = getContext('i18n');
let show = false;
let currentPassword = '';
let newPassword = '';
...
...
@@ -17,7 +20,7 @@
);
if (res) {
toast.success('Successfully updated.');
toast.success(
$i18n.t(
'Successfully updated.')
)
;
}
currentPassword = '';
...
...
@@ -40,20 +43,20 @@
}}
>
<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
class=" text-xs font-medium text-gray-500"
type="button"
on:click={() => {
show = !show;
}}>{show ? 'Hide' : 'Show'}</button
}}>{show ?
$i18n.t(
'Hide'
)
:
$i18n.t(
'Show'
)
}</button
>
</div>
{#if show}
<div class=" py-2.5 space-y-1.5">
<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">
<input
...
...
@@ -67,7 +70,7 @@
</div>
<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">
<input
...
...
@@ -81,7 +84,7 @@
</div>
<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">
<input
...
...
@@ -99,7 +102,7 @@
<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"
>
Update password
{$i18n.t('
Update password
')}
</button>
</div>
{/if}
...
...
src/lib/components/chat/Settings/Advanced.svelte
View file @
9763d885
<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();
import AdvancedParams from './Advanced/AdvancedParams.svelte';
export let saveSettings: Function;
// Advanced
...
...
@@ -55,14 +57,14 @@
<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=" text-sm font-medium">Parameters</div>
<div class=" text-sm font-medium">
{$i18n.t('
Parameters
')}
</div>
<AdvancedParams bind:options />
<hr class=" dark:border-gray-700" />
<div class=" py-1 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -72,9 +74,9 @@
}}
>
{#if keepAlive === null}
<span class="ml-2 self-center">
Default
</span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center">
Custom
</span>
<span class="ml-2 self-center">
{$i18n.t('
Custom
')}
</span>
{/if}
</button>
</div>
...
...
@@ -84,7 +86,7 @@
<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"
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}
/>
</div>
...
...
@@ -93,7 +95,7 @@
<div>
<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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -102,7 +104,7 @@
}}
>
{#if requestFormat === ''}
<span class="ml-2 self-center"> Default </span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else if requestFormat === 'json'}
<!-- <svg
xmlns="http://www.w3.org/2000/svg"
...
...
@@ -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"
/>
</svg> -->
<span class="ml-2 self-center">
JSON
</span>
<span class="ml-2 self-center">
{$i18n.t('
JSON
')}
</span>
{/if}
</button>
</div>
...
...
@@ -147,7 +149,7 @@
dispatch('save');
}}
>
Save
{$i18n.t('
Save
')}
</button>
</div>
</div>
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
View file @
9763d885
<script lang="ts">
import { getContext } from 'svelte';
const i18n = getContext('i18n');
export let options = {
// Advanced
seed: 0,
...
...
@@ -20,7 +24,7 @@
<div class=" space-y-3 text-xs">
<div>
<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">
<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"
...
...
@@ -36,12 +40,12 @@
<div>
<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">
<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"
type="text"
placeholder=
"
Enter
S
top
S
equence
"
placeholder=
{$i18n.t('
Enter
s
top
s
equence
')}
bind:value={options.stop}
autocomplete="off"
/>
...
...
@@ -51,7 +55,7 @@
<div class=" py-0.5 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -61,9 +65,9 @@
}}
>
{#if options.temperature === ''}
<span class="ml-2 self-center"> Default </span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center"> Custom </span>
<span class="ml-2 self-center">
{$i18n.t('
Custom
')}
</span>
{/if}
</button>
</div>
...
...
@@ -97,7 +101,7 @@
<div class=" py-0.5 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -107,9 +111,9 @@
}}
>
{#if options.mirostat === ''}
<span class="ml-2 self-center">
Default
</span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center">
Custom
</span>
<span class="ml-2 self-center">
{$i18n.t('Default')}
</span>
{/if}
</button>
</div>
...
...
@@ -143,7 +147,7 @@
<div class=" py-0.5 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -153,9 +157,9 @@
}}
>
{#if options.mirostat_eta === ''}
<span class="ml-2 self-center">
Default
</span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center">
Custom
</span>
<span class="ml-2 self-center">
{$i18n.t('Default')}
</span>
{/if}
</button>
</div>
...
...
@@ -189,7 +193,7 @@
<div class=" py-0.5 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -199,9 +203,9 @@
}}
>
{#if options.mirostat_tau === ''}
<span class="ml-2 self-center">
Default
</span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center">
Custom
</span>
<span class="ml-2 self-center">
{$i18n.t('
Custom
')}
</span>
{/if}
</button>
</div>
...
...
@@ -235,7 +239,7 @@
<div class=" py-0.5 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -245,9 +249,9 @@
}}
>
{#if options.top_k === ''}
<span class="ml-2 self-center">
Default
</span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center">
Custom
</span>
<span class="ml-2 self-center">
{$i18n.t('Default')}
</span>
{/if}
</button>
</div>
...
...
@@ -281,7 +285,7 @@
<div class=" py-0.5 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -291,9 +295,9 @@
}}
>
{#if options.top_p === ''}
<span class="ml-2 self-center">
Default
</span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center">
Custom
</span>
<span class="ml-2 self-center">
{$i18n.t('Default')}
</span>
{/if}
</button>
</div>
...
...
@@ -327,7 +331,7 @@
<div class=" py-0.5 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -337,9 +341,9 @@
}}
>
{#if options.repeat_penalty === ''}
<span class="ml-2 self-center">
Default
</span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center">
Custom
</span>
<span class="ml-2 self-center">
{$i18n.t('Default')}
</span>
{/if}
</button>
</div>
...
...
@@ -373,7 +377,7 @@
<div class=" py-0.5 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -383,9 +387,9 @@
}}
>
{#if options.repeat_last_n === ''}
<span class="ml-2 self-center">
Default
</span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center">
Custom
</span>
<span class="ml-2 self-center">
{$i18n.t('Default')}
</span>
{/if}
</button>
</div>
...
...
@@ -419,7 +423,7 @@
<div class=" py-0.5 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -429,9 +433,9 @@
}}
>
{#if options.tfs_z === ''}
<span class="ml-2 self-center">
Default
</span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center">
Custom
</span>
<span class="ml-2 self-center">
{$i18n.t('Default')}
</span>
{/if}
</button>
</div>
...
...
@@ -465,7 +469,7 @@
<div class=" py-0.5 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -475,9 +479,9 @@
}}
>
{#if options.num_ctx === ''}
<span class="ml-2 self-center">
Default
</span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center">
Custom
</span>
<span class="ml-2 self-center">
{$i18n.t('Default')}
</span>
{/if}
</button>
</div>
...
...
@@ -510,7 +514,7 @@
</div>
<div class=" py-0.5 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -520,9 +524,9 @@
}}
>
{#if options.num_predict === ''}
<span class="ml-2 self-center">
Default
</span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center">
Custom
</span>
<span class="ml-2 self-center">
{$i18n.t('Default')}
</span>
{/if}
</button>
</div>
...
...
src/lib/components/chat/Settings/Audio.svelte
View file @
9763d885
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import { createEventDispatcher, onMount
, getContext
} from 'svelte';
import { toast } from 'svelte-sonner';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
export let saveSettings: Function;
// Audio
...
...
@@ -101,32 +103,36 @@
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
<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=" 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">
<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}
placeholder="Select a mode"
on:change={(e) => {
if (e.target.value !== '') {
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 = '';
});
}
}}
>
<option value="">Default (Web API)</option>
<option value="whisper-local">Whisper (Local)</option>
<option value="">
{$i18n.t('
Default (Web API)
')}
</option>
<option value="whisper-local">
{$i18n.t('
Whisper (Local)
')}
</option>
</select>
</div>
</div>
<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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -136,15 +142,17 @@
type="button"
>
{#if conversationMode === true}
<span class="ml-2 self-center">
On
</span>
<span class="ml-2 self-center">
{$i18n.t('On')}
</span>
{:else}
<span class="ml-2 self-center">
Off
</span>
<span class="ml-2 self-center">
{$i18n.t('Off')}
</span>
{/if}
</button>
</div>
<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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -154,22 +162,22 @@
type="button"
>
{#if speechAutoSend === true}
<span class="ml-2 self-center">
On
</span>
<span class="ml-2 self-center">
{$i18n.t('On')}
</span>
{:else}
<span class="ml-2 self-center">
Off
</span>
<span class="ml-2 self-center">
{$i18n.t('Off')}
</span>
{/if}
</button>
</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=" 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">
<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}
placeholder="Select a mode"
on:change={(e) => {
...
...
@@ -182,14 +190,14 @@
}
}}
>
<option value="">Default (Web API)</option>
<option value="openai">Open AI</option>
<option value="">
{$i18n.t('
Default (Web API)
')}
</option>
<option value="openai">
{$i18n.t('
Open AI
')}
</option>
</select>
</div>
</div>
<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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -199,9 +207,9 @@
type="button"
>
{#if responseAutoPlayback === true}
<span class="ml-2 self-center">
On
</span>
<span class="ml-2 self-center">
{$i18n.t('On')}
</span>
{:else}
<span class="ml-2 self-center">
Off
</span>
<span class="ml-2 self-center">
{$i18n.t('Off')}
</span>
{/if}
</button>
</div>
...
...
@@ -211,7 +219,7 @@
{#if TTSEngine === ''}
<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-1">
<select
...
...
@@ -219,7 +227,7 @@
bind:value={speaker}
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}
<option value={voice.name} class="bg-gray-100 dark:bg-gray-700">{voice.name}</option
>
...
...
@@ -230,7 +238,7 @@
</div>
{:else if TTSEngine === 'openai'}
<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-1">
<select
...
...
@@ -254,7 +262,7 @@
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit"
>
Save
{$i18n.t('
Save
')}
</button>
</div>
</form>
src/lib/components/chat/Settings/Chats.svelte
View file @
9763d885
...
...
@@ -2,7 +2,6 @@
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
import { resetVectorDB } from '$lib/apis/rag';
import { chats, user } from '$lib/stores';
import {
...
...
@@ -13,10 +12,12 @@
getChatList
} from '$lib/apis/chats';
import { getImportOrigin, convertOpenAIChats } from '$lib/utils';
import { onMount } from 'svelte';
import { onMount
, getContext
} from 'svelte';
import { goto } from '$app/navigation';
import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let saveSettings: Function;
// Chats
let saveChatHistory = true;
...
...
@@ -99,13 +100,13 @@
});
</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="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=" self-center text-sm font-medium">Chat History</div>
<div class=" self-center text-sm font-medium">
{$i18n.t('
Chat History
')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -129,7 +130,7 @@
/>
</svg>
<span class="ml-2 self-center">
On
</span>
<span class="ml-2 self-center">
{$i18n.t('On')}
</span>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
...
...
@@ -147,13 +148,13 @@
/>
</svg>
<span class="ml-2 self-center">
Off
</span>
<span class="ml-2 self-center">
{$i18n.t('Off')}
</span>
{/if}
</button>
</div>
<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>
...
...
@@ -188,7 +189,7 @@
/>
</svg>
</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
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
...
...
@@ -210,7 +211,7 @@
/>
</svg>
</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>
</div>
...
...
@@ -232,7 +233,7 @@
clip-rule="evenodd"
/>
</svg>
<span>Are you sure?</span>
<span>
{$i18n.t('
Are you sure?
')}
</span>
</div>
<div class="flex space-x-1.5 items-center">
...
...
@@ -296,7 +297,7 @@
/>
</svg>
</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>
{/if}
...
...
@@ -324,39 +325,9 @@
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">Export All Chats (All Users)</div>
</button>
<hr class=" dark:border-gray-700" />
<button
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
const res = resetVectorDB(localStorage.token).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success('Success');
}
}}
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M3.5 2A1.5 1.5 0 0 0 2 3.5v9A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 4H9.621a1.5 1.5 0 0 1-1.06-.44L7.439 2.44A1.5 1.5 0 0 0 6.38 2H3.5Zm6.75 7.75a.75.75 0 0 0 0-1.5h-4.5a.75.75 0 0 0 0 1.5h4.5Z"
clip-rule="evenodd"
/>
</svg>
<div class=" self-center text-sm font-medium">
{$i18n.t('Export All Chats (All Users)')}
</div>
<div class=" self-center text-sm font-medium">Reset Vector Storage</div>
</button>
{/if}
</div>
...
...
src/lib/components/chat/Settings/Connections.svelte
View file @
9763d885
<script lang="ts">
import { models, user } from '$lib/stores';
import { createEventDispatcher, onMount } from 'svelte';
import { createEventDispatcher, onMount
, getContext
} from 'svelte';
const dispatch = createEventDispatcher();
import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
...
...
@@ -12,6 +12,8 @@
} from '$lib/apis/openai';
import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let getModels: Function;
// External
...
...
@@ -42,7 +44,7 @@
});
if (ollamaVersion) {
toast.success('Server connection verified');
toast.success(
$i18n.t(
'Server connection verified')
)
;
await models.set(await getModels());
}
};
...
...
@@ -63,17 +65,17 @@
dispatch('save');
}}
>
<div class=" pr-1.5 overflow-y-scroll max-h-[2
0.5
rem] space-y-3">
<div class=" pr-1.5 overflow-y-scroll max-h-[2
2
rem] space-y-3">
<div class=" space-y-3">
<div class="mt-2 space-y-2 pr-1.5">
<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
class=" text-xs font-medium text-gray-500"
type="button"
on:click={() => {
showOpenAI = !showOpenAI;
}}>{showOpenAI ? 'Hide' : 'Show'}</button
}}>{showOpenAI ?
$i18n.t(
'Hide'
)
:
$i18n.t(
'Show'
)
}</button
>
</div>
...
...
@@ -84,7 +86,7 @@
<div class="flex-1">
<input
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}
autocomplete="off"
/>
...
...
@@ -93,7 +95,7 @@
<div class="flex-1">
<input
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]}
autocomplete="off"
/>
...
...
@@ -143,7 +145,8 @@
</div>
</div>
<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>
{/each}
</div>
...
...
@@ -154,7 +157,7 @@
<hr class=" dark:border-gray-700" />
<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-1 flex flex-col gap-2">
{#each OLLAMA_BASE_URLS as url, idx}
...
...
@@ -233,13 +236,13 @@
</div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Trouble accessing Ollama?
{$i18n.t('
Trouble accessing Ollama?
')}
<a
class=" text-gray-300 font-medium"
class=" text-gray-300 font-medium
underline
"
href="https://github.com/open-webui/open-webui#troubleshooting"
target="_blank"
>
Click here for help.
{$i18n.t('
Click here for help.
')}
</a>
</div>
</div>
...
...
@@ -250,7 +253,7 @@
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit"
>
Save
{$i18n.t('
Save
')}
</button>
</div>
</form>
src/lib/components/chat/Settings/General.svelte
View file @
9763d885
<script lang="ts">
import { toast } from 'svelte-sonner';
import { createEventDispatcher, onMount } from 'svelte';
import { createEventDispatcher, onMount, getContext } from 'svelte';
import { getLanguages } from '$lib/i18n';
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';
...
...
@@ -11,8 +14,11 @@
export let getModels: Function;
// General
let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light'];
let theme = 'dark';
let themes = ['dark', 'light', 'rose-pine dark', 'rose-pine-dawn light', 'oled-dark'];
let selectedTheme = 'system';
let languages = [];
let lang = $i18n.language;
let notificationEnabled = false;
let system = '';
...
...
@@ -63,9 +69,11 @@
};
onMount(async () => {
selectedTheme = localStorage.theme ?? 'system';
let settings = JSON.parse(localStorage.getItem('settings') ?? '{}');
languages = await getLanguages();
theme = localStorage.theme ?? 'dark';
notificationEnabled = settings.notificationEnabled ?? false;
system = settings.system ?? '';
...
...
@@ -81,77 +89,103 @@
options = { ...options, ...settings.options };
options.stop = (settings?.options?.stop ?? []).join(',');
});
const applyTheme = (_theme: string) => {
let themeToApply = _theme === 'oled-dark' ? 'dark' : _theme;
if (_theme === 'system') {
themeToApply = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
if (themeToApply === 'dark' && !_theme.includes('oled')) {
document.documentElement.style.setProperty('--color-gray-900', '#171717');
document.documentElement.style.setProperty('--color-gray-950', '#0d0d0d');
}
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);
if (_theme.includes('oled')) {
document.documentElement.style.setProperty('--color-gray-900', '#000000');
document.documentElement.style.setProperty('--color-gray-950', '#000000');
document.documentElement.classList.add('dark');
}
applyTheme(_theme);
};
</script>
<div class="flex flex-col h-full justify-between text-sm">
<div class=" pr-1.5 overflow-y-scroll max-h-[2
0.5
rem]">
<div class=" pr-1.5 overflow-y-scroll max-h-[2
2
rem]">
<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=" self-center text-xs font-medium">Theme</div>
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('
Theme
')}
</div>
<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
class="w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
bind:value={
t
heme}
class="
dark:bg-gray-900
w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
bind:value={
selectedT
heme}
placeholder="Select a theme"
on:change={(e) => {
localStorage.theme = theme;
themes
.filter((e) => e !== theme)
.forEach((e) => {
e.split(' ').forEach((e) => {
document.documentElement.classList.remove(e);
});
});
theme.split(' ').forEach((e) => {
document.documentElement.classList.add(e);
});
on:change={() => themeChangeHandler(selectedTheme)}
>
<option value="system">⚙️ {$i18n.t('System')}</option>
<option value="dark">🌑 {$i18n.t('Dark')}</option>
<option value="oled-dark">🌃 {$i18n.t('OLED Dark')}</option>
<option value="light">☀️ {$i18n.t('Light')}</option>
<option value="rose-pine dark">🪻 {$i18n.t('Rosé Pine')}</option>
<option value="rose-pine-dawn light">🌷 {$i18n.t('Rosé Pine Dawn')}</option>
</select>
</div>
</div>
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>
<option value="light">Light</option>
<option value="rose-pine dark">Rosé Pine</option>
<option value="rose-pine-dawn light">Rosé Pine Dawn</option>
{#each languages as language}
<option value={language['code']}>{language['title']}</option>
{/each}
</select>
</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 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
Notification
s')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -161,9 +195,9 @@
type="button"
>
{#if notificationEnabled === true}
<span class="ml-2 self-center">
On
</span>
<span class="ml-2 self-center">
{$i18n.t('On')}
</span>
{:else}
<span class="ml-2 self-center">
Off
</span>
<span class="ml-2 self-center">
{$i18n.t('Off')}
</span>
{/if}
</button>
</div>
...
...
@@ -173,7 +207,7 @@
<hr class=" dark:border-gray-700 my-3" />
<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
bind:value={system}
class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
...
...
@@ -183,13 +217,13 @@
<div class="mt-2 space-y-3 pr-1.5">
<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
class=" text-xs font-medium text-gray-500"
type="button"
on:click={() => {
showAdvanced = !showAdvanced;
}}>{showAdvanced ? 'Hide' : 'Show'}</button
}}>{showAdvanced ?
$i18n.t(
'Hide'
)
:
$i18n.t(
'Show'
)
}</button
>
</div>
...
...
@@ -199,7 +233,7 @@
<div class=" py-1 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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -209,9 +243,9 @@
}}
>
{#if keepAlive === null}
<span class="ml-2 self-center"> Default </span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else}
<span class="ml-2 self-center"> Custom </span>
<span class="ml-2 self-center">
{$i18n.t('
Custom
')}
</span>
{/if}
</button>
</div>
...
...
@@ -221,7 +255,7 @@
<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"
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}
/>
</div>
...
...
@@ -230,7 +264,7 @@
<div>
<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
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -239,7 +273,7 @@
}}
>
{#if requestFormat === ''}
<span class="ml-2 self-center"> Default </span>
<span class="ml-2 self-center">
{$i18n.t('
Default
')}
</span>
{:else if requestFormat === 'json'}
<!-- <svg
xmlns="http://www.w3.org/2000/svg"
...
...
@@ -251,7 +285,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"
/>
</svg> -->
<span class="ml-2 self-center"> JSON </span>
<span class="ml-2 self-center">
{$i18n.t('
JSON
')}
</span>
{/if}
</button>
</div>
...
...
@@ -286,7 +320,7 @@
dispatch('save');
}}
>
Save
{$i18n.t('
Save
')}
</button>
</div>
</div>
Prev
1
2
3
4
5
6
7
8
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment