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
3c9fc785
Commit
3c9fc785
authored
Apr 30, 2024
by
Timothy J. Baek
Browse files
fix: styling
parent
bf2ff47d
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
499 additions
and
499 deletions
+499
-499
src/lib/components/chat/MessageInput.svelte
src/lib/components/chat/MessageInput.svelte
+470
-467
src/lib/components/chat/Messages.svelte
src/lib/components/chat/Messages.svelte
+1
-1
src/lib/components/layout/Sidebar.svelte
src/lib/components/layout/Sidebar.svelte
+15
-18
src/lib/stores/index.ts
src/lib/stores/index.ts
+2
-0
src/routes/(app)/+page.svelte
src/routes/(app)/+page.svelte
+1
-2
src/routes/(app)/c/[id]/+page.svelte
src/routes/(app)/c/[id]/+page.svelte
+10
-11
No files found.
src/lib/components/chat/MessageInput.svelte
View file @
3c9fc785
<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 { settings } from '$lib/stores';
import { settings
, showSidebar
} from '$lib/stores';
import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
import { blobToFile, calculateSHA256, findWordIndices } from '$lib/utils';
import Prompts from './MessageInput/PromptCommands.svelte';
import Prompts from './MessageInput/PromptCommands.svelte';
...
@@ -291,6 +291,7 @@
...
@@ -291,6 +291,7 @@
};
};
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');
...
@@ -389,142 +390,216 @@
...
@@ -389,142 +390,216 @@
</div>
</div>
{/if}
{/if}
<div class="w-full absolute bottom-0">
<div class="fixed bottom-0 {$showSidebar ? 'left-0 lg:left-[260px]' : 'left-0'} right-0">
<div class="px-2.5 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
<div class="w-full">
<div class="flex flex-col max-w-3xl w-full">
<div class=" px-2.5 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
<div class="relative">
<div class="flex flex-col max-w-3xl w-full">
{#if autoScroll === false && messages.length > 0}
<div class="relative">
<div class=" absolute -top-12 left-0 right-0 flex justify-center">
{#if autoScroll === false && messages.length > 0}
<button
<div class=" absolute -top-12 left-0 right-0 flex justify-center">
class=" bg-white border border-gray-100 dark:border-none dark:bg-white/20 p-1.5 rounded-full"
<button
on:click={() => {
class=" bg-white border border-gray-100 dark:border-none dark:bg-white/20 p-1.5 rounded-full"
autoScroll = true;
on:click={() => {
scrollToBottom();
autoScroll = true;
}}
scrollToBottom();
>
}}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
>
<path
<svg
fill-rule="evenodd"
xmlns="http://www.w3.org/2000/svg"
d="M10 3a.75.75 0 01.75.75v10.638l3.96-4.158a.75.75 0 111.08 1.04l-5.25 5.5a.75.75 0 01-1.08 0l-5.25-5.5a.75.75 0 111.08-1.04l3.96 4.158V3.75A.75.75 0 0110 3z"
viewBox="0 0 20 20"
clip-rule="evenodd"
fill="currentColor"
/>
class="w-5 h-5"
</svg>
>
</button>
<path
</div>
fill-rule="evenodd"
{/if}
d="M10 3a.75.75 0 01.75.75v10.638l3.96-4.158a.75.75 0 111.08 1.04l-5.25 5.5a.75.75 0 01-1.08 0l-5.25-5.5a.75.75 0 111.08-1.04l3.96 4.158V3.75A.75.75 0 0110 3z"
</div>
clip-rule="evenodd"
/>
</svg>
</button>
</div>
{/if}
</div>
<div class="w-full relative">
<div class="w-full relative">
{#if prompt.charAt(0) === '/'}
{#if prompt.charAt(0) === '/'}
<Prompts bind:this={promptsElement} bind:prompt />
<Prompts bind:this={promptsElement} bind:prompt />
{:else if prompt.charAt(0) === '#'}
{:else if prompt.charAt(0) === '#'}
<Documents
<Documents
bind:this={documentsElement}
bind:this={documentsElement}
bind:prompt
bind:prompt
on:url={(e) => {
on:url={(e) => {
console.log(e);
console.log(e);
uploadWeb(e.detail);
uploadWeb(e.detail);
}}
}}
on:select={(e) => {
on:select={(e) => {
console.log(e);
console.log(e);
files = [
files = [
...files,
...files,
{
{
type: e?.detail?.type ?? 'doc',
type: e?.detail?.type ?? 'doc',
...e.detail,
...e.detail,
upload_status: true
upload_status: true
}
}
];
];
}}
}}
/>
/>
{:else if prompt.charAt(0) === '@'}
{:else if prompt.charAt(0) === '@'}
<Models
<Models
bind:this={modelsElement}
bind:this={modelsElement}
bind:prompt
bind:prompt
bind:user
bind:user
bind:chatInputPlaceholder
bind:chatInputPlaceholder
{messages}
{messages}
/>
/>
{/if}
{/if}
<!-- {#if messages.length == 0 && suggestionPrompts.length !== 0}
<!-- {#if messages.length == 0 && suggestionPrompts.length !== 0}
<Suggestions {suggestionPrompts} {submitPrompt} />
<Suggestions {suggestionPrompts} {submitPrompt} />
{/if} -->
{/if} -->
</div>
</div>
</div>
</div>
</div>
</div>
<div class="bg-white dark:bg-gray-900">
<div class="bg-white dark:bg-gray-900">
<div class="max-w-3xl px-2.5 mx-auto inset-x-0">
<div class="max-w-3xl px-2.5 mx-auto inset-x-0">
<div class=" pb-2">
<div class=" pb-2">
<input
<input
bind:this={filesInputElement}
bind:this={filesInputElement}
bind:files={inputFiles}
bind:files={inputFiles}
type="file"
type="file"
hidden
hidden
multiple
multiple
on:change={async () => {
on:change={async () => {
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/jpeg', 'image/png'].includes(file['type'])) {
let reader = new FileReader();
let reader = new FileReader();
reader.onload = (event) => {
reader.onload = (event) => {
files = [
files = [
...files,
...files,
{
{
type: 'image',
type: 'image',
url: `${event.target.result}`
url: `${event.target.result}`
}
}
];
];
inputFiles = null;
inputFiles = null;
filesInputElement.value = '';
};
reader.readAsDataURL(file);
} else if (
SUPPORTED_FILE_TYPE.includes(file['type']) ||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
) {
uploadDoc(file);
filesInputElement.value = '';
filesInputElement.value = '';
};
} else {
reader.readAsDataURL(file);
toast.error(
} else if (
$i18n.t(
SUPPORTED_FILE_TYPE.includes(file['type']) ||
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
{ file_type: file['type'] }
) {
)
uploadDoc(file);
);
filesInputElement.value = '';
uploadDoc(file);
} else {
filesInputElement.value = '';
toast.error(
}
$i18n.t(
});
`Unknown File Type '{{file_type}}', but accepting and treating as plain text`,
} else {
{ file_type: file['type'] }
toast.error($i18n.t(`File not found.`));
)
}
);
}}
uploadDoc(file);
/>
filesInputElement.value = '';
<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"
});
on:submit|preventDefault={() => {
} else {
submitPrompt(prompt, user);
toast.error($i18n.t(`File not found.`));
}}
}
>
}}
{#if files.length > 0}
/>
<div class="mx-2 mt-2 mb-1 flex flex-wrap gap-2">
<form
{#each files as file, fileIdx}
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"
<div class=" relative group">
on:submit|preventDefault={() => {
{#if file.type === 'image'}
submitPrompt(prompt, user);
<img src={file.url} alt="input" class=" h-16 w-16 rounded-xl object-cover" />
}}
{:else if file.type === 'doc'}
>
<div
{#if files.length > 0}
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"
<div class="mx-2 mt-2 mb-1 flex flex-wrap gap-2">
>
{#each files as file, fileIdx}
<div class="p-2.5 bg-red-400 text-white rounded-lg">
<div class=" relative group">
{#if file.upload_status}
{#if file.type === 'image'}
<svg
<img src={file.url} alt="input" class=" h-16 w-16 rounded-xl object-cover" />
xmlns="http://www.w3.org/2000/svg"
{:else if file.type === 'doc'}
viewBox="0 0 24 24"
<div
fill="currentColor"
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"
class="w-6 h-6"
>
>
<div class="p-2.5 bg-red-400 text-white rounded-lg">
<path
{#if file.upload_status}
fill-rule="evenodd"
d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
clip-rule="evenodd"
/>
<path
d="M12.971 1.816A5.23 5.23 0 0 1 14.25 5.25v1.875c0 .207.168.375.375.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963Z"
/>
</svg>
{:else}
<svg
class=" w-6 h-6 translate-y-[0.5px]"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_qM83 {
animation: spinner_8HQG 1.05s infinite;
}
.spinner_oXPr {
animation-delay: 0.1s;
}
.spinner_ZTLf {
animation-delay: 0.2s;
}
@keyframes spinner_8HQG {
0%,
57.14% {
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
transform: translate(0);
}
28.57% {
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
transform: translateY(-6px);
}
100% {
transform: translate(0);
}
}
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
class="spinner_qM83 spinner_oXPr"
cx="12"
cy="12"
r="2.5"
/><circle
class="spinner_qM83 spinner_ZTLf"
cx="20"
cy="12"
r="2.5"
/></svg
>
{/if}
</div>
<div class="flex flex-col justify-center -space-y-0.5">
<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
{file.name}
</div>
<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
</div>
</div>
{:else if file.type === 'collection'}
<div
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"
>
<div class="p-2.5 bg-red-400 text-white rounded-lg">
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
viewBox="0 0 24 24"
...
@@ -532,421 +607,349 @@
...
@@ -532,421 +607,349 @@
class="w-6 h-6"
class="w-6 h-6"
>
>
<path
<path
fill-rule="evenodd"
d="M7.5 3.375c0-1.036.84-1.875 1.875-1.875h.375a3.75 3.75 0 0 1 3.75 3.75v1.875C13.5 8.161 14.34 9 15.375 9h1.875A3.75 3.75 0 0 1 21 12.75v3.375C21 17.16 20.16 18 19.125 18h-9.75A1.875 1.875 0 0 1 7.5 16.125V3.375Z"
d="M5.625 1.5c-1.036 0-1.875.84-1.875 1.875v17.25c0 1.035.84 1.875 1.875 1.875h12.75c1.035 0 1.875-.84 1.875-1.875V12.75A3.75 3.75 0 0 0 16.5 9h-1.875a1.875 1.875 0 0 1-1.875-1.875V5.25A3.75 3.75 0 0 0 9 1.5H5.625ZM7.5 15a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 7.5 15Zm.75 2.25a.75.75 0 0 0 0 1.5H12a.75.75 0 0 0 0-1.5H8.25Z"
clip-rule="evenodd"
/>
/>
<path
<path
d="M1
2.971 1.816
A5.23 5.23 0 0
1
1
4
.25
5.25v1.875c0 .207.168
.375.375
.375H16.5a5.23 5.23 0 0 1 3.434 1.279 9.768 9.768 0 0 0-6.963-6.963
Z"
d="M1
5 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963
A5.23 5.23 0 0
0
1
7
.25
7.5h-1.875A.375.375 0 0 1 15 7.125V5.25ZM4.875 6H6v10.125A3
.375
3
.375
0 0 0 9.375 19.5H16.5v1.125c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 0 1 3 20.625V7.875C3 6.839 3.84 6 4.875 6
Z"
/>
/>
</svg>
</svg>
{:else}
<svg
class=" w-6 h-6 translate-y-[0.5px]"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_qM83 {
animation: spinner_8HQG 1.05s infinite;
}
.spinner_oXPr {
animation-delay: 0.1s;
}
.spinner_ZTLf {
animation-delay: 0.2s;
}
@keyframes spinner_8HQG {
0%,
57.14% {
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
transform: translate(0);
}
28.57% {
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
transform: translateY(-6px);
}
100% {
transform: translate(0);
}
}
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
class="spinner_qM83 spinner_oXPr"
cx="12"
cy="12"
r="2.5"
/><circle
class="spinner_qM83 spinner_ZTLf"
cx="20"
cy="12"
r="2.5"
/></svg
>
{/if}
</div>
<div class="flex flex-col justify-center -space-y-0.5">
<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
{file.name}
</div>
</div>
<div class=" text-gray-500 text-sm">{$i18n.t('Document')}</div>
<div class="flex flex-col justify-center -space-y-0.5">
<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
{file?.title ?? `#${file.name}`}
</div>
<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
</div>
</div>
</div>
</div>
{/if}
{:else if file.type === 'collection'}
<div
<div class=" absolute -top-1 -right-1">
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"
<button
>
class=" bg-gray-400 text-white border border-white rounded-full group-hover:visible invisible transition"
<div class="p-2.5 bg-red-400 text-white rounded-lg">
type="button"
on:click={() => {
files.splice(fileIdx, 1);
files = files;
}}
>
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 2
4
2
4
"
viewBox="0 0 2
0
2
0
"
fill="currentColor"
fill="currentColor"
class="w-
6
h-
6
"
class="w-
4
h-
4
"
>
>
<path
<path
d="M7.5 3.375c0-1.036.84-1.875 1.875-1.875h.375a3.75 3.75 0 0 1 3.75 3.75v1.875C13.5 8.161 14.34 9 15.375 9h1.875A3.75 3.75 0 0 1 21 12.75v3.375C21 17.16 20.16 18 19.125 18h-9.75A1.875 1.875 0 0 1 7.5 16.125V3.375Z"
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
d="M15 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 17.25 7.5h-1.875A.375.375 0 0 1 15 7.125V5.25ZM4.875 6H6v10.125A3.375 3.375 0 0 0 9.375 19.5H16.5v1.125c0 1.035-.84 1.875-1.875 1.875h-9.75A1.875 1.875 0 0 1 3 20.625V7.875C3 6.839 3.84 6 4.875 6Z"
/>
/>
</svg>
</svg>
</div>
</button>
<div class="flex flex-col justify-center -space-y-0.5">
<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1">
{file?.title ?? `#${file.name}`}
</div>
<div class=" text-gray-500 text-sm">{$i18n.t('Collection')}</div>
</div>
</div>
</div>
{/if}
</div>
{/each}
</div>
{/if}
<div class=" absolute -top-1 -right-1">
<div class=" flex">
{#if fileUploadEnabled}
<div class=" self-end mb-2 ml-1">
<Tooltip content={$i18n.t('Upload files')}>
<button
<button
class="
bg-gray-
4
00 text-
white border border-white rounded-full group-hover:visible invisible transition
"
class="
bg-gray-50 hover:
bg-gray-
1
00 text-
gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5
"
type="button"
type="button"
on:click={() => {
on:click={() => {
files.splice(fileIdx, 1);
filesInputElement.click();
files = files;
}}
}}
>
>
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0
20 20
"
viewBox="0 0
16 16
"
fill="currentColor"
fill="currentColor"
class="w-
4 h-4
"
class="w-
[1.2rem] h-[1.2rem]
"
>
>
<path
<path
d="M
6.28 5.22
a.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
"
d="M
8.75 3.75
a.75.75 0 0
0-1.
5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z
"
/>
/>
</svg>
</svg>
</button>
</button>
</
div
>
</
Tooltip
>
</div>
</div>
{/each}
{/if}
</div>
{/if}
<div class=" flex">
{#if fileUploadEnabled}
<div class=" self-end mb-2 ml-1">
<Tooltip content={$i18n.t('Upload files')}>
<button
class="bg-gray-50 hover:bg-gray-100 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
type="button"
on:click={() => {
filesInputElement.click();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-[1.2rem] h-[1.2rem]"
>
<path
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
/>
</svg>
</button>
</Tooltip>
</div>
{/if}
<textarea
<textarea
id="chat-textarea"
id="chat-textarea"
bind:this={chatTextAreaElement}
bind:this={chatTextAreaElement}
class=" dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
class=" dark:bg-gray-900 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 !== ''
? chatInputPlaceholder
? chatInputPlaceholder
: isRecording
: isRecording
? $i18n.t('Listening...')
? $i18n.t('Listening...')
: $i18n.t('Send a Message')}
: $i18n.t('Send a Message')}
bind:value={prompt}
bind:value={prompt}
on:keypress={(e) => {
on:keypress={(e) => {
if (window.innerWidth > 1024) {
if (window.innerWidth > 1024) {
if (e.keyCode == 13 && !e.shiftKey) {
if (e.keyCode == 13 && !e.shiftKey) {
e.preventDefault();
e.preventDefault();
}
}
if (prompt !== '' && e.keyCode == 13 && !e.shiftKey) {
if (prompt !== '' && e.keyCode == 13 && !e.shiftKey) {
submitPrompt(prompt, user);
submitPrompt(prompt, user);
}
}
}
}
}}
}}
on:keydown={async (e) => {
on:keydown={async (e) => {
const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
// Check if Ctrl + R is pressed
// Check if Ctrl + R is pressed
if (prompt === '' && isCtrlPressed && e.key.toLowerCase() === 'r') {
if (prompt === '' && isCtrlPressed && e.key.toLowerCase() === 'r') {
e.preventDefault();
e.preventDefault();
console.log('regenerate');
console.log('regenerate');
const regenerateButton = [
const regenerateButton = [
...document.getElementsByClassName('regenerate-response-button')
...document.getElementsByClassName('regenerate-response-button')
]?.at(-1);
]?.at(-1);
regenerateButton?.click();
regenerateButton?.click();
}
}
if (prompt === '' && e.key == 'ArrowUp') {
if (prompt === '' && e.key == 'ArrowUp') {
e.preventDefault();
e.preventDefault();
const userMessageElement = [
const userMessageElement = [
...document.getElementsByClassName('user-message')
...document.getElementsByClassName('user-message')
]?.at(-1);
]?.at(-1);
const editButton = [
const editButton = [
...document.getElementsByClassName('edit-user-message-button')
...document.getElementsByClassName('edit-user-message-button')
]?.at(-1);
]?.at(-1);
console.log(userMessageElement);
console.log(userMessageElement);
userMessageElement.scrollIntoView({ block: 'center' });
userMessageElement.scrollIntoView({ block: 'center' });
editButton?.click();
editButton?.click();
}
}
if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'ArrowUp') {
if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'ArrowUp') {
e.preventDefault();
e.preventDefault();
(promptsElement || documentsElement || modelsElement).selectUp();
(promptsElement || documentsElement || modelsElement).selectUp();
const commandOptionButton = [
const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button')
...document.getElementsByClassName('selected-command-option-button')
]?.at(-1);
]?.at(-1);
commandOptionButton.scrollIntoView({ block: 'center' });
commandOptionButton.scrollIntoView({ block: 'center' });
}
}
if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'ArrowDown') {
if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'ArrowDown') {
e.preventDefault();
e.preventDefault();
(promptsElement || documentsElement || modelsElement).selectDown();
(promptsElement || documentsElement || modelsElement).selectDown();
const commandOptionButton = [
const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button')
...document.getElementsByClassName('selected-command-option-button')
]?.at(-1);
]?.at(-1);
commandOptionButton.scrollIntoView({ block: 'center' });
commandOptionButton.scrollIntoView({ block: 'center' });
}
}
if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Enter') {
if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Enter') {
e.preventDefault();
e.preventDefault();
const commandOptionButton = [
const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button')
...document.getElementsByClassName('selected-command-option-button')
]?.at(-1);
]?.at(-1);
if (commandOptionButton) {
if (commandOptionButton) {
commandOptionButton?.click();
commandOptionButton?.click();
} else {
} else {
document.getElementById('send-message-button')?.click();
document.getElementById('send-message-button')?.click();
}
}
}
}
if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Tab') {
if (['/', '#', '@'].includes(prompt.charAt(0)) && e.key === 'Tab') {
e.preventDefault();
e.preventDefault();
const commandOptionButton = [
const commandOptionButton = [
...document.getElementsByClassName('selected-command-option-button')
...document.getElementsByClassName('selected-command-option-button')
]?.at(-1);
]?.at(-1);
commandOptionButton?.click();
commandOptionButton?.click();
} else if (e.key === 'Tab') {
} else if (e.key === 'Tab') {
const words = findWordIndices(prompt);
const words = findWordIndices(prompt);
if (words.length > 0) {
if (words.length > 0) {
const word = words.at(0);
const word = words.at(0);
const fullPrompt = prompt;
const fullPrompt = prompt;
prompt = prompt.substring(0, word?.endIndex + 1);
prompt = prompt.substring(0, word?.endIndex + 1);
await tick();
await tick();
e.target.scrollTop = e.target.scrollHeight;
e.target.scrollTop = e.target.scrollHeight;
prompt = fullPrompt;
prompt = fullPrompt;
await tick();
await tick();
e.preventDefault();
e.preventDefault();
e.target.setSelectionRange(word?.startIndex, word.endIndex + 1);
e.target.setSelectionRange(word?.startIndex, word.endIndex + 1);
}
}
}}
rows="1"
on:input={(e) => {
e.target.style.height = '';
e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
user = null;
}}
on:focus={(e) => {
e.target.style.height = '';
e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
}}
on:paste={(e) => {
const clipboardData = e.clipboardData || window.clipboardData;
if (clipboardData && clipboardData.items) {
for (const item of clipboardData.items) {
if (item.type.indexOf('image') !== -1) {
const blob = item.getAsFile();
const reader = new FileReader();
reader.onload = function (e) {
files = [
...files,
{
type: 'image',
url: `${e.target.result}`
}
];
};
reader.readAsDataURL(blob);
}
}
}
}
}
}}
}}
rows="1"
/>
on:input={(e) => {
e.target.style.height = '';
<div class="self-end mb-2 flex space-x-1 mr-1">
e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
{#if messages.length == 0 || messages.at(-1).done == true}
user = null;
<Tooltip content={$i18n.t('Record voice')}>
}}
{#if speechRecognitionEnabled}
on:focus={(e) => {
<button
e.target.style.height = '';
id="voice-input-button"
e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
class=" text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-full p-1.5 mr-0.5 self-center"
}}
type="button"
on:paste={(e) => {
on:click={() => {
const clipboardData = e.clipboardData || window.clipboardData;
speechRecognitionHandler();
}}
if (clipboardData && clipboardData.items) {
>
for (const item of clipboardData.items) {
{#if isRecording}
if (item.type.indexOf('image') !== -1) {
<svg
const blob = item.getAsFile();
class=" w-5 h-5 translate-y-[0.5px]"
const reader = new FileReader();
fill="currentColor"
viewBox="0 0 24 24"
reader.onload = function (e) {
xmlns="http://www.w3.org/2000/svg"
files = [
><style>
...files,
.spinner_qM83 {
{
animation: spinner_8HQG 1.05s infinite;
type: 'image',
url: `${e.target.result}`
}
}
.spinner_oXPr {
];
animation-delay: 0.1s;
};
}
.spinner_ZTLf {
reader.readAsDataURL(blob);
animation-delay: 0.2s;
}
}
}
@keyframes spinner_8HQG {
}
0%,
}}
57.14% {
/>
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
transform: translate(0);
<div class="self-end mb-2 flex space-x-1 mr-1">
{#if messages.length == 0 || messages.at(-1).done == true}
<Tooltip content={$i18n.t('Record voice')}>
{#if speechRecognitionEnabled}
<button
id="voice-input-button"
class=" text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-850 transition rounded-full p-1.5 mr-0.5 self-center"
type="button"
on:click={() => {
speechRecognitionHandler();
}}
>
{#if isRecording}
<svg
class=" w-5 h-5 translate-y-[0.5px]"
fill="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_qM83 {
animation: spinner_8HQG 1.05s infinite;
}
}
28.57% {
.spinner_oXPr {
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
animation-delay: 0.1s;
transform: translateY(-6px);
}
}
100%
{
.spinner_ZTLf
{
transform: translate(0)
;
animation-delay: 0.2s
;
}
}
}
@keyframes spinner_8HQG {
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
0%,
class="spinner_qM83 spinner_oXPr"
57.14% {
cx="12"
animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1);
cy="12"
transform: translate(0);
r="2.5"
}
/><circle
28.57% {
class="spinner_qM83 spinner_ZTLf"
animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33);
cx="20"
transform: translateY(-6px);
cy="12"
}
r="2.5"
100% {
/></svg
transform: translate(0);
>
}
{:else}
}
<svg
</style><circle class="spinner_qM83" cx="4" cy="12" r="2.5" /><circle
xmlns="http://www.w3.org/2000/svg"
class="spinner_qM83 spinner_oXPr"
viewBox="0 0 20 20"
cx="12"
fill="currentColor"
cy="12"
class="w-5 h-5 translate-y-[0.5px]"
r="2.5"
>
/><circle
<path d="M7 4a3 3 0 016 0v6a3 3 0 11-6 0V4z" />
class="spinner_qM83 spinner_ZTLf"
<path
cx="20"
d="M5.5 9.643a.75.75 0 00-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-1.5v-1.546A6.001 6.001 0 0016 10v-.357a.75.75 0 00-1.5 0V10a4.5 4.5 0 01-9 0v-.357z"
cy="12"
/>
r="2.5"
</svg>
/></svg
{/if}
>
</button>
{:else}
{/if}
<svg
</Tooltip>
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5 translate-y-[0.5px]"
>
<path d="M7 4a3 3 0 016 0v6a3 3 0 11-6 0V4z" />
<path
d="M5.5 9.643a.75.75 0 00-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-1.5v-1.546A6.001 6.001 0 0016 10v-.357a.75.75 0 00-1.5 0V10a4.5 4.5 0 01-9 0v-.357z"
/>
</svg>
{/if}
</button>
{/if}
</Tooltip>
<Tooltip content={$i18n.t('Send message')}>
<Tooltip content={$i18n.t('Send message')}>
<button
id="send-message-button"
class="{prompt !== ''
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
: 'text-white bg-gray-100 dark:text-gray-900 dark:bg-gray-800 disabled'} transition rounded-full p-1.5 self-center"
type="submit"
disabled={prompt === ''}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-5 h-5"
>
<path
fill-rule="evenodd"
d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
clip-rule="evenodd"
/>
</svg>
</button>
</Tooltip>
{:else}
<button
<button
id="send-message-button"
class="bg-white hover:bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
class="{prompt !== ''
on:click={stopResponse}
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
: 'text-white bg-gray-100 dark:text-gray-900 dark:bg-gray-800 disabled'} transition rounded-full p-1.5 self-center"
type="submit"
disabled={prompt === ''}
>
>
<svg
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0
16 16
"
viewBox="0 0
24 24
"
fill="currentColor"
fill="currentColor"
class="w-5 h-5"
class="w-5 h-5"
>
>
<path
<path
fill-rule="evenodd"
fill-rule="evenodd"
d="M
8 14a
.75.75
0 0 1
-.75
-
.75
V
4.
56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75
.75 0
0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z
"
d="M
2.25 12c0-5.385 4.365-9
.75
9
.75-
9
.75
s9
.75
4.
365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm6-2.438c0-.724.588-1.312 1.313-1.312h4.874c
.7
2
5 0
1.313.588 1.313 1.313v4.874c0 .725-.588 1.313-1.313 1.313H9.564a1.312 1.312 0 01-1.313-1.313V9.564z
"
clip-rule="evenodd"
clip-rule="evenodd"
/>
/>
</svg>
</svg>
</button>
</button>
</Tooltip>
{/if}
{:else}
</div>
<button
class="bg-white hover:bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-800 transition rounded-full p-1.5"
on:click={stopResponse}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="w-5 h-5"
>
<path
fill-rule="evenodd"
d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm6-2.438c0-.724.588-1.312 1.313-1.312h4.874c.725 0 1.313.588 1.313 1.313v4.874c0 .725-.588 1.313-1.313 1.313H9.564a1.312 1.312 0 01-1.313-1.313V9.564z"
clip-rule="evenodd"
/>
</svg>
</button>
{/if}
</div>
</div>
</div>
</form>
</form>
<div class="mt-1.5 text-xs text-gray-500 text-center">
<div class="mt-1.5 text-xs text-gray-500 text-center">
{$i18n.t('LLMs can make mistakes. Verify important information.')}
{$i18n.t('LLMs can make mistakes. Verify important information.')}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...
...
src/lib/components/chat/Messages.svelte
View file @
3c9fc785
...
@@ -297,7 +297,7 @@
...
@@ -297,7 +297,7 @@
}}
}}
/>
/>
{:else}
{:else}
<div class="pt-2 pb-28">
<div class="
{$settings?.fullScreenMode ?? null ? 'w-full' : 'mx-auto'}
pt-2 pb-28">
{#key chatId}
{#key chatId}
{#each messages as message, messageIdx}
{#each messages as message, messageIdx}
<div class=" w-full">
<div class=" w-full">
...
...
src/lib/components/layout/Sidebar.svelte
View file @
3c9fc785
<script lang="ts">
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { goto } from '$app/navigation';
import { user, chats, settings, showSettings, chatId, tags, showSidebar } from '$lib/stores';
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
import { goto, invalidateAll } from '$app/navigation';
import { page } from '$app/stores';
import { user, chats, settings, showSettings, chatId, tags } from '$lib/stores';
import { onMount, getContext } from 'svelte';
import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
const i18n = getContext('i18n');
...
@@ -30,6 +24,7 @@
...
@@ -30,6 +24,7 @@
import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
const BREAKPOINT = 1024;
const BREAKPOINT = 1024;
let show = false;
let show = false;
let navElement;
let navElement;
...
@@ -50,7 +45,7 @@
...
@@ -50,7 +45,7 @@
let isEditing = false;
let isEditing = false;
onMount(async () => {
onMount(async () => {
show
=
window.innerWidth > BREAKPOINT;
show
Sidebar.set(
window.innerWidth > BREAKPOINT
)
;
await chats.set(await getChatList(localStorage.token));
await chats.set(await getChatList(localStorage.token));
let touchstart;
let touchstart;
...
@@ -61,10 +56,10 @@
...
@@ -61,10 +56,10 @@
const swipeDistance = Math.abs(touchend.screenX - touchstart.screenX);
const swipeDistance = Math.abs(touchend.screenX - touchstart.screenX);
if (touchstart.clientX < 40 && swipeDistance >= screenWidth / 4) {
if (touchstart.clientX < 40 && swipeDistance >= screenWidth / 4) {
if (touchend.screenX < touchstart.screenX) {
if (touchend.screenX < touchstart.screenX) {
show
=
false;
show
Sidebar.set(
false
)
;
}
}
if (touchend.screenX > touchstart.screenX) {
if (touchend.screenX > touchstart.screenX) {
show
=
true;
show
Sidebar.set(
true
)
;
}
}
}
}
}
}
...
@@ -80,8 +75,8 @@
...
@@ -80,8 +75,8 @@
};
};
const onResize = () => {
const onResize = () => {
if (show && window.innerWidth < BREAKPOINT) {
if (
$
show
Sidebar
&& window.innerWidth < BREAKPOINT) {
show
=
false;
show
Sidebar.set(
false
)
;
}
}
};
};
...
@@ -167,13 +162,15 @@
...
@@ -167,13 +162,15 @@
<div
<div
bind:this={navElement}
bind:this={navElement}
class="h-screen max-h-[100dvh] min-h-screen {show
id="sidebar"
class="h-screen max-h-[100dvh] min-h-screen {$showSidebar
? 'lg:relative w-[260px]'
? 'lg:relative w-[260px]'
: '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0
: '-translate-x-[260px] w-[0px]'} bg-gray-50 text-gray-900 dark:bg-gray-950 dark:text-gray-200 text-sm transition fixed z-50 top-0 left-0
"
"
data-state={$showSidebar}
>
>
<div
<div
class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] {show
class="py-2.5 my-auto flex flex-col justify-between h-screen max-h-[100dvh] w-[260px] {
$
show
Sidebar
? ''
? ''
: 'invisible'}"
: 'invisible'}"
>
>
...
@@ -466,7 +463,7 @@
...
@@ -466,7 +463,7 @@
on:click={() => {
on:click={() => {
selectedChatId = chat.id;
selectedChatId = chat.id;
if (window.innerWidth < 1024) {
if (window.innerWidth < 1024) {
show
=
false;
show
Sidebar.set(
false
)
;
}
}
}}
}}
draggable="false"
draggable="false"
...
@@ -803,14 +800,14 @@
...
@@ -803,14 +800,14 @@
>
>
<Tooltip
<Tooltip
placement="right"
placement="right"
content={`${show ? $i18n.t('Close') : $i18n.t('Open')} ${$i18n.t('sidebar')}`}
content={`${
$
show
Sidebar
? $i18n.t('Close') : $i18n.t('Open')} ${$i18n.t('sidebar')}`}
touch={false}
touch={false}
>
>
<button
<button
id="sidebar-toggle-button"
id="sidebar-toggle-button"
class=" group"
class=" group"
on:click={() => {
on:click={() => {
show
= !show
;
show
Sidebar.set(!$showSidebar)
;
}}
}}
><span class="" data-state="closed"
><span class="" data-state="closed"
><div
><div
...
...
src/lib/stores/index.ts
View file @
3c9fc785
...
@@ -34,6 +34,8 @@ export const documents = writable([
...
@@ -34,6 +34,8 @@ export const documents = writable([
]);
]);
export
const
settings
:
Writable
<
Settings
>
=
writable
({});
export
const
settings
:
Writable
<
Settings
>
=
writable
({});
export
const
showSidebar
=
writable
(
false
);
export
const
showSettings
=
writable
(
false
);
export
const
showSettings
=
writable
(
false
);
export
const
showChangelog
=
writable
(
false
);
export
const
showChangelog
=
writable
(
false
);
...
...
src/routes/(app)/+page.svelte
View file @
3c9fc785
...
@@ -877,7 +877,6 @@
...
@@ -877,7 +877,6 @@
/>
/>
</div>
</div>
</div>
</div>
<MessageInput bind:files bind:prompt bind:autoScroll {messages} {submitPrompt} {stopResponse} />
</div>
</div>
</div>
</div>
<MessageInput bind:files bind:prompt bind:autoScroll {messages} {submitPrompt} {stopResponse} />
src/routes/(app)/c/[id]/+page.svelte
View file @
3c9fc785
...
@@ -900,17 +900,16 @@
...
@@ -900,17 +900,16 @@
/>
/>
</div>
</div>
</div>
</div>
<MessageInput
bind:files
bind:prompt
bind:autoScroll
suggestionPrompts={selectedModelfile?.suggestionPrompts ??
$config.default_prompt_suggestions}
{messages}
{submitPrompt}
{stopResponse}
/>
</div>
</div>
</div>
</div>
<MessageInput
bind:files
bind:prompt
bind:autoScroll
suggestionPrompts={selectedModelfile?.suggestionPrompts ?? $config.default_prompt_suggestions}
{messages}
{submitPrompt}
{stopResponse}
/>
{/if}
{/if}
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