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
db0712ae
Unverified
Commit
db0712ae
authored
Mar 16, 2024
by
Danny Liu
Committed by
GitHub
Mar 16, 2024
Browse files
Merge branch 'dev' into feat/system-wide-theme
parents
f1716f45
e414b9ea
Changes
90
Show whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
359 additions
and
115 deletions
+359
-115
src/lib/apis/index.ts
src/lib/apis/index.ts
+62
-0
src/lib/apis/rag/index.ts
src/lib/apis/rag/index.ts
+15
-6
src/lib/components/AddFilesPlaceholder.svelte
src/lib/components/AddFilesPlaceholder.svelte
+7
-2
src/lib/components/ChangelogModal.svelte
src/lib/components/ChangelogModal.svelte
+7
-4
src/lib/components/admin/EditUserModal.svelte
src/lib/components/admin/EditUserModal.svelte
+9
-7
src/lib/components/admin/Settings/Database.svelte
src/lib/components/admin/Settings/Database.svelte
+6
-4
src/lib/components/admin/Settings/General.svelte
src/lib/components/admin/Settings/General.svelte
+17
-14
src/lib/components/admin/Settings/Users.svelte
src/lib/components/admin/Settings/Users.svelte
+123
-6
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
+26
-18
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
+5
-3
src/lib/components/chat/Messages/ResponseMessage.svelte
src/lib/components/chat/Messages/ResponseMessage.svelte
+6
-4
src/lib/components/chat/Messages/UserMessage.svelte
src/lib/components/chat/Messages/UserMessage.svelte
+11
-8
src/lib/components/chat/ModelSelector.svelte
src/lib/components/chat/ModelSelector.svelte
+9
-5
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
+10
-8
No files found.
src/lib/apis/index.ts
View file @
db0712ae
...
...
@@ -77,3 +77,65 @@ export const getVersionUpdates = async () => {
return
res
;
};
export
const
getModelFilterConfig
=
async
(
token
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_BASE_URL
}
/api/config/model/filter`
,
{
method
:
'
GET
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
}
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
export
const
updateModelFilterConfig
=
async
(
token
:
string
,
enabled
:
boolean
,
models
:
string
[]
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_BASE_URL
}
/api/config/model/filter`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
enabled
:
enabled
,
models
:
models
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
src/lib/apis/rag/index.ts
View file @
db0712ae
import
{
RAG_API_BASE_URL
}
from
'
$lib/constants
'
;
export
const
get
ChunkParams
=
async
(
token
:
string
)
=>
{
export
const
get
RAGConfig
=
async
(
token
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
RAG_API_BASE_URL
}
/c
hunk
`
,
{
const
res
=
await
fetch
(
`
${
RAG_API_BASE_URL
}
/c
onfig
`
,
{
method
:
'
GET
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
...
...
@@ -27,18 +27,27 @@ export const getChunkParams = async (token: string) => {
return
res
;
};
export
const
updateChunkParams
=
async
(
token
:
string
,
size
:
number
,
overlap
:
number
)
=>
{
type
ChunkConfigForm
=
{
chunk_size
:
number
;
chunk_overlap
:
number
;
};
type
RAGConfigForm
=
{
pdf_extract_images
:
boolean
;
chunk
:
ChunkConfigForm
;
};
export
const
updateRAGConfig
=
async
(
token
:
string
,
payload
:
RAGConfigForm
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
RAG_API_BASE_URL
}
/c
hunk
/update`
,
{
const
res
=
await
fetch
(
`
${
RAG_API_BASE_URL
}
/c
onfig
/update`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
chunk_size
:
size
,
chunk_overlap
:
overlap
...
payload
})
})
.
then
(
async
(
res
)
=>
{
...
...
src/lib/components/AddFilesPlaceholder.svelte
View file @
db0712ae
<script>
import { getContext } from 'svelte';
const i18n = getContext('i18n');
</script>
<div class=" text-center text-6xl mb-3">📄</div>
<div class="text-center dark:text-white text-2xl font-semibold z-50">Add Files</div>
<div class="text-center dark:text-white text-2xl font-semibold z-50">
{$i18n.t('
Add Files
')}
</div>
<slot
><div class=" mt-2 text-center text-sm dark:text-gray-200 w-full">
Drop any files here to add to the conversation
{$i18n.t('
Drop any files here to add to the conversation
')}
</div>
</slot>
src/lib/components/ChangelogModal.svelte
View file @
db0712ae
<script lang="ts">
import { onMount } from 'svelte';
import { onMount
, getContext
} from 'svelte';
import { Confetti } from 'svelte-confetti';
import { WEBUI_NAME, config } from '$lib/stores';
...
...
@@ -9,6 +9,8 @@
import Modal from './common/Modal.svelte';
const i18n = getContext('i18n');
export let show = false;
let changelog = null;
...
...
@@ -23,7 +25,8 @@
<div class="px-5 py-4 dark:text-gray-300">
<div class="flex justify-between items-start">
<div class="text-xl font-bold">
What’s New in {$WEBUI_NAME}
{$i18n.t('What’s New in')}
{$WEBUI_NAME}
<Confetti x={[-1, -0.25]} y={[0, 0.5]} />
</div>
<button
...
...
@@ -45,7 +48,7 @@
</button>
</div>
<div class="flex items-center mt-1">
<div class="text-sm dark:text-gray-200">Release Notes</div>
<div class="text-sm dark:text-gray-200">
{$i18n.t('
Release Notes
')}
</div>
<div class="flex self-center w-[1px] h-6 mx-2.5 bg-gray-200 dark:bg-gray-700" />
<div class="text-sm dark:text-gray-200">
v{WEBUI_VERSION}
...
...
@@ -108,7 +111,7 @@
}}
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
>
<span class="relative">Okay, Let's Go!</span>
<span class="relative">
{$i18n.t("
Okay, Let's Go!
")}
</span>
</button>
</div>
</div>
...
...
src/lib/components/admin/EditUserModal.svelte
View file @
db0712ae
...
...
@@ -2,11 +2,12 @@
import { toast } from 'svelte-sonner';
import dayjs from 'dayjs';
import { createEventDispatcher } from 'svelte';
import { onMount } from 'svelte';
import { onMount
, getContext
} from 'svelte';
import { updateUserById } from '$lib/apis/users';
import Modal from '../common/Modal.svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let show = false;
...
...
@@ -42,7 +43,7 @@
<Modal size="sm" bind:show>
<div>
<div class=" flex justify-between dark:text-gray-300 px-5 py-4">
<div class=" text-lg font-medium self-center">Edit User</div>
<div class=" text-lg font-medium self-center">
{$i18n.t('
Edit User
')}
</div>
<button
class="self-center"
on:click={() => {
...
...
@@ -84,7 +85,8 @@
<div class=" self-center capitalize font-semibold">{selectedUser.name}</div>
<div class="text-xs text-gray-500">
Created at {dayjs(selectedUser.timestamp * 1000).format('MMMM DD, YYYY')}
{$i18n.t('Created at')}
{dayjs(selectedUser.timestamp * 1000).format($i18n.t('MMMM DD, YYYY'))}
</div>
</div>
</div>
...
...
@@ -93,7 +95,7 @@
<div class=" flex flex-col space-y-1.5">
<div class="flex flex-col w-full">
<div class=" mb-1 text-xs text-gray-500">Email</div>
<div class=" mb-1 text-xs text-gray-500">
{$i18n.t('
Email
')}
</div>
<div class="flex-1">
<input
...
...
@@ -108,7 +110,7 @@
</div>
<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
...
...
@@ -122,7 +124,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
...
...
@@ -140,7 +142,7 @@
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
type="submit"
>
Save
{$i18n.t('
Save
')}
</button>
</div>
</form>
...
...
src/lib/components/admin/Settings/Database.svelte
View file @
db0712ae
<script lang="ts">
import { downloadDatabase } from '$lib/apis/utils';
import { onMount } from 'svelte';
import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
export let saveHandler: Function;
...
...
@@ -17,10 +19,10 @@
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
<div>
<div class=" mb-2 text-sm font-medium">Database</div>
<div class=" mb-2 text-sm font-medium">
{$i18n.t('
Database
')}
</div>
<div class=" flex w-full justify-between">
<!-- <div class=" self-center text-xs font-medium">Allow Chat Deletion</div> -->
<!-- <div class=" self-center text-xs font-medium">
{$i18n.t('
Allow Chat Deletion
')}
</div> -->
<button
class=" flex rounded-md py-1.5 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
...
...
@@ -46,7 +48,7 @@
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">Download Database</div>
<div class=" self-center text-sm font-medium">
{$i18n.t('
Download Database
')}
</div>
</button>
</div>
</div>
...
...
src/lib/components/admin/Settings/General.svelte
View file @
db0712ae
...
...
@@ -7,7 +7,9 @@
updateDefaultUserRole,
updateJWTExpiresDuration
} from '$lib/apis/auths';
import { onMount } from 'svelte';
import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
export let saveHandler: Function;
let signUpEnabled = true;
...
...
@@ -43,10 +45,10 @@
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
<div>
<div class=" mb-2 text-sm font-medium">General Settings</div>
<div class=" mb-2 text-sm font-medium">
{$i18n.t('
General Settings
')}
</div>
<div class=" flex w-full justify-between">
<div class=" self-center text-xs font-medium">Enable New Sign Ups</div>
<div class=" self-center text-xs font-medium">
{$i18n.t('
Enable New Sign Ups
')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -66,7 +68,7 @@
d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
/>
</svg>
<span class="ml-2 self-center">Enabled</span>
<span class="ml-2 self-center">
{$i18n.t('
Enabled
')}
</span>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
...
...
@@ -81,25 +83,25 @@
/>
</svg>
<span class="ml-2 self-center">Disabled</span>
<span class="ml-2 self-center">
{$i18n.t('
Disabled
')}
</span>
{/if}
</button>
</div>
<div class=" flex w-full justify-between">
<div class=" self-center text-xs font-medium">Default User Role</div>
<div class=" self-center text-xs font-medium">
{$i18n.t('
Default User Role
')}
</div>
<div class="flex items-center relative">
<select
class="w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
class="
dark:bg-gray-900
w-fit pr-8 rounded py-2 px-2 text-xs bg-transparent outline-none text-right"
bind:value={defaultUserRole}
placeholder="Select a theme"
on:change={(e) => {
updateDefaultUserRoleHandler(e.target.value);
}}
>
<option value="pending">
P
ending</option>
<option value="user">
User
</option>
<option value="admin">
A
dmin</option>
<option value="pending">
{$i18n.t('p
ending
')}
</option>
<option value="user">
{$i18n.t('user')}
</option>
<option value="admin">
{$i18n.t('a
dmin
')}
</option>
</select>
</div>
</div>
...
...
@@ -108,7 +110,7 @@
<div class=" w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">JWT Expiration</div>
<div class=" self-center text-xs font-medium">
{$i18n.t('
JWT Expiration
')}
</div>
</div>
<div class="flex mt-2 space-x-2">
...
...
@@ -121,8 +123,9 @@
</div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Valid time units: <span class=" text-gray-300 font-medium"
>'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.</span
{$i18n.t('Valid time units:')}
<span class=" text-gray-300 font-medium"
>{$i18n.t("'s', 'm', 'h', 'd', 'w' or '-1' for no expiration.")}</span
>
</div>
</div>
...
...
@@ -134,7 +137,7 @@
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
type="submit"
>
Save
{$i18n.t('
Save
')}
</button>
</div>
</form>
src/lib/components/admin/Settings/Users.svelte
View file @
db0712ae
<script lang="ts">
import { getModelFilterConfig, updateModelFilterConfig } from '$lib/apis';
import { getSignUpEnabledStatus, toggleSignUpEnabledStatus } from '$lib/apis/auths';
import { getUserPermissions, updateUserPermissions } from '$lib/apis/users';
import { onMount } from 'svelte';
import { onMount, getContext } from 'svelte';
import { models } from '$lib/stores';
const i18n = getContext('i18n');
export let saveHandler: Function;
let whitelistEnabled = false;
let whitelistModels = [''];
let permissions = {
chat: {
deletion: true
...
...
@@ -13,6 +20,13 @@
onMount(async () => {
permissions = await getUserPermissions(localStorage.token);
const res = await getModelFilterConfig(localStorage.token);
if (res) {
whitelistEnabled = res.enabled;
whitelistModels = res.models.length > 0 ? res.models : [''];
}
});
</script>
...
...
@@ -21,15 +35,17 @@
on:submit|preventDefault={async () => {
// console.log('submit');
await updateUserPermissions(localStorage.token, permissions);
await updateModelFilterConfig(localStorage.token, whitelistEnabled, whitelistModels);
saveHandler();
}}
>
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
<div>
<div class=" mb-2 text-sm font-medium">User Permissions</div>
<div class=" mb-2 text-sm font-medium">
{$i18n.t('
User Permissions
')}
</div>
<div class=" flex w-full justify-between">
<div class=" self-center text-xs font-medium">Allow Chat Deletion</div>
<div class=" self-center text-xs font-medium">
{$i18n.t('
Allow Chat Deletion
')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
...
...
@@ -49,7 +65,7 @@
d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"
/>
</svg>
<span class="ml-2 self-center">Allow</span>
<span class="ml-2 self-center">
{$i18n.t('
Allow
')}
</span>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
...
...
@@ -64,11 +80,112 @@
/>
</svg>
<span class="ml-2 self-center">Don't Allow</span>
<span class="ml-2 self-center">
{$i18n.t("
Don't Allow
")}
</span>
{/if}
</button>
</div>
</div>
<hr class=" dark:border-gray-700 my-2" />
<div class="mt-2 space-y-3 pr-1.5">
<div>
<div class="mb-2">
<div class="flex justify-between items-center text-xs">
<div class=" text-sm font-medium">{$i18n.t('Manage Models')}</div>
</div>
</div>
<div class=" space-y-3">
<div>
<div class="flex justify-between items-center text-xs">
<div class=" text-xs font-medium">{$i18n.t('Model Whitelisting')}</div>
<button
class=" text-xs font-medium text-gray-500"
type="button"
on:click={() => {
whitelistEnabled = !whitelistEnabled;
}}>{whitelistEnabled ? $i18n.t('On') : $i18n.t('Off')}</button
>
</div>
</div>
{#if whitelistEnabled}
<div>
<div class=" space-y-1.5">
{#each whitelistModels as modelId, modelIdx}
<div class="flex w-full">
<div class="flex-1 mr-2">
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={modelId}
placeholder="Select a model"
>
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
{#each $models.filter((model) => model.id) as model}
<option value={model.id} class="bg-gray-100 dark:bg-gray-700"
>{model.name}</option
>
{/each}
</select>
</div>
{#if modelIdx === 0}
<button
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-900 dark:text-white rounded-lg transition"
type="button"
on:click={() => {
if (whitelistModels.at(-1) !== '') {
whitelistModels = [...whitelistModels, ''];
}
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
/>
</svg>
</button>
{:else}
<button
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-900 dark:text-white rounded-lg transition"
type="button"
on:click={() => {
whitelistModels.splice(modelIdx, 1);
whitelistModels = whitelistModels;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
</svg>
</button>
{/if}
</div>
{/each}
</div>
<div class="flex justify-end items-center text-xs mt-1.5 text-right">
<div class=" text-xs font-medium">
{whitelistModels.length}
{$i18n.t('Model(s) Whitelisted')}
</div>
</div>
</div>
{/if}
</div>
</div>
</div>
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
...
...
@@ -76,7 +193,7 @@
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
type="submit"
>
Save
{$i18n.t('
Save
')}
</button>
</div>
</form>
src/lib/components/admin/SettingsModal.svelte
View file @
db0712ae
<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 @
db0712ae
<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,12 +14,14 @@
import { transcribeAudio } from '$lib/apis/audio';
import Tooltip from '../common/Tooltip.svelte';
const i18n = getContext('i18n');
export let submitPrompt: Function;
export let stopResponse: Function;
export let suggestionPrompts = [];
export let autoScroll = true;
let chatTextAreaElement:HTMLTextAreaElement
let chatTextAreaElement:
HTMLTextAreaElement
;
let filesInputElement;
let promptsElement;
...
...
@@ -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.`)
)
;
}
}
...
...
@@ -359,12 +364,12 @@
{#if dragged}
<div
class="fixed w-full h-full flex z-50 touch-none pointer-events-none"
class="fixed
lg:w-[calc(100%-260px)]
w-full h-full flex z-50 touch-none pointer-events-none"
id="dropzone"
role="region"
aria-label="Drag and Drop Container"
>
<div class="absolute
rounded-xl
w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
<div class="absolute w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
<div class="m-auto pt-64 flex flex-col justify-center">
<div class="max-w-md">
<AddFilesPlaceholder />
...
...
@@ -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
me
ssage'}
?
$i18n.t(
'Listening...'
)
:
$i18n.t(
'Send a
Mes
ssage'
)
}
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 @
db0712ae
...
...
@@ -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(
$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 @
db0712ae
...
...
@@ -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 @
db0712ae
<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 @
db0712ae
...
...
@@ -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 @
db0712ae
<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 = [];
...
...
@@ -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 @
db0712ae
...
...
@@ -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>
...
...
src/lib/components/chat/Messages/UserMessage.svelte
View file @
db0712ae
<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>
...
...
src/lib/components/chat/ModelSelector.svelte
View file @
db0712ae
<script lang="ts">
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';
const i18n = getContext('i18n');
export let selectedModels = [''];
export let disabled = false;
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 +22,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) {
...
...
@@ -39,7 +41,9 @@
bind:value={selectedModel}
{disabled}
>
<option class=" text-gray-700" value="" selected disabled>Select a model</option>
<option class=" text-gray-700" value="" selected disabled
>{$i18n.t('Select a model')}</option
>
{#each $models as model}
{#if model.name === 'hr'}
...
...
@@ -133,5 +137,5 @@
</div>
<div class="text-left mt-1.5 text-xs text-gray-500">
<button on:click={saveDefaultModel}> Set as default</button>
<button on:click={saveDefaultModel}>
{$i18n.t('
Set as default
')}
</button>
</div>
src/lib/components/chat/Settings/About.svelte
View file @
db0712ae
...
...
@@ -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 @
db0712ae
<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">
...
...
@@ -271,7 +273,7 @@
<div class="flex justify-end pt-3 text-sm font-medium">
<button
class=" px-4 py-2 bg-emerald-
6
00 hover:bg-emerald-
7
00 text-gray-100 transition rounded"
class="
px-4 py-2 bg-emerald-
7
00 hover:bg-emerald-
8
00 text-gray-100 transition rounded
-lg
"
on:click={async () => {
const res = await submitHandler();
...
...
@@ -280,7 +282,7 @@
}
}}
>
Save
{$i18n.t('
Save
')}
</button>
</div>
</div>
Prev
1
2
3
4
5
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