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
be5534c6
"src/vscode:/vscode.git/clone" did not exist on "a19d4267f705ccdcc97bffe2d166d41a7f274ef0"
Unverified
Commit
be5534c6
authored
May 19, 2024
by
Timothy Jaeryang Baek
Committed by
GitHub
May 19, 2024
Browse files
Merge pull request #2376 from open-webui/dev
0.1.125
parents
90503be2
bcc2bab6
Changes
148
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
857 additions
and
311 deletions
+857
-311
src/lib/components/admin/Settings/General.svelte
src/lib/components/admin/Settings/General.svelte
+2
-2
src/lib/components/chat/MessageInput.svelte
src/lib/components/chat/MessageInput.svelte
+19
-16
src/lib/components/chat/Messages.svelte
src/lib/components/chat/Messages.svelte
+69
-31
src/lib/components/chat/Messages/CitationsModal.svelte
src/lib/components/chat/Messages/CitationsModal.svelte
+1
-1
src/lib/components/chat/Messages/CodeBlock.svelte
src/lib/components/chat/Messages/CodeBlock.svelte
+226
-5
src/lib/components/chat/Messages/CompareMessages.svelte
src/lib/components/chat/Messages/CompareMessages.svelte
+163
-0
src/lib/components/chat/Messages/Name.svelte
src/lib/components/chat/Messages/Name.svelte
+1
-1
src/lib/components/chat/Messages/Placeholder.svelte
src/lib/components/chat/Messages/Placeholder.svelte
+2
-0
src/lib/components/chat/Messages/ProfileImage.svelte
src/lib/components/chat/Messages/ProfileImage.svelte
+15
-2
src/lib/components/chat/Messages/RateComment.svelte
src/lib/components/chat/Messages/RateComment.svelte
+2
-2
src/lib/components/chat/Messages/ResponseMessage.svelte
src/lib/components/chat/Messages/ResponseMessage.svelte
+139
-122
src/lib/components/chat/Messages/UserMessage.svelte
src/lib/components/chat/Messages/UserMessage.svelte
+172
-91
src/lib/components/chat/ModelSelector.svelte
src/lib/components/chat/ModelSelector.svelte
+3
-3
src/lib/components/chat/ModelSelector/Selector.svelte
src/lib/components/chat/ModelSelector/Selector.svelte
+15
-12
src/lib/components/chat/Settings/Account.svelte
src/lib/components/chat/Settings/Account.svelte
+2
-2
src/lib/components/chat/Settings/Advanced.svelte
src/lib/components/chat/Settings/Advanced.svelte
+1
-1
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
...b/components/chat/Settings/Advanced/AdvancedParams.svelte
+2
-2
src/lib/components/chat/Settings/Audio.svelte
src/lib/components/chat/Settings/Audio.svelte
+2
-2
src/lib/components/chat/Settings/Connections.svelte
src/lib/components/chat/Settings/Connections.svelte
+19
-14
src/lib/components/chat/Settings/General.svelte
src/lib/components/chat/Settings/General.svelte
+2
-2
No files found.
src/lib/components/admin/Settings/General.svelte
View file @
be5534c6
...
@@ -123,7 +123,7 @@
...
@@ -123,7 +123,7 @@
<div class="flex mt-2 space-x-2">
<div class="flex mt-2 space-x-2">
<input
<input
class="w-full rounded py-
1.5
px-4 text-sm dark:text-gray-300 dark:bg-gray-8
0
0 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-8
5
0 outline-none"
type="text"
type="text"
placeholder={`https://example.com/webhook`}
placeholder={`https://example.com/webhook`}
bind:value={webhookUrl}
bind:value={webhookUrl}
...
@@ -140,7 +140,7 @@
...
@@ -140,7 +140,7 @@
<div class="flex mt-2 space-x-2">
<div class="flex mt-2 space-x-2">
<input
<input
class="w-full rounded py-
1.5
px-4 text-sm dark:text-gray-300 dark:bg-gray-8
0
0 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-8
5
0 outline-none"
type="text"
type="text"
placeholder={`e.g.) "30m","1h", "10d". `}
placeholder={`e.g.) "30m","1h", "10d". `}
bind:value={JWTExpiresIn}
bind:value={JWTExpiresIn}
...
...
src/lib/components/chat/MessageInput.svelte
View file @
be5534c6
<script lang="ts">
<script lang="ts">
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
import { onMount, tick, getContext } from 'svelte';
import { onMount, tick, getContext } from 'svelte';
import { modelfiles, settings, showSidebar } from '$lib/stores';
import {
mobile,
modelfiles, settings, showSidebar } from '$lib/stores';
import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
import {
import {
...
@@ -327,7 +327,6 @@
...
@@ -327,7 +327,6 @@
};
};
onMount(() => {
onMount(() => {
console.log(document.getElementById('sidebar'));
window.setTimeout(() => chatTextAreaElement?.focus(), 0);
window.setTimeout(() => chatTextAreaElement?.focus(), 0);
const dropZone = document.querySelector('body');
const dropZone = document.querySelector('body');
...
@@ -358,7 +357,7 @@
...
@@ -358,7 +357,7 @@
if (inputFiles && inputFiles.length > 0) {
if (inputFiles && inputFiles.length > 0) {
inputFiles.forEach((file) => {
inputFiles.forEach((file) => {
console.log(file, file.name.split('.').at(-1));
console.log(file, file.name.split('.').at(-1));
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
if (['image/gif',
'image/webp',
'image/jpeg', 'image/png'].includes(file['type'])) {
let reader = new FileReader();
let reader = new FileReader();
reader.onload = (event) => {
reader.onload = (event) => {
files = [
files = [
...
@@ -412,7 +411,7 @@
...
@@ -412,7 +411,7 @@
{#if dragged}
{#if dragged}
<div
<div
class="fixed {$showSidebar
class="fixed {$showSidebar
? 'left-0
lg
:left-[260px]
lg
:w-[calc(100%-260px)]'
? 'left-0
md
:left-[260px]
md
:w-[calc(100%-260px)]'
: 'left-0'} w-full h-full flex z-50 touch-none pointer-events-none"
: 'left-0'} w-full h-full flex z-50 touch-none pointer-events-none"
id="dropzone"
id="dropzone"
role="region"
role="region"
...
@@ -428,9 +427,9 @@
...
@@ -428,9 +427,9 @@
</div>
</div>
{/if}
{/if}
<div class="fixed bottom-0 {$showSidebar ? 'left-0
lg
:left-[260px]' : 'left-0'} right-0">
<div class="fixed bottom-0 {$showSidebar ? 'left-0
md
:left-[260px]' : 'left-0'} right-0">
<div class="w-full">
<div class="w-full">
<div class="px-2.5
lg
:px-16 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
<div class="px-2.5
md
:px-16 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
<div class="flex flex-col max-w-5xl w-full">
<div class="flex flex-col max-w-5xl w-full">
<div class="relative">
<div class="relative">
{#if autoScroll === false && messages.length > 0}
{#if autoScroll === false && messages.length > 0}
...
@@ -506,6 +505,7 @@
...
@@ -506,6 +505,7 @@
>
>
<div class="flex items-center gap-2 text-sm dark:text-gray-500">
<div class="flex items-center gap-2 text-sm dark:text-gray-500">
<img
<img
crossorigin="anonymous"
alt="model profile"
alt="model profile"
class="size-5 max-w-[28px] object-cover rounded-full"
class="size-5 max-w-[28px] object-cover rounded-full"
src={$modelfiles.find((modelfile) => modelfile.tagName === selectedModel.id)
src={$modelfiles.find((modelfile) => modelfile.tagName === selectedModel.id)
...
@@ -535,7 +535,7 @@
...
@@ -535,7 +535,7 @@
</div>
</div>
<div class="bg-white dark:bg-gray-900">
<div class="bg-white dark:bg-gray-900">
<div class="max-w-6xl px-2.5
lg
:px-16 mx-auto inset-x-0">
<div class="max-w-6xl px-2.5
md
:px-16 mx-auto inset-x-0">
<div class=" pb-2">
<div class=" pb-2">
<input
<input
bind:this={filesInputElement}
bind:this={filesInputElement}
...
@@ -547,7 +547,9 @@
...
@@ -547,7 +547,9 @@
if (inputFiles && inputFiles.length > 0) {
if (inputFiles && inputFiles.length > 0) {
const _inputFiles = Array.from(inputFiles);
const _inputFiles = Array.from(inputFiles);
_inputFiles.forEach((file) => {
_inputFiles.forEach((file) => {
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
if (
['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])
) {
let reader = new FileReader();
let reader = new FileReader();
reader.onload = (event) => {
reader.onload = (event) => {
files = [
files = [
...
@@ -584,7 +586,8 @@
...
@@ -584,7 +586,8 @@
}}
}}
/>
/>
<form
<form
class=" flex flex-col relative w-full rounded-3xl px-1.5 border border-gray-100 dark:border-gray-850 bg-white dark:bg-gray-900 dark:text-gray-100"
dir={$settings?.chatDirection ?? 'LTR'}
class=" flex flex-col relative w-full rounded-3xl px-1.5 bg-gray-50 dark:bg-gray-850 dark:text-gray-100"
on:submit|preventDefault={() => {
on:submit|preventDefault={() => {
submitPrompt(prompt, user);
submitPrompt(prompt, user);
}}
}}
...
@@ -754,7 +757,7 @@
...
@@ -754,7 +757,7 @@
<textarea
<textarea
id="chat-textarea"
id="chat-textarea"
bind:this={chatTextAreaElement}
bind:this={chatTextAreaElement}
class="scrollbar-
none
dark:bg-gray-
90
0 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
class="scrollbar-
hidden bg-gray-50
dark:bg-gray-
85
0 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
? ''
? ''
: ' pl-4'} rounded-xl resize-none h-[48px]"
: ' pl-4'} rounded-xl resize-none h-[48px]"
placeholder={chatInputPlaceholder !== ''
placeholder={chatInputPlaceholder !== ''
...
@@ -765,7 +768,7 @@
...
@@ -765,7 +768,7 @@
bind:value={prompt}
bind:value={prompt}
on:keypress={(e) => {
on:keypress={(e) => {
if (
if (
window.innerWidth > 1024
||
!$mobile
||
!(
!(
'ontouchstart' in window ||
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.maxTouchPoints > 0 ||
...
@@ -995,7 +998,7 @@
...
@@ -995,7 +998,7 @@
id="send-message-button"
id="send-message-button"
class="{prompt !== ''
class="{prompt !== ''
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
: 'text-white bg-gray-
1
00 dark:text-gray-900 dark:bg-gray-
8
00 disabled'} transition rounded-full p-1.5 self-center"
: 'text-white bg-gray-
2
00 dark:text-gray-900 dark:bg-gray-
7
00 disabled'} transition rounded-full p-1.5 self-center"
type="submit"
type="submit"
disabled={prompt === ''}
disabled={prompt === ''}
>
>
...
@@ -1046,12 +1049,12 @@
...
@@ -1046,12 +1049,12 @@
</div>
</div>
<style>
<style>
.scrollbar-
none
:active::-webkit-scrollbar-thumb,
.scrollbar-
hidden
:active::-webkit-scrollbar-thumb,
.scrollbar-
none
:focus::-webkit-scrollbar-thumb,
.scrollbar-
hidden
:focus::-webkit-scrollbar-thumb,
.scrollbar-
none
:hover::-webkit-scrollbar-thumb {
.scrollbar-
hidden
:hover::-webkit-scrollbar-thumb {
visibility: visible;
visibility: visible;
}
}
.scrollbar-
none
::-webkit-scrollbar-thumb {
.scrollbar-
hidden
::-webkit-scrollbar-thumb {
visibility: hidden;
visibility: hidden;
}
}
</style>
</style>
src/lib/components/chat/Messages.svelte
View file @
be5534c6
<script lang="ts">
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { v4 as uuidv4 } from 'uuid';
import { chats, config, modelfiles, settings, user } from '$lib/stores';
import { chats, config, modelfiles, settings, user
as _user, mobile
} from '$lib/stores';
import { tick, getContext } from 'svelte';
import { tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
...
@@ -13,6 +13,8 @@
...
@@ -13,6 +13,8 @@
import Spinner from '../common/Spinner.svelte';
import Spinner from '../common/Spinner.svelte';
import { imageGenerations } from '$lib/apis/images';
import { imageGenerations } from '$lib/apis/images';
import { copyToClipboard, findWordIndices } from '$lib/utils';
import { copyToClipboard, findWordIndices } from '$lib/utils';
import CompareMessages from './Messages/CompareMessages.svelte';
import { stringify } from 'postcss';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
...
@@ -22,15 +24,16 @@
...
@@ -22,15 +24,16 @@
export let continueGeneration: Function;
export let continueGeneration: Function;
export let regenerateResponse: Function;
export let regenerateResponse: Function;
export let user = $_user;
export let prompt;
export let prompt;
export let suggestionPrompts;
export let suggestionPrompts
= []
;
export let processing = '';
export let processing = '';
export let bottomPadding = false;
export let bottomPadding = false;
export let autoScroll;
export let autoScroll;
export let selectedModels;
export let history = {};
export let history = {};
export let messages = [];
export let messages = [];
export let selectedModels;
export let selectedModelfiles = [];
export let selectedModelfiles = [];
$: if (autoScroll && bottomPadding) {
$: if (autoScroll && bottomPadding) {
...
@@ -62,7 +65,8 @@
...
@@ -62,7 +65,8 @@
childrenIds: [],
childrenIds: [],
role: 'user',
role: 'user',
content: userPrompt,
content: userPrompt,
...(history.messages[messageId].files && { files: history.messages[messageId].files })
...(history.messages[messageId].files && { files: history.messages[messageId].files }),
models: selectedModels.filter((m, mIdx) => selectedModels.indexOf(m) === mIdx)
};
};
let messageParentId = history.messages[messageId].parentId;
let messageParentId = history.messages[messageId].parentId;
...
@@ -78,7 +82,7 @@
...
@@ -78,7 +82,7 @@
history.currentId = userMessageId;
history.currentId = userMessageId;
await tick();
await tick();
await sendPrompt(userPrompt, userMessageId
, chatId
);
await sendPrompt(userPrompt, userMessageId);
};
};
const updateChatMessages = async () => {
const updateChatMessages = async () => {
...
@@ -294,7 +298,7 @@
...
@@ -294,7 +298,7 @@
{#if message.role === 'user'}
{#if message.role === 'user'}
<UserMessage
<UserMessage
on:delete={() => messageDeleteHandler(message.id)}
on:delete={() => messageDeleteHandler(message.id)}
user={$
user}
{
user}
{readOnly}
{readOnly}
{message}
{message}
isFirstMessage={messageIdx === 0}
isFirstMessage={messageIdx === 0}
...
@@ -308,32 +312,66 @@
...
@@ -308,32 +312,66 @@
{showNextMessage}
{showNextMessage}
copyToClipboard={copyToClipboardWithToast}
copyToClipboard={copyToClipboardWithToast}
/>
/>
{:else if $mobile || (history.messages[message.parentId]?.models?.length ?? 1) === 1}
{#key message.id}
<ResponseMessage
{message}
modelfiles={selectedModelfiles}
siblings={history.messages[message.parentId]?.childrenIds ?? []}
isLastMessage={messageIdx + 1 === messages.length}
{readOnly}
{updateChatMessages}
{confirmEditResponseMessage}
{showPreviousMessage}
{showNextMessage}
{rateMessage}
copyToClipboard={copyToClipboardWithToast}
{continueGeneration}
{regenerateResponse}
on:save={async (e) => {
console.log('save', e);
const message = e.detail;
history.messages[message.id] = message;
await updateChatById(localStorage.token, chatId, {
messages: messages,
history: history
});
}}
/>
{/key}
{:else}
{:else}
<ResponseMessage
{#key message.parentId}
{message}
<CompareMessages
modelfiles={selectedModelfiles}
bind:history
siblings={history.messages[message.parentId]?.childrenIds ?? []}
{messages}
isLastMessage={messageIdx + 1 === messages.length}
{chatId}
{readOnly}
parentMessage={history.messages[message.parentId]}
{updateChatMessages}
{messageIdx}
{confirmEditResponseMessage}
{selectedModelfiles}
{showPreviousMessage}
{updateChatMessages}
{showNextMessage}
{confirmEditResponseMessage}
{rateMessage}
{rateMessage}
copyToClipboard={copyToClipboardWithToast}
copyToClipboard={copyToClipboardWithToast}
{continueGeneration}
{continueGeneration}
{regenerateResponse}
{regenerateResponse}
on:save={async (e) => {
on:change={async () => {
console.log('save', e);
await updateChatById(localStorage.token, chatId, {
messages: messages,
const message = e.detail;
history: history
history.messages[message.id] = message;
});
await updateChatById(localStorage.token, chatId, {
messages: messages,
if (autoScroll) {
history: history
const element = document.getElementById('messages-container');
});
autoScroll =
}}
element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
/>
setTimeout(() => {
scrollToBottom();
}, 100);
}
}}
/>
{/key}
{/if}
{/if}
</div>
</div>
</div>
</div>
...
...
src/lib/components/chat/Messages/CitationsModal.svelte
View file @
be5534c6
...
@@ -47,7 +47,7 @@
...
@@ -47,7 +47,7 @@
<div class="flex flex-col md:flex-row w-full px-6 pb-5 md:space-x-4">
<div class="flex flex-col md:flex-row w-full px-6 pb-5 md:space-x-4">
<div
<div
class="flex flex-col w-full dark:text-gray-200 overflow-y-scroll max-h-[22rem] scrollbar-
none
"
class="flex flex-col w-full dark:text-gray-200 overflow-y-scroll max-h-[22rem] scrollbar-
hidden
"
>
>
{#each mergedDocuments as document, documentIdx}
{#each mergedDocuments as document, documentIdx}
<div class="flex flex-col w-full">
<div class="flex flex-col w-full">
...
...
src/lib/components/chat/Messages/CodeBlock.svelte
View file @
be5534c6
<script lang="ts">
<script lang="ts">
import Spinner from '$lib/components/common/Spinner.svelte';
import { copyToClipboard } from '$lib/utils';
import { copyToClipboard } from '$lib/utils';
import hljs from 'highlight.js';
import hljs from 'highlight.js';
import 'highlight.js/styles/github-dark.min.css';
import 'highlight.js/styles/github-dark.min.css';
import { loadPyodide } from 'pyodide';
import { tick } from 'svelte';
import PyodideWorker from '$lib/workers/pyodide.worker?worker';
export let id = '';
export let lang = '';
export let lang = '';
export let code = '';
export let code = '';
let executing = false;
let stdout = null;
let stderr = null;
let result = null;
let copied = false;
let copied = false;
const copyCode = async () => {
const copyCode = async () => {
...
@@ -17,24 +29,233 @@
...
@@ -17,24 +29,233 @@
}, 1000);
}, 1000);
};
};
const checkPythonCode = (str) => {
// Check if the string contains typical Python syntax characters
const pythonSyntax = [
'def ',
'else:',
'elif ',
'try:',
'except:',
'finally:',
'yield ',
'lambda ',
'assert ',
'nonlocal ',
'del ',
'True',
'False',
'None',
' and ',
' or ',
' not ',
' in ',
' is ',
' with '
];
for (let syntax of pythonSyntax) {
if (str.includes(syntax)) {
return true;
}
}
// If none of the above conditions met, it's probably not Python code
return false;
};
const executePython = async (code) => {
if (!code.includes('input') && !code.includes('matplotlib')) {
executePythonAsWorker(code);
} else {
result = null;
stdout = null;
stderr = null;
executing = true;
document.pyodideMplTarget = document.getElementById(`plt-canvas-${id}`);
let pyodide = await loadPyodide({
indexURL: '/pyodide/',
stdout: (text) => {
console.log('Python output:', text);
if (stdout) {
stdout += `${text}\n`;
} else {
stdout = `${text}\n`;
}
},
stderr: (text) => {
console.log('An error occured:', text);
if (stderr) {
stderr += `${text}\n`;
} else {
stderr = `${text}\n`;
}
},
packages: ['micropip']
});
try {
const micropip = pyodide.pyimport('micropip');
await micropip.set_index_urls('https://pypi.org/pypi/{package_name}/json');
let packages = [
code.includes('requests') ? 'requests' : null,
code.includes('bs4') ? 'beautifulsoup4' : null,
code.includes('numpy') ? 'numpy' : null,
code.includes('pandas') ? 'pandas' : null,
code.includes('matplotlib') ? 'matplotlib' : null,
code.includes('sklearn') ? 'scikit-learn' : null,
code.includes('scipy') ? 'scipy' : null,
code.includes('re') ? 'regex' : null,
code.includes('seaborn') ? 'seaborn' : null
].filter(Boolean);
console.log(packages);
await micropip.install(packages);
result = await pyodide.runPythonAsync(`from js import prompt
def input(p):
return prompt(p)
__builtins__.input = input`);
result = await pyodide.runPython(code);
if (!result) {
result = '[NO OUTPUT]';
}
console.log(result);
console.log(stdout);
console.log(stderr);
const pltCanvasElement = document.getElementById(`plt-canvas-${id}`);
if (pltCanvasElement?.innerHTML !== '') {
pltCanvasElement.classList.add('pt-4');
}
} catch (error) {
console.error('Error:', error);
stderr = error;
}
executing = false;
}
};
const executePythonAsWorker = async (code) => {
result = null;
stdout = null;
stderr = null;
executing = true;
let packages = [
code.includes('requests') ? 'requests' : null,
code.includes('bs4') ? 'beautifulsoup4' : null,
code.includes('numpy') ? 'numpy' : null,
code.includes('pandas') ? 'pandas' : null,
code.includes('sklearn') ? 'scikit-learn' : null,
code.includes('scipy') ? 'scipy' : null,
code.includes('re') ? 'regex' : null,
code.includes('seaborn') ? 'seaborn' : null
].filter(Boolean);
console.log(packages);
const pyodideWorker = new PyodideWorker();
pyodideWorker.postMessage({
id: id,
code: code,
packages: packages
});
setTimeout(() => {
if (executing) {
executing = false;
stderr = 'Execution Time Limit Exceeded';
pyodideWorker.terminate();
}
}, 60000);
pyodideWorker.onmessage = (event) => {
console.log('pyodideWorker.onmessage', event);
const { id, ...data } = event.data;
console.log(id, data);
data['stdout'] && (stdout = data['stdout']);
data['stderr'] && (stderr = data['stderr']);
data['result'] && (result = data['result']);
executing = false;
};
pyodideWorker.onerror = (event) => {
console.log('pyodideWorker.onerror', event);
executing = false;
};
};
$: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : '';
$: highlightedCode = code ? hljs.highlightAuto(code, hljs.getLanguage(lang)?.aliases).value : '';
</script>
</script>
{#if code}
{#if code}
<div class="mb-4">
<div class="mb-4"
dir="ltr"
>
<div
<div
class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
class="flex justify-between bg-[#202123] text-white text-xs px-4 pt-1 pb-0.5 rounded-t-lg overflow-x-auto"
>
>
<div class="p-1">{@html lang}</div>
<div class="p-1">{@html lang}</div>
<button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
>{copied ? 'Copied' : 'Copy Code'}</button
<div class="flex items-center">
>
{#if ['', 'python'].includes(lang) && (lang === 'python' || checkPythonCode(code))}
{#if executing}
<div class="copy-code-button bg-none border-none p-1 cursor-not-allowed">Running</div>
{:else}
<button
class="copy-code-button bg-none border-none p-1"
on:click={() => {
executePython(code);
}}>Run</button
>
{/if}
{/if}
<button class="copy-code-button bg-none border-none p-1" on:click={copyCode}
>{copied ? 'Copied' : 'Copy Code'}</button
>
</div>
</div>
</div>
<pre
<pre
class=" hljs p-4 px-5 overflow-x-auto"
class=" hljs p-4 px-5 overflow-x-auto"
style="border-top-left-radius: 0px; border-top-right-radius: 0px;"><code
style="border-top-left-radius: 0px; border-top-right-radius: 0px; {(executing ||
stdout ||
stderr ||
result) &&
'border-bottom-left-radius: 0px; border-bottom-right-radius: 0px;'}"><code
class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code
class="language-{lang} rounded-t-none whitespace-pre">{@html highlightedCode || code}</code
></pre>
></pre>
<div
id="plt-canvas-{id}"
class="bg-[#202123] text-white max-w-full overflow-x-auto scrollbar-hidden"
/>
{#if executing}
<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div class="text-sm">Running...</div>
</div>
{:else if stdout || stderr || result}
<div class="bg-[#202123] text-white px-4 py-4 rounded-b-lg">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div class="text-sm">{stdout || stderr || result}</div>
</div>
{/if}
</div>
</div>
{/if}
{/if}
src/lib/components/chat/Messages/CompareMessages.svelte
0 → 100644
View file @
be5534c6
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { updateChatById } from '$lib/apis/chats';
import { onMount, tick } from 'svelte';
import ResponseMessage from './ResponseMessage.svelte';
export let chatId;
export let history;
export let messages = [];
export let messageIdx;
export let parentMessage;
export let selectedModelfiles;
export let updateChatMessages: Function;
export let confirmEditResponseMessage: Function;
export let rateMessage: Function;
export let copyToClipboard: Function;
export let continueGeneration: Function;
export let regenerateResponse: Function;
const dispatch = createEventDispatcher();
let currentMessageId;
let groupedMessagesIdx = {};
let groupedMessages = {};
$: groupedMessages = parentMessage?.models.reduce((a, model) => {
const modelMessages = parentMessage?.childrenIds
.map((id) => history.messages[id])
.filter((m) => m.model === model);
return {
...a,
[model]: { messages: modelMessages }
};
}, {});
const showPreviousMessage = (model) => {
groupedMessagesIdx[model] = Math.max(0, groupedMessagesIdx[model] - 1);
let messageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
console.log(messageId);
let messageChildrenIds = history.messages[messageId].childrenIds;
while (messageChildrenIds.length !== 0) {
messageId = messageChildrenIds.at(-1);
messageChildrenIds = history.messages[messageId].childrenIds;
}
history.currentId = messageId;
dispatch('change');
};
const showNextMessage = (model) => {
groupedMessagesIdx[model] = Math.min(
groupedMessages[model].messages.length - 1,
groupedMessagesIdx[model] + 1
);
let messageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
console.log(messageId);
let messageChildrenIds = history.messages[messageId].childrenIds;
while (messageChildrenIds.length !== 0) {
messageId = messageChildrenIds.at(-1);
messageChildrenIds = history.messages[messageId].childrenIds;
}
history.currentId = messageId;
dispatch('change');
};
onMount(async () => {
await tick();
currentMessageId = messages[messageIdx].id;
for (const model of parentMessage?.models) {
const idx = groupedMessages[model].messages.findIndex((m) => m.id === currentMessageId);
if (idx !== -1) {
groupedMessagesIdx[model] = idx;
} else {
groupedMessagesIdx[model] = 0;
}
}
});
</script>
<div>
<div
class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden"
id="responses-container-{parentMessage.id}"
>
{#each Object.keys(groupedMessages) as model}
{#if groupedMessagesIdx[model] !== undefined && groupedMessages[model].messages.length > 0}
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class=" snap-center min-w-80 w-full max-w-full m-1 border {history.messages[
currentMessageId
].model === model
? 'border-gray-100 dark:border-gray-700 border-[1.5px]'
: 'border-gray-50 dark:border-gray-850 '} transition p-5 rounded-3xl"
on:click={() => {
currentMessageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
let messageId = groupedMessages[model].messages[groupedMessagesIdx[model]].id;
console.log(messageId);
let messageChildrenIds = history.messages[messageId].childrenIds;
while (messageChildrenIds.length !== 0) {
messageId = messageChildrenIds.at(-1);
messageChildrenIds = history.messages[messageId].childrenIds;
}
history.currentId = messageId;
dispatch('change');
}}
>
<ResponseMessage
message={groupedMessages[model].messages[groupedMessagesIdx[model]]}
modelfiles={selectedModelfiles}
siblings={groupedMessages[model].messages.map((m) => m.id)}
isLastMessage={true}
{updateChatMessages}
{confirmEditResponseMessage}
showPreviousMessage={() => showPreviousMessage(model)}
showNextMessage={() => showNextMessage(model)}
{rateMessage}
{copyToClipboard}
{continueGeneration}
regenerateResponse={async (message) => {
regenerateResponse(message);
await tick();
groupedMessagesIdx[model] = groupedMessages[model].messages.length - 1;
}}
on:save={async (e) => {
console.log('save', e);
const message = e.detail;
history.messages[message.id] = message;
await updateChatById(localStorage.token, chatId, {
messages: messages,
history: history
});
}}
/>
</div>
{/if}
{/each}
</div>
</div>
src/lib/components/chat/Messages/Name.svelte
View file @
be5534c6
<div class=" self-center font-bold mb-0.5
capitalize
line-clamp-1">
<div class=" self-center font-bold mb-0.5 line-clamp-1
contents
">
<slot />
<slot />
</div>
</div>
src/lib/components/chat/Messages/Placeholder.svelte
View file @
be5534c6
...
@@ -43,6 +43,7 @@
...
@@ -43,6 +43,7 @@
>
>
{#if model in modelfiles}
{#if model in modelfiles}
<img
<img
crossorigin="anonymous"
src={modelfiles[model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`}
src={modelfiles[model]?.imageUrl ?? `${WEBUI_BASE_URL}/static/favicon.png`}
alt="modelfile"
alt="modelfile"
class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
class=" size-[2.7rem] rounded-full border-[1px] border-gray-200 dark:border-none"
...
@@ -50,6 +51,7 @@
...
@@ -50,6 +51,7 @@
/>
/>
{:else}
{:else}
<img
<img
crossorigin="anonymous"
src={$i18n.language === 'dg-DG'
src={$i18n.language === 'dg-DG'
? `/doge.png`
? `/doge.png`
: `${WEBUI_BASE_URL}/static/favicon.png`}
: `${WEBUI_BASE_URL}/static/favicon.png`}
...
...
src/lib/components/chat/Messages/ProfileImage.svelte
View file @
be5534c6
<script lang="ts">
<script lang="ts">
import { settings } from '$lib/stores';
import { WEBUI_BASE_URL } from '$lib/constants';
export let src = '/user.png';
export let src = '/user.png';
</script>
</script>
<div class=" mr-4">
<div class={($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}>
<img {src} class=" max-w-[28px] object-cover rounded-full" alt="profile" draggable="false" />
<img
crossorigin="anonymous"
src={src.startsWith(WEBUI_BASE_URL) ||
src.startsWith('https://www.gravatar.com/avatar/') ||
src.startsWith('data:')
? src
: `/user.png`}
class=" w-8 object-cover rounded-full"
alt="profile"
draggable="false"
/>
</div>
</div>
src/lib/components/chat/Messages/RateComment.svelte
View file @
be5534c6
...
@@ -39,9 +39,9 @@
...
@@ -39,9 +39,9 @@
let selectedReason = null;
let selectedReason = null;
let comment = '';
let comment = '';
$: if (message.annotation.rating === 1) {
$: if (message
?
.annotation
?
.rating === 1) {
reasons = LIKE_REASONS;
reasons = LIKE_REASONS;
} else if (message.annotation.rating === -1) {
} else if (message
?
.annotation
?
.rating === -1) {
reasons = DISLIKE_REASONS;
reasons = DISLIKE_REASONS;
}
}
...
...
src/lib/components/chat/Messages/ResponseMessage.svelte
View file @
be5534c6
...
@@ -65,11 +65,11 @@
...
@@ -65,11 +65,11 @@
let generatingImage = false;
let generatingImage = false;
let showRateComment = false;
let showRateComment = false;
let showCitationModal = false;
let showCitationModal = false;
let selectedCitation = null;
let selectedCitation = null;
$: tokens = marked.lexer(sanitizeResponseContent(message.content));
$: tokens = marked.lexer(sanitizeResponseContent(message
?
.content));
const renderer = new marked.Renderer();
const renderer = new marked.Renderer();
...
@@ -332,13 +332,17 @@
...
@@ -332,13 +332,17 @@
<CitationsModal bind:show={showCitationModal} citation={selectedCitation} />
<CitationsModal bind:show={showCitationModal} citation={selectedCitation} />
{#key message.id}
{#key message.id}
<div class=" flex w-full message-{message.id}" id="message-{message.id}">
<div
class=" flex w-full message-{message.id}"
id="message-{message.id}"
dir={$settings.chatDirection}
>
<ProfileImage
<ProfileImage
src={modelfiles[message.model]?.imageUrl ??
src={modelfiles[message.model]?.imageUrl ??
($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
($i18n.language === 'dg-DG' ? `/doge.png` : `${WEBUI_BASE_URL}/static/favicon.png`)}
/>
/>
<div class="w-full overflow-hidden">
<div class="w-full overflow-hidden
pl-1
">
<Name>
<Name>
{#if message.model in modelfiles}
{#if message.model in modelfiles}
{modelfiles[message.model]?.title}
{modelfiles[message.model]?.title}
...
@@ -347,8 +351,10 @@
...
@@ -347,8 +351,10 @@
{/if}
{/if}
{#if message.timestamp}
{#if message.timestamp}
<span class=" invisible group-hover:visible text-gray-400 text-xs font-medium">
<span
{dayjs(message.timestamp * 1000).format($i18n.t('DD/MM/YYYY HH:mm'))}
class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase"
>
{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
</span>
</span>
{/if}
{/if}
</Name>
</Name>
...
@@ -370,7 +376,7 @@
...
@@ -370,7 +376,7 @@
>
>
<div>
<div>
{#if edit === true}
{#if edit === true}
<div class="
w-full">
<div class="w-full
bg-gray-50 dark:bg-gray-800 rounded-3xl px-5 py-3 my-2
">
<textarea
<textarea
id="message-edit-{message.id}"
id="message-edit-{message.id}"
bind:this={editTextAreaElement}
bind:this={editTextAreaElement}
...
@@ -382,23 +388,25 @@
...
@@ -382,23 +388,25 @@
}}
}}
/>
/>
<div class=" mt-2 mb-1 flex justify-
c
en
ter
space-x-
2
text-sm font-medium">
<div class=" mt-2 mb-1 flex justify-en
d
space-x-
1.5
text-sm font-medium">
<button
<button
class="px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
id="close-edit-message-button"
class=" px-4 py-2 bg-gray-900 hover:bg-gray-850 text-gray-100 transition rounded-3xl"
on:click={() => {
on:click={() => {
e
ditMessage
ConfirmHandler
();
cancelE
ditMessage();
}}
}}
>
>
{$i18n.t('
Save
')}
{$i18n.t('
Cancel
')}
</button>
</button>
<button
<button
class=" px-4 py-2 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-100 transition outline outline-1 outline-gray-200 dark:outline-gray-600 rounded-lg"
id="save-edit-message-button"
class="px-4 py-2 bg-white hover:bg-gray-100 text-gray-800 transition rounded-3xl"
on:click={() => {
on:click={() => {
cancelE
ditMessage();
e
ditMessage
ConfirmHandler
();
}}
}}
>
>
{$i18n.t('
Cancel
')}
{$i18n.t('
Save
')}
</button>
</button>
</div>
</div>
</div>
</div>
...
@@ -430,9 +438,10 @@
...
@@ -430,9 +438,10 @@
{:else if message.content === ''}
{:else if message.content === ''}
<Skeleton />
<Skeleton />
{:else}
{:else}
{#each tokens as token}
{#each tokens as token
, tokenIdx
}
{#if token.type === 'code'}
{#if token.type === 'code'}
<CodeBlock
<CodeBlock
id={`${message.id}-${tokenIdx}`}
lang={token.lang}
lang={token.lang}
code={revertSanitizedResponseContent(token.text)}
code={revertSanitizedResponseContent(token.text)}
/>
/>
...
@@ -476,7 +485,7 @@
...
@@ -476,7 +485,7 @@
<div class="bg-white dark:bg-gray-700 rounded-full size-4">
<div class="bg-white dark:bg-gray-700 rounded-full size-4">
{idx + 1}
{idx + 1}
</div>
</div>
<div class="
mx-2
">
<div class="
flex-1 mx-2 line-clamp-1
">
{citation.source.name}
{citation.source.name}
</div>
</div>
</button>
</button>
...
@@ -487,50 +496,56 @@
...
@@ -487,50 +496,56 @@
{#if message.done || siblings.length > 1}
{#if message.done || siblings.length > 1}
<div
<div
class=" flex justify-start
space-x-1
overflow-x-auto buttons text-gray-
7
00 dark:text-gray-500"
class=" flex justify-start overflow-x-auto buttons text-gray-
6
00 dark:text-gray-500"
>
>
{#if siblings.length > 1}
{#if siblings.length > 1}
<div class="flex self-center min-w-fit">
<div class="flex self-center min-w-fit"
dir="ltr"
>
<button
<button
class="self-center dark:hover:text-white hover:text-black transition"
class="self-center
p-1 hover:bg-black/5 dark:hover:bg-white/5
dark:hover:text-white hover:text-black
rounded-md
transition"
on:click={() => {
on:click={() => {
showPreviousMessage(message);
showPreviousMessage(message);
}}
}}
>
>
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="none"
fill="currentColor"
viewBox="0 0 24 24"
class="w-4 h-4"
stroke="currentColor"
stroke-width="2.5"
class="size-3.5"
>
>
<path
<path
fill-rule="evenod
d"
stroke-linecap="roun
d"
d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z
"
stroke-linejoin="round
"
clip-rule="evenodd
"
d="M15.75 19.5 8.25 12l7.5-7.5
"
/>
/>
</svg>
</svg>
</button>
</button>
<div class="text-xs font-bold self-center min-w-fit dark:text-gray-100">
<div
{siblings.indexOf(message.id) + 1} / {siblings.length}
class="text-sm tracking-widest font-semibold self-center dark:text-gray-100 min-w-fit"
>
{siblings.indexOf(message.id) + 1}/{siblings.length}
</div>
</div>
<button
<button
class="self-center dark:hover:text-white hover:text-black transition"
class="self-center
p-1 hover:bg-black/5 dark:hover:bg-white/5
dark:hover:text-white hover:text-black
rounded-md
transition"
on:click={() => {
on:click={() => {
showNextMessage(message);
showNextMessage(message);
}}
}}
>
>
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="none"
fill="currentColor"
viewBox="0 0 24 24"
class="w-4 h-4"
stroke="currentColor"
stroke-width="2.5"
class="size-3.5"
>
>
<path
<path
fill-rule="evenod
d"
stroke-linecap="roun
d"
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z
"
stroke-linejoin="round
"
clip-rule="evenodd
"
d="m8.25 4.5 7.5 7.5-7.5 7.5
"
/>
/>
</svg>
</svg>
</button>
</button>
...
@@ -543,7 +558,7 @@
...
@@ -543,7 +558,7 @@
<button
<button
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
: 'invisible group-hover:visible'} p-1
.5 hover:bg-black/5 dark:hover:bg-white/5
rounded
-lg
dark:hover:text-white hover:text-black transition"
on:click={() => {
on:click={() => {
editMessageHandler();
editMessageHandler();
}}
}}
...
@@ -552,7 +567,7 @@
...
@@ -552,7 +567,7 @@
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
fill="none"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
stroke-width="2"
stroke-width="2
.3
"
stroke="currentColor"
stroke="currentColor"
class="w-4 h-4"
class="w-4 h-4"
>
>
...
@@ -570,7 +585,7 @@
...
@@ -570,7 +585,7 @@
<button
<button
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition copy-response-button"
: 'invisible group-hover:visible'} p-1
.5 hover:bg-black/5 dark:hover:bg-white/5
rounded
-lg
dark:hover:text-white hover:text-black transition copy-response-button"
on:click={() => {
on:click={() => {
copyToClipboard(message.content);
copyToClipboard(message.content);
}}
}}
...
@@ -579,7 +594,7 @@
...
@@ -579,7 +594,7 @@
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
fill="none"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
stroke-width="2"
stroke-width="2
.3
"
stroke="currentColor"
stroke="currentColor"
class="w-4 h-4"
class="w-4 h-4"
>
>
...
@@ -592,83 +607,12 @@
...
@@ -592,83 +607,12 @@
</button>
</button>
</Tooltip>
</Tooltip>
{#if !readOnly}
<Tooltip content={$i18n.t('Good Response')} placement="bottom">
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded {message?.annotation
?.rating === 1
? 'bg-gray-100 dark:bg-gray-800'
: ''} dark:hover:text-white hover:text-black transition"
on:click={() => {
rateMessage(message.id, 1);
showRateComment = true;
window.setTimeout(() => {
document
.getElementById(`message-feedback-${message.id}`)
?.scrollIntoView();
}, 0);
}}
>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
><path
d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"
/></svg
>
</button>
</Tooltip>
<Tooltip content={$i18n.t('Bad Response')} placement="bottom">
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded {message?.annotation
?.rating === -1
? 'bg-gray-100 dark:bg-gray-800'
: ''} dark:hover:text-white hover:text-black transition"
on:click={() => {
rateMessage(message.id, -1);
showRateComment = true;
window.setTimeout(() => {
document
.getElementById(`message-feedback-${message.id}`)
?.scrollIntoView();
}, 0);
}}
>
<svg
stroke="currentColor"
fill="none"
stroke-width="2"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
><path
d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"
/></svg
>
</button>
</Tooltip>
{/if}
<Tooltip content={$i18n.t('Read Aloud')} placement="bottom">
<Tooltip content={$i18n.t('Read Aloud')} placement="bottom">
<button
<button
id="speak-button-{message.id}"
id="speak-button-{message.id}"
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
: 'invisible group-hover:visible'} p-1
.5 hover:bg-black/5 dark:hover:bg-white/5
rounded
-lg
dark:hover:text-white hover:text-black transition"
on:click={() => {
on:click={() => {
if (!loadingSpeech) {
if (!loadingSpeech) {
toggleSpeakMessage(message);
toggleSpeakMessage(message);
...
@@ -715,7 +659,7 @@
...
@@ -715,7 +659,7 @@
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
fill="none"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
stroke-width="2"
stroke-width="2
.3
"
stroke="currentColor"
stroke="currentColor"
class="w-4 h-4"
class="w-4 h-4"
>
>
...
@@ -730,7 +674,7 @@
...
@@ -730,7 +674,7 @@
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
fill="none"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
stroke-width="2"
stroke-width="2
.3
"
stroke="currentColor"
stroke="currentColor"
class="w-4 h-4"
class="w-4 h-4"
>
>
...
@@ -749,7 +693,7 @@
...
@@ -749,7 +693,7 @@
<button
<button
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition"
: 'invisible group-hover:visible'}
p-1
.5 hover:bg-black/5 dark:hover:bg-white/5
rounded
-lg
dark:hover:text-white hover:text-black transition"
on:click={() => {
on:click={() => {
if (!generatingImage) {
if (!generatingImage) {
generateImage(message);
generateImage(message);
...
@@ -796,7 +740,7 @@
...
@@ -796,7 +740,7 @@
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
fill="none"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
stroke-width="2"
stroke-width="2
.3
"
stroke="currentColor"
stroke="currentColor"
class="w-4 h-4"
class="w-4 h-4"
>
>
...
@@ -816,7 +760,7 @@
...
@@ -816,7 +760,7 @@
<button
<button
class=" {isLastMessage
class=" {isLastMessage
? 'visible'
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition whitespace-pre-wrap"
: 'invisible group-hover:visible'} p-1
.5 hover:bg-black/5 dark:hover:bg-white/5
rounded
-lg
dark:hover:text-white hover:text-black transition whitespace-pre-wrap"
on:click={() => {
on:click={() => {
console.log(message);
console.log(message);
}}
}}
...
@@ -826,7 +770,7 @@
...
@@ -826,7 +770,7 @@
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
fill="none"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
stroke-width="2"
stroke-width="2
.3
"
stroke="currentColor"
stroke="currentColor"
class="w-4 h-4"
class="w-4 h-4"
>
>
...
@@ -840,13 +784,84 @@
...
@@ -840,13 +784,84 @@
</Tooltip>
</Tooltip>
{/if}
{/if}
{#if !readOnly}
<Tooltip content={$i18n.t('Good Response')} placement="bottom">
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg {message
?.annotation?.rating === 1
? 'bg-gray-100 dark:bg-gray-800'
: ''} dark:hover:text-white hover:text-black transition"
on:click={() => {
rateMessage(message.id, 1);
showRateComment = true;
window.setTimeout(() => {
document
.getElementById(`message-feedback-${message.id}`)
?.scrollIntoView();
}, 0);
}}
>
<svg
stroke="currentColor"
fill="none"
stroke-width="2.3"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
><path
d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"
/></svg
>
</button>
</Tooltip>
<Tooltip content={$i18n.t('Bad Response')} placement="bottom">
<button
class="{isLastMessage
? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg {message
?.annotation?.rating === -1
? 'bg-gray-100 dark:bg-gray-800'
: ''} dark:hover:text-white hover:text-black transition"
on:click={() => {
rateMessage(message.id, -1);
showRateComment = true;
window.setTimeout(() => {
document
.getElementById(`message-feedback-${message.id}`)
?.scrollIntoView();
}, 0);
}}
>
<svg
stroke="currentColor"
fill="none"
stroke-width="2.3"
viewBox="0 0 24 24"
stroke-linecap="round"
stroke-linejoin="round"
class="w-4 h-4"
xmlns="http://www.w3.org/2000/svg"
><path
d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"
/></svg
>
</button>
</Tooltip>
{/if}
{#if isLastMessage && !readOnly}
{#if isLastMessage && !readOnly}
<Tooltip content={$i18n.t('Continue Response')} placement="bottom">
<Tooltip content={$i18n.t('Continue Response')} placement="bottom">
<button
<button
type="button"
type="button"
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button"
: 'invisible group-hover:visible'} p-1
.5 hover:bg-black/5 dark:hover:bg-white/5
rounded
-lg
dark:hover:text-white hover:text-black transition regenerate-response-button"
on:click={() => {
on:click={() => {
continueGeneration();
continueGeneration();
}}
}}
...
@@ -855,7 +870,7 @@
...
@@ -855,7 +870,7 @@
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
fill="none"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
stroke-width="2"
stroke-width="2
.3
"
stroke="currentColor"
stroke="currentColor"
class="w-4 h-4"
class="w-4 h-4"
>
>
...
@@ -878,14 +893,16 @@
...
@@ -878,14 +893,16 @@
type="button"
type="button"
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
: 'invisible group-hover:visible'} p-1 rounded dark:hover:text-white hover:text-black transition regenerate-response-button"
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
on:click={regenerateResponse}
on:click={() => {
regenerateResponse(message);
}}
>
>
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
fill="none"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
stroke-width="2"
stroke-width="2
.3
"
stroke="currentColor"
stroke="currentColor"
class="w-4 h-4"
class="w-4 h-4"
>
>
...
@@ -902,7 +919,7 @@
...
@@ -902,7 +919,7 @@
</div>
</div>
{/if}
{/if}
{#if showRateComment}
{#if
message.done &&
showRateComment}
<RateComment
<RateComment
messageId={message.id}
messageId={message.id}
bind:show={showRateComment}
bind:show={showRateComment}
...
...
src/lib/components/chat/Messages/UserMessage.svelte
View file @
be5534c6
...
@@ -7,6 +7,8 @@
...
@@ -7,6 +7,8 @@
import { modelfiles, settings } from '$lib/stores';
import { modelfiles, settings } from '$lib/stores';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import { user as _user } from '$lib/stores';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher();
...
@@ -54,49 +56,55 @@
...
@@ -54,49 +56,55 @@
};
};
</script>
</script>
<div class=" flex w-full">
<div class=" flex w-full user-message" dir={$settings.chatDirection}>
<ProfileImage
{#if !($settings?.chatBubble ?? true)}
src={message.user
<ProfileImage
? $modelfiles.find((modelfile) => modelfile.tagName === message.user)?.imageUrl ?? '/user.png'
src={message.user
: user?.profile_image_url ?? '/user.png'}
? $modelfiles.find((modelfile) => modelfile.tagName === message.user)?.imageUrl ??
/>
'/user.png'
: user?.profile_image_url ?? '/user.png'}
<div class="w-full overflow-hidden">
/>
<div class="user-message">
{/if}
<Name>
<div class="w-full overflow-hidden pl-1">
{#if message.user}
{#if !($settings?.chatBubble ?? true)}
{#if $modelfiles.map((modelfile) => modelfile.tagName).includes(message.user)}
<div>
{$modelfiles.find((modelfile) => modelfile.tagName === message.user)?.title}
<Name>
{#if message.user}
{#if $modelfiles.map((modelfile) => modelfile.tagName).includes(message.user)}
{$modelfiles.find((modelfile) => modelfile.tagName === message.user)?.title}
{:else}
{$i18n.t('You')}
<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
{/if}
{:else if $settings.showUsername || $_user.name !== user.name}
{user.name}
{:else}
{:else}
{$i18n.t('You')}
{$i18n.t('You')}
<span class=" text-gray-500 text-sm font-medium">{message?.user ?? ''}</span>
{/if}
{/if}
{:else if $settings.showUsername}
{user.name}
{#if message.timestamp}
{:else}
<span
{$i18n.t('You')}
class=" invisible group-hover:visible text-gray-400 text-xs font-medium uppercase"
{/if}
>
{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
{#if message.timestamp}
</span>
<span class=" invisible group-hover:visible text-gray-400 text-xs font-medium">
{/if}
{dayjs(message.timestamp * 1000).format($i18n.t('DD/MM/YYYY HH:mm'))}
</Name>
</span>
</div>
{/if}
{/if}
</Name>
</div>
<div
<div
class="prose chat-{message.role} w-full max-w-full dark:prose-invert prose-headings:my-0 prose-p:my-0 prose-p:-mb-4 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-6 prose-li:-mb-4 whitespace-pre-line"
class="prose chat-{message.role} w-full max-w-full
flex flex-col justify-end
dark:prose-invert prose-headings:my-0 prose-p:my-0 prose-p:-mb-4 prose-pre:my-0 prose-table:my-0 prose-blockquote:my-0 prose-img:my-0 prose-ul:-my-4 prose-ol:-my-4 prose-li:-my-3 prose-ul:-mb-6 prose-ol:-mb-6 prose-li:-mb-4 whitespace-pre-line"
>
>
{#if message.files}
{#if message.files}
<div class="m
y
-2.5 w-full flex overflow-x-auto gap-
2
flex-wrap">
<div class="m
t
-2.5
mb-1
w-full flex
flex-col justify-end
overflow-x-auto gap-
1
flex-wrap">
{#each message.files as file}
{#each message.files as file}
<div>
<div
class={$settings?.chatBubble ?? true ? 'self-end' : ''}
>
{#if file.type === 'image'}
{#if file.type === 'image'}
<img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" />
<img src={file.url} alt="input" class=" max-h-96 rounded-lg" draggable="false" />
{:else if file.type === 'doc'}
{:else if file.type === 'doc'}
<button
<button
class="h-16 w-
[15rem]
flex items-center space-x-3 px-2.5 dark:bg-gray-
60
0 rounded-xl border border-gray-200 dark:border-none text-left"
class="h-16 w-
72
flex items-center space-x-3 px-2.5 dark:bg-gray-
85
0 rounded-xl border border-gray-200 dark:border-none text-left"
type="button"
type="button"
on:click={() => {
on:click={() => {
if (file?.url) {
if (file?.url) {
...
@@ -132,7 +140,7 @@
...
@@ -132,7 +140,7 @@
</button>
</button>
{:else if file.type === 'collection'}
{:else if file.type === 'collection'}
<button
<button
class="h-16 w-
[15rem]
flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none text-left"
class="h-16 w-
72
flex items-center space-x-3 px-2.5 dark:bg-gray-600 rounded-xl border border-gray-200 dark:border-none text-left"
type="button"
type="button"
>
>
<div class="p-2.5 bg-red-400 text-white rounded-lg">
<div class="p-2.5 bg-red-400 text-white rounded-lg">
...
@@ -166,7 +174,7 @@
...
@@ -166,7 +174,7 @@
{/if}
{/if}
{#if edit === true}
{#if edit === true}
<div class=" w-full">
<div class=" w-full
bg-gray-50 dark:bg-gray-800 rounded-3xl px-5 py-3 mb-2
">
<textarea
<textarea
id="message-edit-{message.id}"
id="message-edit-{message.id}"
bind:this={messageEditTextAreaElement}
bind:this={messageEditTextAreaElement}
...
@@ -190,85 +198,104 @@
...
@@ -190,85 +198,104 @@
}}
}}
/>
/>
<div class=" mt-2 mb-1 flex justify-
c
en
ter
space-x-
2
text-sm font-medium">
<div class=" mt-2 mb-1 flex justify-en
d
space-x-
1.5
text-sm font-medium">
<button
<button
id="
sav
e-edit-message-button"
id="
clos
e-edit-message-button"
class="px-4 py-2 bg-
emerald-7
00 hover:bg-
emerald
-8
0
0 text-gray-100 transition rounded-l
g
"
class="
px-4 py-2 bg-
gray-9
00 hover:bg-
gray
-8
5
0 text-gray-100 transition rounded-
3x
l"
on:click={() => {
on:click={() => {
e
ditMessage
ConfirmHandler
();
cancelE
ditMessage();
}}
}}
>
>
{$i18n.t('
Save & Submit
')}
{$i18n.t('
Cancel
')}
</button>
</button>
<button
<button
id="
clos
e-edit-message-button"
id="
sav
e-edit-message-button"
class="
px-4 py-2
hover:bg-gray-100 dark:bg-gray-800 dark:
hover:bg-gray-
7
00 text-gray-
700 dark:text-gray-100 transition outline outline-1 outline-gray-200 dark:outline-gray-600
rounded-l
g
"
class="px-4 py-2
bg-white
hover:bg-gray-
1
00 text-gray-
800 transition
rounded-
3x
l"
on:click={() => {
on:click={() => {
cancelE
ditMessage();
e
ditMessage
ConfirmHandler
();
}}
}}
>
>
{$i18n.t('
Cancel
')}
{$i18n.t('
Send
')}
</button>
</button>
</div>
</div>
</div>
</div>
{:else}
{:else}
<div class="w-full">
<div class="w-full">
<pre id="user-message">{message.content}</pre>
<div class="flex {$settings?.chatBubble ?? true ? 'justify-end' : ''} mb-2">
<div
class="rounded-3xl {$settings?.chatBubble ?? true
? `max-w-[90%] px-5 py-2 bg-gray-50 dark:bg-gray-850 ${
message.files ? 'rounded-tr-lg' : ''
}`
: ''} "
>
<pre id="user-message">{message.content}</pre>
</div>
</div>
<div class=" flex justify-start space-x-1 text-gray-700 dark:text-gray-500">
<div
{#if siblings.length > 1}
class=" flex {$settings?.chatBubble ?? true
<div class="flex self-center">
? 'justify-end'
<button
: ''} text-gray-600 dark:text-gray-500"
class="self-center dark:hover:text-white hover:text-black transition"
>
on:click={() => {
{#if !($settings?.chatBubble ?? true)}
showPreviousMessage(message);
{#if siblings.length > 1}
}}
<div class="flex self-center" dir="ltr">
>
<button
<svg
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
xmlns="http://www.w3.org/2000/svg"
on:click={() => {
viewBox="0 0 20 20"
showPreviousMessage(message);
fill="currentColor"
}}
class="w-4 h-4"
>
>
<path
<svg
fill-rule="evenodd"
xmlns="http://www.w3.org/2000/svg"
d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z"
fill="none"
clip-rule="evenodd"
viewBox="0 0 24 24"
/>
stroke="currentColor"
</svg>
stroke-width="2.5"
</button>
class="size-3.5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 19.5 8.25 12l7.5-7.5"
/>
</svg>
</button>
<div class="text-
x
s font-bold self-center dark:text-gray-100">
<div class="text-s
m tracking-widest
font-
semi
bold self-center dark:text-gray-100">
{siblings.indexOf(message.id) + 1}
/
{siblings.length}
{siblings.indexOf(message.id) + 1}
/
{siblings.length}
</div>
</div>
<button
<button
class="self-center dark:hover:text-white hover:text-black transition"
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
on:click={() => {
on:click={() => {
showNextMessage(message);
showNextMessage(message);
}}
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
>
<path
<svg
fill-rule="evenodd"
xmlns="http://www.w3.org/2000/svg"
d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z"
fill="none"
clip-rule="evenodd"
viewBox="0 0 24 24"
/>
stroke="currentColor"
</svg>
stroke-width="2.5"
</button>
class="size-3.5"
</div>
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m8.25 4.5 7.5 7.5-7.5 7.5"
/>
</svg>
</button>
</div>
{/if}
{/if}
{/if}
{#if !readOnly}
{#if !readOnly}
<Tooltip content={$i18n.t('Edit')} placement="bottom">
<Tooltip content={$i18n.t('Edit')} placement="bottom">
<button
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
class="invisible group-hover:visible p-1
.5 hover:bg-black/5 dark:hover:bg-white/5
rounded
-lg
dark:hover:text-white hover:text-black transition edit-user-message-button"
on:click={() => {
on:click={() => {
editMessageHandler();
editMessageHandler();
}}
}}
...
@@ -277,7 +304,7 @@
...
@@ -277,7 +304,7 @@
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
fill="none"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
stroke-width="2"
stroke-width="2
.3
"
stroke="currentColor"
stroke="currentColor"
class="w-4 h-4"
class="w-4 h-4"
>
>
...
@@ -293,7 +320,7 @@
...
@@ -293,7 +320,7 @@
<Tooltip content={$i18n.t('Copy')} placement="bottom">
<Tooltip content={$i18n.t('Copy')} placement="bottom">
<button
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
class="invisible group-hover:visible p-1
.5 hover:bg-black/5 dark:hover:bg-white/5
rounded
-lg
dark:hover:text-white hover:text-black transition"
on:click={() => {
on:click={() => {
copyToClipboard(message.content);
copyToClipboard(message.content);
}}
}}
...
@@ -302,7 +329,7 @@
...
@@ -302,7 +329,7 @@
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
fill="none"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
stroke-width="2"
stroke-width="2
.3
"
stroke="currentColor"
stroke="currentColor"
class="w-4 h-4"
class="w-4 h-4"
>
>
...
@@ -340,6 +367,60 @@
...
@@ -340,6 +367,60 @@
</button>
</button>
</Tooltip>
</Tooltip>
{/if}
{/if}
{#if $settings?.chatBubble ?? true}
{#if siblings.length > 1}
<div class="flex self-center" dir="ltr">
<button
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
on:click={() => {
showPreviousMessage(message);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2.5"
class="size-3.5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 19.5 8.25 12l7.5-7.5"
/>
</svg>
</button>
<div class="text-sm tracking-widest font-semibold self-center dark:text-gray-100">
{siblings.indexOf(message.id) + 1}/{siblings.length}
</div>
<button
class="self-center p-1 hover:bg-black/5 dark:hover:bg-white/5 dark:hover:text-white hover:text-black rounded-md transition"
on:click={() => {
showNextMessage(message);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2.5"
class="size-3.5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m8.25 4.5 7.5 7.5-7.5 7.5"
/>
</svg>
</button>
</div>
{/if}
{/if}
</div>
</div>
</div>
</div>
{/if}
{/if}
...
...
src/lib/components/chat/ModelSelector.svelte
View file @
be5534c6
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
import { Collapsible } from 'bits-ui';
import { Collapsible } from 'bits-ui';
import { setDefaultModels } from '$lib/apis/configs';
import { setDefaultModels } from '$lib/apis/configs';
import { models, showSettings, settings, user } from '$lib/stores';
import { models, showSettings, settings, user
, mobile
} from '$lib/stores';
import { onMount, tick, getContext } from 'svelte';
import { onMount, tick, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
import Selector from './ModelSelector/Selector.svelte';
import Selector from './ModelSelector/Selector.svelte';
...
@@ -38,7 +38,7 @@
...
@@ -38,7 +38,7 @@
}
}
</script>
</script>
<div class="flex flex-col
mt-0.5 w-full
">
<div class="flex flex-col
w-full items-center md:items-start
">
{#each selectedModels as selectedModel, selectedModelIdx}
{#each selectedModels as selectedModel, selectedModelIdx}
<div class="flex w-full max-w-fit">
<div class="flex w-full max-w-fit">
<div class="overflow-hidden w-full">
<div class="overflow-hidden w-full">
...
@@ -108,7 +108,7 @@
...
@@ -108,7 +108,7 @@
{/each}
{/each}
</div>
</div>
{#if showSetDefault}
{#if showSetDefault
&& !$mobile
}
<div class="text-left mt-0.5 ml-1 text-[0.7rem] text-gray-500">
<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>
<button on:click={saveDefaultModel}> {$i18n.t('Set as default')}</button>
</div>
</div>
...
...
src/lib/components/chat/ModelSelector/Selector.svelte
View file @
be5534c6
...
@@ -10,7 +10,7 @@
...
@@ -10,7 +10,7 @@
import { cancelOllamaRequest, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
import { cancelOllamaRequest, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
import { user, MODEL_DOWNLOAD_POOL, models } from '$lib/stores';
import { user, MODEL_DOWNLOAD_POOL, models
, mobile
} from '$lib/stores';
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
import { capitalizeFirstLetter, getModels, splitStream } from '$lib/utils';
import { capitalizeFirstLetter, getModels, splitStream } from '$lib/utils';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
...
@@ -25,7 +25,7 @@
...
@@ -25,7 +25,7 @@
export let items = [{ value: 'mango', label: 'Mango' }];
export let items = [{ value: 'mango', label: 'Mango' }];
export let className = '
w-[3
2
rem]';
export let className = 'w-[3
0
rem]';
let show = false;
let show = false;
...
@@ -36,7 +36,7 @@
...
@@ -36,7 +36,7 @@
let ollamaVersion = null;
let ollamaVersion = null;
$: filteredItems = searchValue
$: filteredItems = searchValue
? items.filter((item) => item.value.includes(searchValue.toLowerCase()))
? items.filter((item) => item.value.
toLowerCase().
includes(searchValue.toLowerCase()))
: items;
: items;
const pullModelHandler = async () => {
const pullModelHandler = async () => {
...
@@ -201,10 +201,13 @@
...
@@ -201,10 +201,13 @@
<ChevronDown className=" self-center ml-2 size-3" strokeWidth="2.5" />
<ChevronDown className=" self-center ml-2 size-3" strokeWidth="2.5" />
</div>
</div>
</DropdownMenu.Trigger>
</DropdownMenu.Trigger>
<DropdownMenu.Content
<DropdownMenu.Content
class=" z-40 {className} max-w-[calc(100vw-1rem)] justify-start 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 "
class=" z-40 {$mobile
? `w-full`
: `${className}`} max-w-[calc(100vw-1rem)] justify-start rounded-xl bg-white dark:bg-gray-850 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-700/50 outline-none "
transition={flyAndScale}
transition={flyAndScale}
side={'bottom-start'}
side={
$mobile ? 'bottom' :
'bottom-start'}
sideOffset={4}
sideOffset={4}
>
>
<slot>
<slot>
...
@@ -224,11 +227,11 @@
...
@@ -224,11 +227,11 @@
<hr class="border-gray-100 dark:border-gray-800" />
<hr class="border-gray-100 dark:border-gray-800" />
{/if}
{/if}
<div class="px-3 my-2 max-h-
72
overflow-y-auto scrollbar-
none
">
<div class="px-3 my-2 max-h-
64
overflow-y-auto scrollbar-
hidden
">
{#each filteredItems as item}
{#each filteredItems as item}
<button
<button
aria-label="model-item"
aria-label="model-item"
class="flex w-full text-left 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-8
5
0 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
class="flex w-full text-left 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-8
0
0 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
on:click={() => {
on:click={() => {
value = item.value;
value = item.value;
...
@@ -312,7 +315,7 @@
...
@@ -312,7 +315,7 @@
{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user.role === 'admin'}
{#if !(searchValue.trim() in $MODEL_DOWNLOAD_POOL) && searchValue && ollamaVersion && $user.role === 'admin'}
<button
<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-8
5
0 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
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-8
0
0 rounded-lg cursor-pointer data-[highlighted]:bg-muted"
on:click={() => {
on:click={() => {
pullModelHandler();
pullModelHandler();
}}
}}
...
@@ -406,12 +409,12 @@
...
@@ -406,12 +409,12 @@
</DropdownMenu.Root>
</DropdownMenu.Root>
<style>
<style>
.scrollbar-
none
:active::-webkit-scrollbar-thumb,
.scrollbar-
hidden
:active::-webkit-scrollbar-thumb,
.scrollbar-
none
:focus::-webkit-scrollbar-thumb,
.scrollbar-
hidden
:focus::-webkit-scrollbar-thumb,
.scrollbar-
none
:hover::-webkit-scrollbar-thumb {
.scrollbar-
hidden
:hover::-webkit-scrollbar-thumb {
visibility: visible;
visibility: visible;
}
}
.scrollbar-
none
::-webkit-scrollbar-thumb {
.scrollbar-
hidden
::-webkit-scrollbar-thumb {
visibility: hidden;
visibility: hidden;
}
}
</style>
</style>
src/lib/components/chat/Settings/Account.svelte
View file @
be5534c6
...
@@ -71,7 +71,7 @@
...
@@ -71,7 +71,7 @@
</script>
</script>
<div class="flex flex-col h-full justify-between text-sm">
<div class="flex flex-col h-full justify-between text-sm">
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[2
2
rem]">
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[2
5
rem]">
<input
<input
id="profile-image-input"
id="profile-image-input"
bind:this={profileImageInputElement}
bind:this={profileImageInputElement}
...
@@ -127,7 +127,7 @@
...
@@ -127,7 +127,7 @@
if (
if (
files.length > 0 &&
files.length > 0 &&
['image/gif', 'image/jpeg', 'image/png'].includes(files[0]['type'])
['image/gif',
'image/webp',
'image/jpeg', 'image/png'].includes(files[0]['type'])
) {
) {
reader.readAsDataURL(files[0]);
reader.readAsDataURL(files[0]);
}
}
...
...
src/lib/components/chat/Settings/Advanced.svelte
View file @
be5534c6
...
@@ -84,7 +84,7 @@
...
@@ -84,7 +84,7 @@
{#if keepAlive !== null}
{#if keepAlive !== null}
<div class="flex mt-1 space-x-2">
<div class="flex mt-1 space-x-2">
<input
<input
class="w-full rounded py-
1.5
px-4 text-sm dark:text-gray-300 dark:bg-gray-8
0
0 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-8
5
0 outline-none"
type="text"
type="text"
placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
bind:value={keepAlive}
bind:value={keepAlive}
...
...
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
View file @
be5534c6
...
@@ -27,7 +27,7 @@
...
@@ -27,7 +27,7 @@
<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Seed')}</div>
<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Seed')}</div>
<div class=" flex-1 self-center">
<div class=" flex-1 self-center">
<input
<input
class="w-full rounded py-
1.5
px-4 text-sm dark:text-gray-300 dark:bg-gray-8
0
0 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-8
5
0 outline-none"
type="number"
type="number"
placeholder="Enter Seed"
placeholder="Enter Seed"
bind:value={options.seed}
bind:value={options.seed}
...
@@ -43,7 +43,7 @@
...
@@ -43,7 +43,7 @@
<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Stop Sequence')}</div>
<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Stop Sequence')}</div>
<div class=" flex-1 self-center">
<div class=" flex-1 self-center">
<input
<input
class="w-full rounded py-
1.5
px-4 text-sm dark:text-gray-300 dark:bg-gray-8
0
0 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-8
5
0 outline-none"
type="text"
type="text"
placeholder={$i18n.t('Enter stop sequence')}
placeholder={$i18n.t('Enter stop sequence')}
bind:value={options.stop}
bind:value={options.stop}
...
...
src/lib/components/chat/Settings/Audio.svelte
View file @
be5534c6
...
@@ -147,7 +147,7 @@
...
@@ -147,7 +147,7 @@
dispatch
(
'save'
);
dispatch
(
'save'
);
}}
}}
>
>
<
div
class
=
" space-y-3 pr-1.5 overflow-y-scroll max-h-[2
2
rem]"
>
<
div
class
=
" space-y-3 pr-1.5 overflow-y-scroll max-h-[2
5
rem]"
>
<
div
>
<
div
>
<
div
class
=
" mb-1 text-sm font-medium"
>{$
i18n
.
t
(
'STT Settings'
)}</
div
>
<
div
class
=
" mb-1 text-sm font-medium"
>{$
i18n
.
t
(
'STT Settings'
)}</
div
>
...
@@ -345,7 +345,7 @@
...
@@ -345,7 +345,7 @@
{/
if
}
{/
if
}
</
div
>
</
div
>
<
div
class
=
"flex justify-end
pt-3
text-sm font-medium"
>
<
div
class
=
"flex justify-end text-sm font-medium"
>
<
button
<
button
class
=
" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
class
=
" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type
=
"submit"
type
=
"submit"
...
...
src/lib/components/chat/Settings/Connections.svelte
View file @
be5534c6
...
@@ -5,28 +5,27 @@
...
@@ -5,28 +5,27 @@
import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
import {
import {
getOpenAIConfig,
getOpenAIKeys,
getOpenAIKeys,
getOpenAIUrls,
getOpenAIUrls,
updateOpenAIConfig,
updateOpenAIKeys,
updateOpenAIKeys,
updateOpenAIUrls
updateOpenAIUrls
} from '$lib/apis/openai';
} from '$lib/apis/openai';
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
import Switch from '$lib/components/common/Switch.svelte';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
export let getModels: Function;
export let getModels: Function;
// External
// External
let OLLAMA_BASE_URL = '';
let OLLAMA_BASE_URLS = [''];
let OLLAMA_BASE_URLS = [''];
let OPENAI_API_KEY = '';
let OPENAI_API_BASE_URL = '';
let OPENAI_API_KEYS = [''];
let OPENAI_API_KEYS = [''];
let OPENAI_API_BASE_URLS = [''];
let OPENAI_API_BASE_URLS = [''];
let
showOpenA
I = false;
let
ENABLE_OPENAI_AP
I = false;
const updateOpenAIHandler = async () => {
const updateOpenAIHandler = async () => {
OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS);
OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS);
...
@@ -52,6 +51,10 @@
...
@@ -52,6 +51,10 @@
onMount(async () => {
onMount(async () => {
if ($user.role === 'admin') {
if ($user.role === 'admin') {
OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token);
OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token);
const config = await getOpenAIConfig(localStorage.token);
ENABLE_OPENAI_API = config.ENABLE_OPENAI_API;
OPENAI_API_BASE_URLS = await getOpenAIUrls(localStorage.token);
OPENAI_API_BASE_URLS = await getOpenAIUrls(localStorage.token);
OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token);
OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token);
}
}
...
@@ -65,21 +68,23 @@
...
@@ -65,21 +68,23 @@
dispatch('save');
dispatch('save');
}}
}}
>
>
<div class=" pr-1.5 overflow-y-scroll max-h-[2
2
rem] space-y-3">
<div class=" pr-1.5 overflow-y-scroll max-h-[2
5
rem] space-y-3">
<div class=" space-y-3">
<div class=" space-y-3">
<div class="mt-2 space-y-2 pr-1.5">
<div class="mt-2 space-y-2 pr-1.5">
<div class="flex justify-between items-center text-sm">
<div class="flex justify-between items-center text-sm">
<div class=" font-medium">{$i18n.t('OpenAI API')}</div>
<div class=" font-medium">{$i18n.t('OpenAI API')}</div>
<button
class=" text-xs font-medium text-gray-500"
<div class="mt-1">
type="button"
<Switch
on:click={() => {
bind:state={ENABLE_OPENAI_API}
showOpenAI = !showOpenAI;
on:change={async () => {
}}>{showOpenAI ? $i18n.t('Hide') : $i18n.t('Show')}</button
updateOpenAIConfig(localStorage.token, ENABLE_OPENAI_API);
>
}}
/>
</div>
</div>
</div>
{#if
showOpenA
I}
{#if
ENABLE_OPENAI_AP
I}
<div class="flex flex-col gap-1">
<div class="flex flex-col gap-1">
{#each OPENAI_API_BASE_URLS as url, idx}
{#each OPENAI_API_BASE_URLS as url, idx}
<div class="flex w-full gap-2">
<div class="flex w-full gap-2">
...
...
src/lib/components/chat/Settings/General.svelte
View file @
be5534c6
...
@@ -130,7 +130,7 @@
...
@@ -130,7 +130,7 @@
</script>
</script>
<div class="flex flex-col h-full justify-between text-sm">
<div class="flex flex-col h-full justify-between text-sm">
<div class=" pr-1.5 overflow-y-scroll max-h-[2
2
rem]">
<div class=" pr-1.5 overflow-y-scroll max-h-[2
5
rem]">
<div class="">
<div class="">
<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Settings')}</div>
<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Settings')}</div>
...
@@ -253,7 +253,7 @@
...
@@ -253,7 +253,7 @@
{#if keepAlive !== null}
{#if keepAlive !== null}
<div class="flex mt-1 space-x-2">
<div class="flex mt-1 space-x-2">
<input
<input
class="w-full rounded py-
1.5
px-4 text-sm dark:text-gray-300 dark:bg-gray-8
0
0 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-8
5
0 outline-none"
type="text"
type="text"
placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
placeholder={$i18n.t("e.g. '30s','10m'. Valid time units are 's', 'm', 'h'.")}
bind:value={keepAlive}
bind:value={keepAlive}
...
...
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