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
78284e49
"vscode:/vscode.git/clone" did not exist on "8c4856cd6cb40d2c0e40d9ad70fd9e6757e28d3b"
Unverified
Commit
78284e49
authored
Apr 10, 2024
by
Timothy Jaeryang Baek
Committed by
GitHub
Apr 10, 2024
Browse files
Merge pull request #1488 from open-webui/dev
0.1.118
parents
331fe04d
64a6db4b
Changes
61
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
843 additions
and
399 deletions
+843
-399
kubernetes/helm/templates/ollama-service.yaml
kubernetes/helm/templates/ollama-service.yaml
+2
-0
kubernetes/helm/templates/ollama-statefulset.yaml
kubernetes/helm/templates/ollama-statefulset.yaml
+2
-0
kubernetes/helm/templates/webui-pvc.yaml
kubernetes/helm/templates/webui-pvc.yaml
+2
-0
kubernetes/helm/values.yaml
kubernetes/helm/values.yaml
+1
-0
package-lock.json
package-lock.json
+5
-5
package.json
package.json
+1
-1
src/lib/apis/auths/index.ts
src/lib/apis/auths/index.ts
+8
-2
src/lib/apis/rag/index.ts
src/lib/apis/rag/index.ts
+61
-0
src/lib/components/chat/MessageInput.svelte
src/lib/components/chat/MessageInput.svelte
+11
-0
src/lib/components/chat/Messages.svelte
src/lib/components/chat/Messages.svelte
+14
-12
src/lib/components/chat/Messages/RateComment.svelte
src/lib/components/chat/Messages/RateComment.svelte
+117
-0
src/lib/components/chat/Messages/ResponseMessage.svelte
src/lib/components/chat/Messages/ResponseMessage.svelte
+20
-2
src/lib/components/chat/Settings/Account.svelte
src/lib/components/chat/Settings/Account.svelte
+247
-190
src/lib/components/chat/Settings/General.svelte
src/lib/components/chat/Settings/General.svelte
+1
-1
src/lib/components/common/Modal.svelte
src/lib/components/common/Modal.svelte
+11
-0
src/lib/components/documents/Settings/General.svelte
src/lib/components/documents/Settings/General.svelte
+273
-183
src/lib/i18n/locales/bg-BG/translation.json
src/lib/i18n/locales/bg-BG/translation.json
+1
-1
src/lib/i18n/locales/ca-ES/translation.json
src/lib/i18n/locales/ca-ES/translation.json
+1
-1
src/lib/i18n/locales/de-DE/translation.json
src/lib/i18n/locales/de-DE/translation.json
+1
-1
src/lib/i18n/locales/en-GB/translation.json
src/lib/i18n/locales/en-GB/translation.json
+64
-0
No files found.
kubernetes/helm/templates/ollama-service.yaml
View file @
78284e49
{{
- if not .Values.ollama.externalHost
}}
apiVersion
:
v1
apiVersion
:
v1
kind
:
Service
kind
:
Service
metadata
:
metadata
:
...
@@ -19,3 +20,4 @@ spec:
...
@@ -19,3 +20,4 @@ spec:
port
:
{{
.port
}}
port
:
{{
.port
}}
targetPort
:
http
targetPort
:
http
{{
- end
}}
{{
- end
}}
{{
- end
}}
kubernetes/helm/templates/ollama-statefulset.yaml
View file @
78284e49
{{
- if not .Values.ollama.externalHost
}}
apiVersion
:
apps/v1
apiVersion
:
apps/v1
kind
:
StatefulSet
kind
:
StatefulSet
metadata
:
metadata
:
...
@@ -94,3 +95,4 @@ spec:
...
@@ -94,3 +95,4 @@ spec:
{{
- toYaml . | nindent 8
}}
{{
- toYaml . | nindent 8
}}
{{
- end
}}
{{
- end
}}
{{
- end
}}
{{
- end
}}
{{
- end
}}
kubernetes/helm/templates/webui-pvc.yaml
View file @
78284e49
...
@@ -17,7 +17,9 @@ spec:
...
@@ -17,7 +17,9 @@ spec:
resources
:
resources
:
requests
:
requests
:
storage
:
{{
.Values.webui.persistence.size
}}
storage
:
{{
.Values.webui.persistence.size
}}
{{
- if .Values.webui.persistence.storageClass
}}
storageClassName
:
{{
.Values.webui.persistence.storageClass
}}
storageClassName
:
{{
.Values.webui.persistence.storageClass
}}
{{
- end
}}
{{
- with .Values.webui.persistence.selector
}}
{{
- with .Values.webui.persistence.selector
}}
selector
:
selector
:
{{
- toYaml . | nindent 4
}}
{{
- toYaml . | nindent 4
}}
...
...
kubernetes/helm/values.yaml
View file @
78284e49
nameOverride
:
"
"
nameOverride
:
"
"
ollama
:
ollama
:
externalHost
:
"
"
annotations
:
{}
annotations
:
{}
podAnnotations
:
{}
podAnnotations
:
{}
replicaCount
:
1
replicaCount
:
1
...
...
package-lock.json
View file @
78284e49
{
{
"name"
:
"open-webui"
,
"name"
:
"open-webui"
,
"version"
:
"0.1.11
7
"
,
"version"
:
"0.1.11
8
"
,
"lockfileVersion"
:
3
,
"lockfileVersion"
:
3
,
"requires"
:
true
,
"requires"
:
true
,
"packages"
:
{
"packages"
:
{
""
:
{
""
:
{
"name"
:
"open-webui"
,
"name"
:
"open-webui"
,
"version"
:
"0.1.11
7
"
,
"version"
:
"0.1.11
8
"
,
"dependencies"
:
{
"dependencies"
:
{
"@sveltejs/adapter-node"
:
"^1.3.1"
,
"@sveltejs/adapter-node"
:
"^1.3.1"
,
"async"
:
"^3.2.5"
,
"async"
:
"^3.2.5"
,
...
@@ -5688,9 +5688,9 @@
...
@@ -5688,9 +5688,9 @@
}
}
},
},
"node_modules/undici"
:
{
"node_modules/undici"
:
{
"version"
:
"5.28.
3
"
,
"version"
:
"5.28.
4
"
,
"resolved"
:
"https://registry.npmjs.org/undici/-/undici-5.28.
3
.tgz"
,
"resolved"
:
"https://registry.npmjs.org/undici/-/undici-5.28.
4
.tgz"
,
"integrity"
:
"sha512-
3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA
=="
,
"integrity"
:
"sha512-
72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g
=="
,
"dependencies"
:
{
"dependencies"
:
{
"@fastify/busboy"
:
"^2.0.0"
"@fastify/busboy"
:
"^2.0.0"
},
},
...
...
package.json
View file @
78284e49
{
{
"name"
:
"open-webui"
,
"name"
:
"open-webui"
,
"version"
:
"0.1.11
7
"
,
"version"
:
"0.1.11
8
"
,
"private"
:
true
,
"private"
:
true
,
"scripts"
:
{
"scripts"
:
{
"dev"
:
"vite dev --host"
,
"dev"
:
"vite dev --host"
,
...
...
src/lib/apis/auths/index.ts
View file @
78284e49
...
@@ -58,7 +58,12 @@ export const userSignIn = async (email: string, password: string) => {
...
@@ -58,7 +58,12 @@ export const userSignIn = async (email: string, password: string) => {
return
res
;
return
res
;
};
};
export
const
userSignUp
=
async
(
name
:
string
,
email
:
string
,
password
:
string
)
=>
{
export
const
userSignUp
=
async
(
name
:
string
,
email
:
string
,
password
:
string
,
profile_image_url
:
string
)
=>
{
let
error
=
null
;
let
error
=
null
;
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/auths/signup`
,
{
const
res
=
await
fetch
(
`
${
WEBUI_API_BASE_URL
}
/auths/signup`
,
{
...
@@ -69,7 +74,8 @@ export const userSignUp = async (name: string, email: string, password: string)
...
@@ -69,7 +74,8 @@ export const userSignUp = async (name: string, email: string, password: string)
body
:
JSON
.
stringify
({
body
:
JSON
.
stringify
({
name
:
name
,
name
:
name
,
email
:
email
,
email
:
email
,
password
:
password
password
:
password
,
profile_image_url
:
profile_image_url
})
})
})
})
.
then
(
async
(
res
)
=>
{
.
then
(
async
(
res
)
=>
{
...
...
src/lib/apis/rag/index.ts
View file @
78284e49
...
@@ -345,3 +345,64 @@ export const resetVectorDB = async (token: string) => {
...
@@ -345,3 +345,64 @@ export const resetVectorDB = async (token: string) => {
return
res
;
return
res
;
};
};
export
const
getEmbeddingModel
=
async
(
token
:
string
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
RAG_API_BASE_URL
}
/embedding/model`
,
{
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
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
type
EmbeddingModelUpdateForm
=
{
embedding_model
:
string
;
};
export
const
updateEmbeddingModel
=
async
(
token
:
string
,
payload
:
EmbeddingModelUpdateForm
)
=>
{
let
error
=
null
;
const
res
=
await
fetch
(
`
${
RAG_API_BASE_URL
}
/embedding/model/update`
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
,
Authorization
:
`Bearer
${
token
}
`
},
body
:
JSON
.
stringify
({
...
payload
})
})
.
then
(
async
(
res
)
=>
{
if
(
!
res
.
ok
)
throw
await
res
.
json
();
return
res
.
json
();
})
.
catch
((
err
)
=>
{
console
.
log
(
err
);
error
=
err
.
detail
;
return
null
;
});
if
(
error
)
{
throw
error
;
}
return
res
;
};
src/lib/components/chat/MessageInput.svelte
View file @
78284e49
...
@@ -295,6 +295,13 @@
...
@@ -295,6 +295,13 @@
const dropZone = document.querySelector('body');
const dropZone = document.querySelector('body');
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
console.log('Escape');
dragged = false;
}
};
const onDragOver = (e) => {
const onDragOver = (e) => {
e.preventDefault();
e.preventDefault();
dragged = true;
dragged = true;
...
@@ -350,11 +357,15 @@
...
@@ -350,11 +357,15 @@
dragged = false;
dragged = false;
};
};
window.addEventListener('keydown', handleKeyDown);
dropZone?.addEventListener('dragover', onDragOver);
dropZone?.addEventListener('dragover', onDragOver);
dropZone?.addEventListener('drop', onDrop);
dropZone?.addEventListener('drop', onDrop);
dropZone?.addEventListener('dragleave', onDragLeave);
dropZone?.addEventListener('dragleave', onDragLeave);
return () => {
return () => {
window.removeEventListener('keydown', handleKeyDown);
dropZone?.removeEventListener('dragover', onDragOver);
dropZone?.removeEventListener('dragover', onDragOver);
dropZone?.removeEventListener('drop', onDrop);
dropZone?.removeEventListener('drop', onDrop);
dropZone?.removeEventListener('dragleave', onDragLeave);
dropZone?.removeEventListener('dragleave', onDragLeave);
...
...
src/lib/components/chat/Messages.svelte
View file @
78284e49
...
@@ -107,12 +107,8 @@
...
@@ -107,12 +107,8 @@
await sendPrompt(userPrompt, userMessageId, chatId);
await sendPrompt(userPrompt, userMessageId, chatId);
};
};
const confirmEditResponseMessage = async (messageId, content) => {
const updateChatMessages = async () => {
history.messages[messageId].originalContent = history.messages[messageId].content;
history.messages[messageId].content = content;
await tick();
await tick();
await updateChatById(localStorage.token, chatId, {
await updateChatById(localStorage.token, chatId, {
messages: messages,
messages: messages,
history: history
history: history
...
@@ -121,15 +117,20 @@
...
@@ -121,15 +117,20 @@
await chats.set(await getChatList(localStorage.token));
await chats.set(await getChatList(localStorage.token));
};
};
const confirmEditResponseMessage = async (messageId, content) => {
history.messages[messageId].originalContent = history.messages[messageId].content;
history.messages[messageId].content = content;
await updateChatMessages();
};
const rateMessage = async (messageId, rating) => {
const rateMessage = async (messageId, rating) => {
history.messages[messageId].rating = rating;
history.messages[messageId].annotation = {
await tick();
...history.messages[messageId].annotation,
await updateChatById(localStorage.token, chatId, {
rating: rating
messages: messages,
};
history: history
});
await
chats.set(await getChatList(localStorage.token)
);
await
updateChatMessages(
);
};
};
const showPreviousMessage = async (message) => {
const showPreviousMessage = async (message) => {
...
@@ -338,6 +339,7 @@
...
@@ -338,6 +339,7 @@
siblings={history.messages[message.parentId]?.childrenIds ?? []}
siblings={history.messages[message.parentId]?.childrenIds ?? []}
isLastMessage={messageIdx + 1 === messages.length}
isLastMessage={messageIdx + 1 === messages.length}
{readOnly}
{readOnly}
{updateChatMessages}
{confirmEditResponseMessage}
{confirmEditResponseMessage}
{showPreviousMessage}
{showPreviousMessage}
{showNextMessage}
{showNextMessage}
...
...
src/lib/components/chat/Messages/RateComment.svelte
0 → 100644
View file @
78284e49
<script lang="ts">
import { toast } from 'svelte-sonner';
import { createEventDispatcher, onMount } from 'svelte';
const dispatch = createEventDispatcher();
export let show = false;
export let message;
const LIKE_REASONS = [
`Accurate information`,
`Followed instructions perfectly`,
`Showcased creativity`,
`Positive attitude`,
`Attention to detail`,
`Thorough explanation`,
`Other`
];
const DISLIKE_REASONS = [
`Don't like the style`,
`Not factually correct`,
`Didn't fully follow instructions`,
`Refused when it shouldn't have`,
`Being Lazy`,
`Other`
];
let reasons = [];
let selectedReason = null;
let comment = '';
$: if (message.annotation.rating === 1) {
reasons = LIKE_REASONS;
} else if (message.annotation.rating === -1) {
reasons = DISLIKE_REASONS;
}
onMount(() => {
selectedReason = message.annotation.reason;
comment = message.annotation.comment;
});
const submitHandler = () => {
console.log('submitHandler');
message.annotation.reason = selectedReason;
message.annotation.comment = comment;
dispatch('submit');
toast.success('Thanks for your feedback!');
show = false;
};
</script>
<div class=" my-2.5 rounded-xl px-4 py-3 border dark:border-gray-850">
<div class="flex justify-between items-center">
<div class=" text-sm">Tell us more:</div>
<button
on:click={() => {
show = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="size-4"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
</button>
</div>
{#if reasons.length > 0}
<div class="flex flex-wrap gap-2 text-sm mt-2.5">
{#each reasons as reason}
<button
class="px-3.5 py-1 border dark:border-gray-850 dark:hover:bg-gray-850 {selectedReason ===
reason
? 'dark:bg-gray-800'
: ''} transition rounded-lg"
on:click={() => {
selectedReason = reason;
}}
>
{reason}
</button>
{/each}
</div>
{/if}
<div class="mt-2">
<textarea
bind:value={comment}
class="w-full text-sm px-1 py-2 bg-transparent outline-none resize-none rounded-xl"
placeholder="Feel free to add specific details"
rows="2"
/>
</div>
<div class="mt-2 flex justify-end">
<button
class=" bg-emerald-700 text-white text-sm font-medium rounded-lg px-3.5 py-1.5"
on:click={() => {
submitHandler();
}}
>
Submit
</button>
</div>
</div>
src/lib/components/chat/Messages/ResponseMessage.svelte
View file @
78284e49
...
@@ -30,6 +30,7 @@
...
@@ -30,6 +30,7 @@
import Image from '$lib/components/common/Image.svelte';
import Image from '$lib/components/common/Image.svelte';
import { WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_BASE_URL } from '$lib/constants';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import RateComment from './RateComment.svelte';
export let modelfiles = [];
export let modelfiles = [];
export let message;
export let message;
...
@@ -39,6 +40,7 @@
...
@@ -39,6 +40,7 @@
export let readOnly = false;
export let readOnly = false;
export let updateChatMessages: Function;
export let confirmEditResponseMessage: Function;
export let confirmEditResponseMessage: Function;
export let showPreviousMessage: Function;
export let showPreviousMessage: Function;
export let showNextMessage: Function;
export let showNextMessage: Function;
...
@@ -60,6 +62,8 @@
...
@@ -60,6 +62,8 @@
let loadingSpeech = false;
let loadingSpeech = false;
let generatingImage = false;
let generatingImage = false;
let showRateComment = false;
$: tokens = marked.lexer(sanitizeResponseContent(message.content));
$: tokens = marked.lexer(sanitizeResponseContent(message.content));
const renderer = new marked.Renderer();
const renderer = new marked.Renderer();
...
@@ -536,11 +540,13 @@
...
@@ -536,11 +540,13 @@
<button
<button
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded {message.rating === 1
: 'invisible group-hover:visible'} p-1 rounded {message?.annotation
?.rating === 1
? 'bg-gray-100 dark:bg-gray-800'
? 'bg-gray-100 dark:bg-gray-800'
: ''} dark:hover:text-white hover:text-black transition"
: ''} dark:hover:text-white hover:text-black transition"
on:click={() => {
on:click={() => {
rateMessage(message.id, 1);
rateMessage(message.id, 1);
showRateComment = true;
}}
}}
>
>
<svg
<svg
...
@@ -563,11 +569,13 @@
...
@@ -563,11 +569,13 @@
<button
<button
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded {message.rating === -1
: 'invisible group-hover:visible'} p-1 rounded {message?.annotation
?.rating === -1
? 'bg-gray-100 dark:bg-gray-800'
? 'bg-gray-100 dark:bg-gray-800'
: ''} dark:hover:text-white hover:text-black transition"
: ''} dark:hover:text-white hover:text-black transition"
on:click={() => {
on:click={() => {
rateMessage(message.id, -1);
rateMessage(message.id, -1);
showRateComment = true;
}}
}}
>
>
<svg
<svg
...
@@ -824,6 +832,16 @@
...
@@ -824,6 +832,16 @@
{/if}
{/if}
</div>
</div>
{/if}
{/if}
{#if showRateComment}
<RateComment
bind:show={showRateComment}
bind:message
on:submit={() => {
updateChatMessages();
}}
/>
{/if}
</div>
</div>
{/if}
{/if}
</div>
</div>
...
...
src/lib/components/chat/Settings/Account.svelte
View file @
78284e49
...
@@ -7,6 +7,7 @@
...
@@ -7,6 +7,7 @@
import UpdatePassword from './Account/UpdatePassword.svelte';
import UpdatePassword from './Account/UpdatePassword.svelte';
import { getGravatarUrl } from '$lib/apis/utils';
import { getGravatarUrl } from '$lib/apis/utils';
import { generateInitialsImage, canvasPixelTest } from '$lib/utils';
import { copyToClipboard } from '$lib/utils';
import { copyToClipboard } from '$lib/utils';
import Plus from '$lib/components/icons/Plus.svelte';
import Plus from '$lib/components/icons/Plus.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
...
@@ -18,6 +19,8 @@
...
@@ -18,6 +19,8 @@
let profileImageUrl = '';
let profileImageUrl = '';
let name = '';
let name = '';
let showAPIKeys = false;
let showJWTToken = false;
let showJWTToken = false;
let JWTTokenCopied = false;
let JWTTokenCopied = false;
...
@@ -28,6 +31,12 @@
...
@@ -28,6 +31,12 @@
let profileImageInputElement: HTMLInputElement;
let profileImageInputElement: HTMLInputElement;
const submitHandler = async () => {
const submitHandler = async () => {
if (name !== $user.name) {
if (profileImageUrl === generateInitialsImage($user.name) || profileImageUrl === '') {
profileImageUrl = generateInitialsImage(name);
}
}
const updatedUser = await updateUserProfile(localStorage.token, name, profileImageUrl).catch(
const updatedUser = await updateUserProfile(localStorage.token, name, profileImageUrl).catch(
(error) => {
(error) => {
toast.error(error);
toast.error(error);
...
@@ -125,59 +134,93 @@
...
@@ -125,59 +134,93 @@
}}
}}
/>
/>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Profile')}</div>
<div class="space-y-1">
<!-- <div class=" text-sm font-medium">{$i18n.t('Account')}</div> -->
<div class="flex space-x-5">
<div class="flex flex-col">
<div class="self-center">
<button
class="relative rounded-full dark:bg-gray-700"
type="button"
on:click={() => {
profileImageInputElement.click();
}}
>
<img
src={profileImageUrl !== '' ? profileImageUrl : '/user.png'}
alt="profile"
class=" rounded-full w-16 h-16 object-cover"
/>
<div
<div class="flex space-x-5">
class="absolute flex justify-center rounded-full bottom-0 left-0 right-0 top-0 h-full w-full overflow-hidden bg-gray-700 bg-fixed opacity-0 transition duration-300 ease-in-out hover:opacity-50"
<div class="flex flex-col">
<div class="self-center mt-2">
<button
class="relative rounded-full dark:bg-gray-700"
type="button"
on:click={() => {
profileImageInputElement.click();
}}
>
>
<div class="my-auto text-gray-100">
<img
<svg
src={profileImageUrl !== '' ? profileImageUrl : generateInitialsImage(name)}
xmlns="http://www.w3.org/2000/svg"
alt="profile"
viewBox="0 0 20 20"
class=" rounded-full size-16 object-cover"
fill="currentColor"
/>
class="w-5 h-5"
>
<div
<path
class="absolute flex justify-center rounded-full bottom-0 left-0 right-0 top-0 h-full w-full overflow-hidden bg-gray-700 bg-fixed opacity-0 transition duration-300 ease-in-out hover:opacity-50"
d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
>
/>
<div class="my-auto text-gray-100">
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
<path
d="m2.695 14.762-1.262 3.155a.5.5 0 0 0 .65.65l3.155-1.262a4 4 0 0 0 1.343-.886L17.5 5.501a2.121 2.121 0 0 0-3-3L3.58 13.419a4 4 0 0 0-.885 1.343Z"
/>
</svg>
</div>
</div>
</div>
</div>
</button>
</button>
</div>
</div>
<div class="flex-1 flex flex-col self-center gap-0.5">
<div class=" mb-0.5 text-sm font-medium">{$i18n.t('Profile Image')}</div>
<div>
<button
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850"
on:click={async () => {
if (canvasPixelTest()) {
profileImageUrl = generateInitialsImage(name);
} else {
toast.info(
$i18n.t(
'Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.'
),
{
duration: 1000 * 10
}
);
}
}}>{$i18n.t('Use Initials')}</button
>
<button
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850"
on:click={async () => {
const url = await getGravatarUrl($user.email);
profileImageUrl = url;
}}>{$i18n.t('Use Gravatar')}</button
>
<button
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-lg px-2 py-1"
on:click={async () => {
profileImageUrl = '/user.png';
}}>{$i18n.t('Remove')}</button
>
</div>
</div>
</div>
<button
class=" text-xs text-gray-600"
on:click={async () => {
const url = await getGravatarUrl($user.email);
profileImageUrl = url;
}}>{$i18n.t('Use Gravatar')}</button
>
</div>
</div>
<div class="
flex-1
">
<div class="
pt-0.5
">
<div class="flex flex-col w-full">
<div class="flex flex-col w-full">
<div class=" mb-1 text-xs
text-gray-500
">{$i18n.t('Name')}</div>
<div class=" mb-1 text-xs
font-medium
">{$i18n.t('Name')}</div>
<div class="flex-1">
<div class="flex-1">
<input
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-8
0
0 outline-none"
class="w-full rounded
-lg
py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-8
5
0 outline-none"
type="text"
type="text"
bind:value={name}
bind:value={name}
required
required
...
@@ -187,133 +230,46 @@
...
@@ -187,133 +230,46 @@
</div>
</div>
</div>
</div>
<hr class=" dark:border-gray-700 my-4" />
<div class="py-0.5">
<UpdatePassword />
<UpdatePassword />
</div>
<hr class=" dark:border-gray-700 my-4" />
<hr class=" dark:border-gray-700 my-4" />
<div class="flex flex-col gap-4">
<div class="flex justify-between items-center text-sm">
<div class="justify-between w-full">
<div class=" font-medium">{$i18n.t('API keys')}</div>
<div class="flex justify-between w-full">
<button
<div class="self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
class=" text-xs font-medium text-gray-500"
</div>
type="button"
on:click={() => {
<div class="flex mt-2">
showAPIKeys = !showAPIKeys;
<div class="flex w-full">
}}>{showAPIKeys ? $i18n.t('Hide') : $i18n.t('Show')}</button
<input
>
class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-800 outline-none"
</div>
type={showJWTToken ? 'text' : 'password'}
value={localStorage.token}
disabled
/>
<button
{#if showAPIKeys}
class="px-2 transition rounded-r-lg bg-white dark:bg-gray-800"
<div class="flex flex-col gap-4">
on:click={() => {
<div class="justify-between w-full">
showJWTToken = !showJWTToken;
<div class="flex justify-between w-full">
}}
<div class="self-center text-xs font-medium">{$i18n.t('JWT Token')}</div>
>
{#if showJWTToken}
<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.28 2.22a.75.75 0 0 0-1.06 1.06l10.5 10.5a.75.75 0 1 0 1.06-1.06l-1.322-1.323a7.012 7.012 0 0 0 2.16-3.11.87.87 0 0 0 0-.567A7.003 7.003 0 0 0 4.82 3.76l-1.54-1.54Zm3.196 3.195 1.135 1.136A1.502 1.502 0 0 1 9.45 8.389l1.136 1.135a3 3 0 0 0-4.109-4.109Z"
clip-rule="evenodd"
/>
<path
d="m7.812 10.994 1.816 1.816A7.003 7.003 0 0 1 1.38 8.28a.87.87 0 0 1 0-.566 6.985 6.985 0 0 1 1.113-2.039l2.513 2.513a3 3 0 0 0 2.806 2.806Z"
/>
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" />
<path
fill-rule="evenodd"
d="M1.38 8.28a.87.87 0 0 1 0-.566 7.003 7.003 0 0 1 13.238.006.87.87 0 0 1 0 .566A7.003 7.003 0 0 1 1.379 8.28ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
clip-rule="evenodd"
/>
</svg>
{/if}
</button>
</div>
</div>
<button
<div class="flex mt-2">
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-800 transition rounded-lg"
on:click={() => {
copyToClipboard(localStorage.token);
JWTTokenCopied = true;
setTimeout(() => {
JWTTokenCopied = false;
}, 2000);
}}
>
{#if JWTTokenCopied}
<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="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clip-rule="evenodd"
/>
</svg>
{:else}
<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="M11.986 3H12a2 2 0 0 1 2 2v6a2 2 0 0 1-1.5 1.937V7A2.5 2.5 0 0 0 10 4.5H4.063A2 2 0 0 1 6 3h.014A2.25 2.25 0 0 1 8.25 1h1.5a2.25 2.25 0 0 1 2.236 2ZM10.5 4v-.75a.75.75 0 0 0-.75-.75h-1.5a.75.75 0 0 0-.75.75V4h3Z"
clip-rule="evenodd"
/>
<path
fill-rule="evenodd"
d="M3 6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H3Zm1.75 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5ZM4 11.75a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1-.75-.75Z"
clip-rule="evenodd"
/>
</svg>
{/if}
</button>
</div>
</div>
<div class="justify-between w-full">
<div class="flex justify-between w-full">
<div class="self-center text-xs font-medium">{$i18n.t('API Key')}</div>
</div>
<div class="flex mt-2">
{#if APIKey}
<div class="flex w-full">
<div class="flex w-full">
<input
<input
class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-8
0
0 outline-none"
class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-8
5
0 outline-none"
type={show
APIKey
? 'text' : 'password'}
type={show
JWTToken
? 'text' : 'password'}
value={
APIKey
}
value={
localStorage.token
}
disabled
disabled
/>
/>
<button
<button
class="px-2 transition rounded-r-lg bg-white dark:bg-gray-8
0
0"
class="px-2 transition rounded-r-lg bg-white dark:bg-gray-8
5
0"
on:click={() => {
on:click={() => {
show
APIKey = !showAPIKey
;
show
JWTToken = !showJWTToken
;
}}
}}
>
>
{#if show
APIKey
}
{#if show
JWTToken
}
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
viewBox="0 0 16 16"
...
@@ -348,16 +304,16 @@
...
@@ -348,16 +304,16 @@
</div>
</div>
<button
<button
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-8
0
0 transition rounded-lg"
class="ml-1.5 px-1.5 py-1 dark:hover:bg-gray-8
5
0 transition rounded-lg"
on:click={() => {
on:click={() => {
copyToClipboard(
APIKey
);
copyToClipboard(
localStorage.token
);
APIKey
Copied = true;
JWTToken
Copied = true;
setTimeout(() => {
setTimeout(() => {
APIKey
Copied = false;
JWTToken
Copied = false;
}, 2000);
}, 2000);
}}
}}
>
>
{#if
APIKey
Copied}
{#if
JWTToken
Copied}
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
viewBox="0 0 20 20"
...
@@ -390,45 +346,146 @@
...
@@ -390,45 +346,146 @@
</svg>
</svg>
{/if}
{/if}
</button>
</button>
</div>
</div>
<div class="justify-between w-full">
<div class="flex justify-between w-full">
<div class="self-center text-xs font-medium">{$i18n.t('API Key')}</div>
</div>
<div class="flex mt-2">
{#if APIKey}
<div class="flex w-full">
<input
class="w-full rounded-l-lg py-1.5 pl-4 text-sm bg-white dark:text-gray-300 dark:bg-gray-850 outline-none"
type={showAPIKey ? 'text' : 'password'}
value={APIKey}
disabled
/>
<button
class="px-2 transition rounded-r-lg bg-white dark:bg-gray-850"
on:click={() => {
showAPIKey = !showAPIKey;
}}
>
{#if showAPIKey}
<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.28 2.22a.75.75 0 0 0-1.06 1.06l10.5 10.5a.75.75 0 1 0 1.06-1.06l-1.322-1.323a7.012 7.012 0 0 0 2.16-3.11.87.87 0 0 0 0-.567A7.003 7.003 0 0 0 4.82 3.76l-1.54-1.54Zm3.196 3.195 1.135 1.136A1.502 1.502 0 0 1 9.45 8.389l1.136 1.135a3 3 0 0 0-4.109-4.109Z"
clip-rule="evenodd"
/>
<path
d="m7.812 10.994 1.816 1.816A7.003 7.003 0 0 1 1.38 8.28a.87.87 0 0 1 0-.566 6.985 6.985 0 0 1 1.113-2.039l2.513 2.513a3 3 0 0 0 2.806 2.806Z"
/>
</svg>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" />
<path
fill-rule="evenodd"
d="M1.38 8.28a.87.87 0 0 1 0-.566 7.003 7.003 0 0 1 13.238.006.87.87 0 0 1 0 .566A7.003 7.003 0 0 1 1.379 8.28ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
clip-rule="evenodd"
/>
</svg>
{/if}
</button>
</div>
<Tooltip content="Create new key">
<button
<button
class=" px-1.5 py-1 dark:hover:bg-gray-8
00
transition rounded-lg"
class="
ml-1.5
px-1.5 py-1 dark:hover:bg-gray-8
50
transition rounded-lg"
on:click={() => {
on:click={() => {
createAPIKeyHandler();
copyToClipboard(APIKey);
APIKeyCopied = true;
setTimeout(() => {
APIKeyCopied = false;
}, 2000);
}}
}}
>
>
<svg
{#if APIKeyCopied}
xmlns="http://www.w3.org/2000/svg"
<svg
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
viewBox="0 0 20 20"
stroke-width="2"
fill="currentColor"
stroke="currentColor"
class="w-4 h-4"
class="size-4"
>
>
<path
<path
fill-rule="evenodd"
stroke-linecap="round"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
stroke-linejoin="round"
clip-rule="evenodd"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
/>
/>
</svg>
</svg>
{:else}
<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="M11.986 3H12a2 2 0 0 1 2 2v6a2 2 0 0 1-1.5 1.937V7A2.5 2.5 0 0 0 10 4.5H4.063A2 2 0 0 1 6 3h.014A2.25 2.25 0 0 1 8.25 1h1.5a2.25 2.25 0 0 1 2.236 2ZM10.5 4v-.75a.75.75 0 0 0-.75-.75h-1.5a.75.75 0 0 0-.75.75V4h3Z"
clip-rule="evenodd"
/>
<path
fill-rule="evenodd"
d="M3 6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H3Zm1.75 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5ZM4 11.75a.75.75 0 0 1 .75-.75h3.5a.75.75 0 0 1 0 1.5h-3.5a.75.75 0 0 1-.75-.75Z"
clip-rule="evenodd"
/>
</svg>
{/if}
</button>
</button>
</Tooltip>
{:else}
<button
class="flex gap-1.5 items-center font-medium px-3.5 py-1.5 rounded-lg bg-gray-100/70 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition"
on:click={() => {
createAPIKeyHandler();
}}
>
<Plus strokeWidth="2" className=" size-3.5" />
Create new secret key</button
<Tooltip content="Create new key">
>
<button
{/if}
class=" px-1.5 py-1 dark:hover:bg-gray-850transition rounded-lg"
on:click={() => {
createAPIKeyHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
class="size-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
</button>
</Tooltip>
{:else}
<button
class="flex gap-1.5 items-center font-medium px-3.5 py-1.5 rounded-lg bg-gray-100/70 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-850 transition"
on:click={() => {
createAPIKeyHandler();
}}
>
<Plus strokeWidth="2" className=" size-3.5" />
Create new secret key</button
>
{/if}
</div>
</div>
</div>
</div>
</div>
</div>
{/if}
</div>
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
<div class="flex justify-end pt-3 text-sm font-medium">
...
...
src/lib/components/chat/Settings/General.svelte
View file @
78284e49
...
@@ -185,7 +185,7 @@
...
@@ -185,7 +185,7 @@
<div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('
Desktop
Notifications')}</div>
<div class=" self-center text-xs font-medium">{$i18n.t('Notifications')}</div>
<button
<button
class="p-1 px-3 text-xs flex rounded transition"
class="p-1 px-3 text-xs flex rounded transition"
...
...
src/lib/components/common/Modal.svelte
View file @
78284e49
...
@@ -7,6 +7,7 @@
...
@@ -7,6 +7,7 @@
export let show = true;
export let show = true;
export let size = 'md';
export let size = 'md';
let modalElement = null;
let mounted = false;
let mounted = false;
const sizeToWidth = (size) => {
const sizeToWidth = (size) => {
...
@@ -19,14 +20,23 @@
...
@@ -19,14 +20,23 @@
}
}
};
};
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
console.log('Escape');
show = false;
}
};
onMount(() => {
onMount(() => {
mounted = true;
mounted = true;
});
});
$: if (mounted) {
$: if (mounted) {
if (show) {
if (show) {
window.addEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'hidden';
document.body.style.overflow = 'hidden';
} else {
} else {
window.removeEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'unset';
document.body.style.overflow = 'unset';
}
}
}
}
...
@@ -36,6 +46,7 @@
...
@@ -36,6 +46,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
<div
bind:this={modalElement}
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-50 overflow-hidden overscroll-contain"
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-50 overflow-hidden overscroll-contain"
in:fade={{ duration: 10 }}
in:fade={{ duration: 10 }}
on:click={() => {
on:click={() => {
...
...
src/lib/components/documents/Settings/General.svelte
View file @
78284e49
...
@@ -6,18 +6,23 @@
...
@@ -6,18 +6,23 @@
getQuerySettings,
getQuerySettings,
scanDocs,
scanDocs,
updateQuerySettings,
updateQuerySettings,
resetVectorDB
resetVectorDB,
getEmbeddingModel,
updateEmbeddingModel
} from '$lib/apis/rag';
} from '$lib/apis/rag';
import { documents } from '$lib/stores';
import { documents } from '$lib/stores';
import { onMount, getContext } from 'svelte';
import { onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
export let saveHandler: Function;
export let saveHandler: Function;
let loading = false;
let scanDirLoading = false;
let updateEmbeddingModelLoading = false;
let showResetConfirm = false;
let showResetConfirm = false;
...
@@ -30,10 +35,12 @@
...
@@ -30,10 +35,12 @@
k: 4
k: 4
};
};
let embeddingModel = '';
const scanHandler = async () => {
const scanHandler = async () => {
l
oading = true;
scanDirL
oading = true;
const res = await scanDocs(localStorage.token);
const res = await scanDocs(localStorage.token);
l
oading = false;
scanDirL
oading = false;
if (res) {
if (res) {
await documents.set(await getDocs(localStorage.token));
await documents.set(await getDocs(localStorage.token));
...
@@ -41,6 +48,38 @@
...
@@ -41,6 +48,38 @@
}
}
};
};
const embeddingModelUpdateHandler = async () => {
if (embeddingModel.split('/').length - 1 > 1) {
toast.error(
$i18n.t(
'Model filesystem path detected. Model shortname is required for update, cannot continue.'
)
);
return;
}
console.log('Update embedding model attempt:', embeddingModel);
updateEmbeddingModelLoading = true;
const res = await updateEmbeddingModel(localStorage.token, {
embedding_model: embeddingModel
}).catch(async (error) => {
toast.error(error);
embeddingModel = (await getEmbeddingModel(localStorage.token)).embedding_model;
return null;
});
updateEmbeddingModelLoading = false;
if (res) {
console.log('embeddingModelUpdateHandler:', res);
if (res.status === true) {
toast.success($i18n.t('Model {{embedding_model}} update complete!', res), {
duration: 1000 * 10
});
}
}
};
const submitHandler = async () => {
const submitHandler = async () => {
const res = await updateRAGConfig(localStorage.token, {
const res = await updateRAGConfig(localStorage.token, {
pdf_extract_images: pdfExtractImages,
pdf_extract_images: pdfExtractImages,
...
@@ -62,6 +101,8 @@
...
@@ -62,6 +101,8 @@
chunkOverlap = res.chunk.chunk_overlap;
chunkOverlap = res.chunk.chunk_overlap;
}
}
embeddingModel = (await getEmbeddingModel(localStorage.token)).embedding_model;
querySettings = await getQuerySettings(localStorage.token);
querySettings = await getQuerySettings(localStorage.token);
});
});
</script>
</script>
...
@@ -73,7 +114,7 @@
...
@@ -73,7 +114,7 @@
saveHandler();
saveHandler();
}}
}}
>
>
<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]
">
<div>
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('General Settings')}</div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('General Settings')}</div>
...
@@ -83,7 +124,7 @@
...
@@ -83,7 +124,7 @@
</div>
</div>
<button
<button
class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded flex flex-row space-x-1 items-center {
l
oading
class=" self-center text-xs p-1 px-3 bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 rounded
-lg
flex flex-row space-x-1 items-center {
scanDirL
oading
? ' cursor-not-allowed'
? ' cursor-not-allowed'
: ''}"
: ''}"
on:click={() => {
on:click={() => {
...
@@ -91,24 +132,11 @@
...
@@ -91,24 +132,11 @@
console.log('check');
console.log('check');
}}
}}
type="button"
type="button"
disabled={
l
oading}
disabled={
scanDirL
oading}
>
>
<div class="self-center font-medium">{$i18n.t('Scan')}</div>
<div class="self-center font-medium">{$i18n.t('Scan')}</div>
<!-- <svg
{#if scanDirLoading}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-3 h-3"
>
<path
fill-rule="evenodd"
d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
clip-rule="evenodd"
/>
</svg> -->
{#if loading}
<div class="ml-3 self-center">
<div class="ml-3 self-center">
<svg
<svg
class=" w-3 h-3"
class=" w-3 h-3"
...
@@ -141,196 +169,258 @@
...
@@ -141,196 +169,258 @@
<hr class=" dark:border-gray-700" />
<hr class=" dark:border-gray-700" />
<div class=" ">
<div class="space-y-2">
<div class=" text-sm font-medium">{$i18n.t('Chunk Params')}</div>
<div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Update Embedding Model')}</div>
<div class=" flex">
<div class=" flex w-full justify-between">
<div class="self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Size')}</div>
<div class="self-center p-3">
<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="number"
placeholder={$i18n.t('Enter Chunk Size')}
bind:value={chunkSize}
autocomplete="off"
min="0"
/>
</div>
</div>
<div class="flex w-full">
<div class="flex w-full">
<div class=" self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Overlap')}</div>
<div class="flex-1 mr-2">
<div class="self-center p-3">
<input
<input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="number"
placeholder={$i18n.t('Update embedding model (e.g. {{model}})', {
placeholder={$i18n.t('Enter Chunk Overlap')}
model: embeddingModel.slice(-40)
bind:value={chunkOverlap}
})}
autocomplete="off"
bind:value={embeddingModel}
min="0"
/>
/>
</div>
</div>
</div>
</div>
<div>
<div class="flex justify-between items-center text-xs">
<div class=" text-xs font-medium">{$i18n.t('PDF Extract Images (OCR)')}</div>
<button
<button
class=" text-xs font-medium text-gray-500"
class="px-2.5 bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded-lg transition"
type="button"
on:click={() => {
on:click={() => {
pdfExtractImages = !pdfExtractImages;
embeddingModelUpdateHandler();
}}>{pdfExtractImages ? $i18n.t('On') : $i18n.t('Off')}</button
}}
disabled={updateEmbeddingModelLoading}
>
>
{#if updateEmbeddingModelLoading}
<div class="self-center">
<svg
class=" w-4 h-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>
{:else}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 2.75a.75.75 0 0 0-1.5 0v5.69L5.03 6.22a.75.75 0 0 0-1.06 1.06l3.5 3.5a.75.75 0 0 0 1.06 0l3.5-3.5a.75.75 0 0 0-1.06-1.06L8.75 8.44V2.75Z"
/>
<path
d="M3.5 9.75a.75.75 0 0 0-1.5 0v1.5A2.75 2.75 0 0 0 4.75 14h6.5A2.75 2.75 0 0 0 14 11.25v-1.5a.75.75 0 0 0-1.5 0v1.5c0 .69-.56 1.25-1.25 1.25h-6.5c-.69 0-1.25-.56-1.25-1.25v-1.5Z"
/>
</svg>
{/if}
</button>
</div>
</div>
</div>
</div>
<div>
<div class="mt-2 mb-1 text-xs text-gray-400 dark:text-gray-500">
<div class=" text-sm font-medium">{$i18n.t('Query Params')}</div>
{$i18n.t(
'Warning: If you update or change your embedding model, you will need to re-import all documents.'
)}
</div>
<div class=" flex">
<hr class=" dark:border-gray-700 my-3" />
<div class=" flex w-full justify-between">
<div class="self-center text-xs font-medium flex-1">{$i18n.t('Top K')}</div>
<div class=" ">
<div class=" text-sm font-medium">{$i18n.t('Chunk Params')}</div>
<div class=" flex">
<div class=" flex w-full justify-between">
<div class="self-center text-xs font-medium min-w-fit">{$i18n.t('Chunk Size')}</div>
<div class="self-center p-3">
<input
class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="number"
placeholder={$i18n.t('Enter Chunk Size')}
bind:value={chunkSize}
autocomplete="off"
min="0"
/>
</div>
</div>
<div class="self-center p-3">
<div class="flex w-full">
<input
<div class=" self-center text-xs font-medium min-w-fit">
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"
{$i18n.t('Chunk Overlap')}
type="number"
</div>
placeholder={$i18n.t('Enter Top K')}
bind:value={querySettings.k}
<div class="self-center p-3">
autocomplete="off"
<input
min="0"
class="w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
/>
type="number"
placeholder={$i18n.t('Enter Chunk Overlap')}
bind:value={chunkOverlap}
autocomplete="off"
min="0"
/>
</div>
</div>
</div>
</div>
</div>
<!-- <div class="flex w-full">
<div class="pr-2">
<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
<div class="flex justify-between items-center text-xs">
<div class=" text-xs font-medium">{$i18n.t('PDF Extract Images (OCR)')}</div>
<div class="self-center p-3">
<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="number"
placeholder="Enter Chunk Overlap"
bind:value={chunkOverlap}
autocomplete="off"
min="0"
/>
</div>
</div> -->
</div>
<div>
<button
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('RAG Template')}</div>
class=" text-xs font-medium text-gray-500"
<textarea
type="button"
bind:value={querySettings.template}
on:click={() => {
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
pdfExtractImages = !pdfExtractImages;
rows="4"
}}>{pdfExtractImages ? $i18n.t('On') : $i18n.t('Off')}</button
/>
>
</div>
</div>
</div>
</div>
</div>
<hr class=" dark:border-gray-700" />
<hr class=" dark:border-gray-700 my-3" />
<div>
<div class=" text-sm font-medium">{$i18n.t('Query Params')}</div>
<div class=" flex">
<div class=" flex w-full justify-between">
<div class="self-center text-xs font-medium flex-1">{$i18n.t('Top K')}</div>
<div class="self-center p-3">
<input
class=" w-full rounded-lg py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="number"
placeholder={$i18n.t('Enter Top K')}
bind:value={querySettings.k}
autocomplete="off"
min="0"
/>
</div>
</div>
</div>
{#if showResetConfirm}
<div>
<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('RAG Template')}</div>
<div class="flex items-center space-x-3">
<textarea
<svg
bind:value={querySettings.template}
xmlns="http://www.w3.org/2000/svg"
class="w-full rounded-lg px-4 py-3 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
viewBox="0 0 16 16"
rows="4"
fill="currentColor"
class="w-4 h-4"
>
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
<path
fill-rule="evenodd"
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
/>
</svg>
</div>
<span>{$i18n.t('Are you sure?')}</span>
</div>
</div>
<div class="flex space-x-1.5 items-center">
<hr class=" dark:border-gray-700 my-3" />
<button
class="hover:text-white transition"
{#if showResetConfirm}
on:click={() => {
<div class="flex justify-between rounded-md items-center py-2 px-3.5 w-full transition">
const res = resetVectorDB(localStorage.token).catch((error) => {
<div class="flex items-center space-x-3">
toast.error(error);
<svg
return null;
xmlns="http://www.w3.org/2000/svg"
});
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
<path
fill-rule="evenodd"
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM5.72 7.47a.75.75 0 0 1 1.06 0L8 8.69l1.22-1.22a.75.75 0 1 1 1.06 1.06L9.06 9.75l1.22 1.22a.75.75 0 1 1-1.06 1.06L8 10.81l-1.22 1.22a.75.75 0 0 1-1.06-1.06l1.22-1.22-1.22-1.22a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg>
<span>{$i18n.t('Are you sure?')}</span>
</div>
if (res) {
<div class="flex space-x-1.5 items-center">
toast.success($i18n.t('Success'));
<button
}
class="hover:text-white transition"
on:click={() => {
const res = resetVectorDB(localStorage.token).catch((error) => {
toast.error(error);
return null;
});
if (res) {
toast.success($i18n.t('Success'));
}
showResetConfirm = false;
showResetConfirm = false;
}}
}}
>
>
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
viewBox="0 0 20 20"
fill="currentColor"
fill="currentColor"
class="w-4 h-4"
class="w-4 h-4"
>
>
<path
<path
fill-rule="evenodd"
fill-rule="evenodd"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z"
clip-rule="evenodd"
clip-rule="evenodd"
/>
/>
</svg>
</svg>
</button>
</button>
<button
class="hover:text-white transition"
on:click={() => {
showResetConfirm = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
</div>
{:else}
<button
<button
class="
hover:text-white
transition"
class="
flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800
transition"
on:click={() => {
on:click={() => {
showResetConfirm =
fals
e;
showResetConfirm =
tru
e;
}}
}}
>
>
<svg
<div class=" self-center mr-3">
xmlns="http://www.w3.org/2000/svg"
<svg
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 16 16"
class="w-4 h-4"
fill="currentColor"
>
class="w-4 h-4"
<path
>
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
<path
/>
fill-rule="evenodd"
</svg>
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>
<div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
</button>
</button>
</div>
{/if}
</div>
</div>
{:else}
</div>
<button
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
showResetConfirm = true;
}}
>
<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>
<div class=" self-center text-sm font-medium">{$i18n.t('Reset Vector Storage')}</div>
</button>
{/if}
</div>
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
<div class="flex justify-end pt-3 text-sm font-medium">
<button
<button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded"
...
...
src/lib/i18n/locales/bg-BG/translation.json
View file @
78284e49
...
@@ -100,7 +100,7 @@
...
@@ -100,7 +100,7 @@
"Deleted {{deleteModelTag}}"
:
"Изтрито {{deleteModelTag}}"
,
"Deleted {{deleteModelTag}}"
:
"Изтрито {{deleteModelTag}}"
,
"Deleted {tagName}"
:
"Изтрито {tagName}"
,
"Deleted {tagName}"
:
"Изтрито {tagName}"
,
"Description"
:
"Описание"
,
"Description"
:
"Описание"
,
"
Desktop
Notifications"
:
"Десктоп Известия"
,
"Notifications"
:
"Десктоп Известия"
,
"Disabled"
:
"Деактивиран"
,
"Disabled"
:
"Деактивиран"
,
"Discover a modelfile"
:
"Откриване на модфайл"
,
"Discover a modelfile"
:
"Откриване на модфайл"
,
"Discover a prompt"
:
"Откриване на промпт"
,
"Discover a prompt"
:
"Откриване на промпт"
,
...
...
src/lib/i18n/locales/ca-ES/translation.json
View file @
78284e49
...
@@ -100,7 +100,7 @@
...
@@ -100,7 +100,7 @@
"Deleted {{deleteModelTag}}"
:
"Esborrat {{deleteModelTag}}"
,
"Deleted {{deleteModelTag}}"
:
"Esborrat {{deleteModelTag}}"
,
"Deleted {tagName}"
:
"Esborrat {tagName}"
,
"Deleted {tagName}"
:
"Esborrat {tagName}"
,
"Description"
:
"Descripció"
,
"Description"
:
"Descripció"
,
"
Desktop
Notifications"
:
"Notificacions d'Escriptori"
,
"Notifications"
:
"Notificacions d'Escriptori"
,
"Disabled"
:
"Desactivat"
,
"Disabled"
:
"Desactivat"
,
"Discover a modelfile"
:
"Descobreix un fitxer de model"
,
"Discover a modelfile"
:
"Descobreix un fitxer de model"
,
"Discover a prompt"
:
"Descobreix un prompt"
,
"Discover a prompt"
:
"Descobreix un prompt"
,
...
...
src/lib/i18n/locales/de-DE/translation.json
View file @
78284e49
...
@@ -100,7 +100,7 @@
...
@@ -100,7 +100,7 @@
"Deleted {{deleteModelTag}}"
:
"{{deleteModelTag}} gelöscht"
,
"Deleted {{deleteModelTag}}"
:
"{{deleteModelTag}} gelöscht"
,
"Deleted {tagName}"
:
"{tagName} gelöscht"
,
"Deleted {tagName}"
:
"{tagName} gelöscht"
,
"Description"
:
"Beschreibung"
,
"Description"
:
"Beschreibung"
,
"
Desktop
Notifications"
:
"Desktop-Benachrichtigungen"
,
"Notifications"
:
"Desktop-Benachrichtigungen"
,
"Disabled"
:
"Deaktiviert"
,
"Disabled"
:
"Deaktiviert"
,
"Discover a modelfile"
:
"Eine Modelfiles entdecken"
,
"Discover a modelfile"
:
"Eine Modelfiles entdecken"
,
"Discover a prompt"
:
"Einen Prompt entdecken"
,
"Discover a prompt"
:
"Einen Prompt entdecken"
,
...
...
src/lib/i18n/locales/en-GB/translation.json
0 → 100644
View file @
78284e49
{
"analyze"
:
"analyse"
,
"analyzed"
:
"analysed"
,
"analyzes"
:
"analyses"
,
"apologize"
:
"apologise"
,
"apologized"
:
"apologised"
,
"apologizes"
:
"apologises"
,
"apologizing"
:
"apologising"
,
"canceled"
:
"cancelled"
,
"canceling"
:
"cancelling"
,
"capitalize"
:
"capitalise"
,
"capitalized"
:
"capitalised"
,
"capitalizes"
:
"capitalises"
,
"center"
:
"centre"
,
"centered"
:
"centred"
,
"color"
:
"colour"
,
"colorize"
:
"colourise"
,
"customize"
:
"customise"
,
"customizes"
:
"customises"
,
"defense"
:
"defence"
,
"dialog"
:
"dialogue"
,
"emphasize"
:
"emphasise"
,
"emphasized"
:
"emphasised"
,
"emphasizes"
:
"emphasises"
,
"favor"
:
"favour"
,
"favorable"
:
"favourable"
,
"favorite"
:
"favourite"
,
"favoritism"
:
"favouritism"
,
"labor"
:
"labour"
,
"labored"
:
"laboured"
,
"laboring"
:
"labouring"
,
"maximize"
:
"maximise"
,
"maximizes"
:
"maximises"
,
"minimize"
:
"minimise"
,
"minimizes"
:
"minimises"
,
"neighbor"
:
"neighbour"
,
"neighborhood"
:
"neighbourhood"
,
"offense"
:
"offence"
,
"organize"
:
"organise"
,
"organizes"
:
"organises"
,
"personalize"
:
"personalise"
,
"personalizes"
:
"personalises"
,
"program"
:
"programme"
,
"programmed"
:
"programmed"
,
"programs"
:
"programmes"
,
"quantization"
:
"quantisation"
,
"quantize"
:
"quantise"
,
"randomize"
:
"randomise"
,
"randomizes"
:
"randomises"
,
"realize"
:
"realise"
,
"realizes"
:
"realises"
,
"recognize"
:
"recognise"
,
"recognizes"
:
"recognises"
,
"summarize"
:
"summarise"
,
"summarizes"
:
"summarises"
,
"theater"
:
"theatre"
,
"theaters"
:
"theatres"
,
"toward"
:
"towards"
,
"traveled"
:
"travelled"
,
"traveler"
:
"traveller"
,
"traveling"
:
"travelling"
,
"utilize"
:
"utilise"
,
"utilizes"
:
"utilises"
}
Prev
1
2
3
4
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