forked from open-webui/open-webui
feat: image upload support
This commit is contained in:
parent
fb1f8b167c
commit
9b12cdcf83
4 changed files with 84 additions and 57 deletions
|
@ -14,7 +14,7 @@
|
|||
|
||||
export let files = [];
|
||||
|
||||
export let fileUploadEnabled = false;
|
||||
export let fileUploadEnabled = true;
|
||||
export let speechRecognitionEnabled = true;
|
||||
export let speechRecognitionListening = false;
|
||||
|
||||
|
@ -84,40 +84,40 @@
|
|||
};
|
||||
</script>
|
||||
|
||||
<div class="fixed bottom-0 w-full bg-white dark:bg-gray-800">
|
||||
<div class=" absolute right-0 left-0 bottom-0 mb-20">
|
||||
<div class="max-w-3xl px-2.5 pt-2.5 -mb-0.5 mx-auto inset-x-0">
|
||||
{#if messages.length == 0 && suggestionPrompts.length !== 0}
|
||||
<div class="fixed bottom-0 w-full">
|
||||
<div class="px-2.5 pt-2.5 -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
|
||||
{#if messages.length == 0 && suggestionPrompts.length !== 0}
|
||||
<div class="max-w-3xl">
|
||||
<Suggestions {suggestionPrompts} {submitPrompt} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if autoScroll === false && messages.length > 0}
|
||||
<div class=" flex justify-center mb-4">
|
||||
<button
|
||||
class=" bg-white border border-gray-100 dark:border-none dark:bg-white/20 p-1.5 rounded-full"
|
||||
on:click={() => {
|
||||
autoScroll = true;
|
||||
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
||||
}}
|
||||
{#if autoScroll === false && messages.length > 0}
|
||||
<div class=" flex justify-center mb-4">
|
||||
<button
|
||||
class=" bg-white border border-gray-100 dark:border-none dark:bg-white/20 p-1.5 rounded-full"
|
||||
on:click={() => {
|
||||
autoScroll = true;
|
||||
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
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"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
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"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<div class="bg-white dark:bg-gray-800">
|
||||
<div class="max-w-3xl px-2.5 -mb-0.5 mx-auto inset-x-0">
|
||||
<div class="bg-gradient-to-t from-white dark:from-gray-800 from-40% pb-2">
|
||||
<input
|
||||
|
@ -136,6 +136,7 @@
|
|||
}
|
||||
];
|
||||
inputFiles = null;
|
||||
filesInputElement.value = '';
|
||||
};
|
||||
|
||||
if (
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
export let sendPrompt: Function;
|
||||
export let regenerateResponse: Function;
|
||||
|
||||
export let bottomPadding = false;
|
||||
export let autoScroll;
|
||||
export let selectedModels;
|
||||
export let history = {};
|
||||
|
@ -31,6 +32,13 @@
|
|||
})();
|
||||
}
|
||||
|
||||
$: if (autoScroll && bottomPadding) {
|
||||
(async () => {
|
||||
await tick();
|
||||
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
||||
})();
|
||||
}
|
||||
|
||||
const speakMessage = (message) => {
|
||||
const speak = new SpeechSynthesisUtterance(message);
|
||||
speechSynthesis.speak(speak);
|
||||
|
@ -184,7 +192,8 @@
|
|||
parentId: history.messages[messageId].parentId,
|
||||
childrenIds: [],
|
||||
role: 'user',
|
||||
content: userPrompt
|
||||
content: userPrompt,
|
||||
...(history.messages[messageId].files && { files: history.messages[messageId].files })
|
||||
};
|
||||
|
||||
let messageParentId = history.messages[messageId].parentId;
|
||||
|
@ -425,6 +434,18 @@
|
|||
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"
|
||||
>
|
||||
{#if message.role == 'user'}
|
||||
{#if message.files}
|
||||
<div class="my-3 w-full flex overflow-x-auto space-x-2">
|
||||
{#each message.files as file}
|
||||
<div>
|
||||
{#if file.type === 'image'}
|
||||
<img src={file.url} alt="input" class=" max-h-96 rounded-lg" />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if message?.edit === true}
|
||||
<div class=" w-full">
|
||||
<textarea
|
||||
|
@ -458,17 +479,6 @@
|
|||
</div>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
{#if message.files}
|
||||
<div class="my-3">
|
||||
{#each message.files as file}
|
||||
<div>
|
||||
{#if file.type === 'image'}
|
||||
<img src={file.url} alt="input" class=" max-h-96" />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<pre id="user-message">{message.content}</pre>
|
||||
|
||||
<div class=" flex justify-start space-x-1">
|
||||
|
@ -889,4 +899,8 @@
|
|||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
{#if bottomPadding}
|
||||
<div class=" mb-10" />
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
|
@ -50,6 +50,10 @@
|
|||
messages = [];
|
||||
}
|
||||
|
||||
$: if (files) {
|
||||
console.log(files);
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
await chatId.set(uuidv4());
|
||||
|
||||
|
@ -146,7 +150,15 @@
|
|||
...messages
|
||||
]
|
||||
.filter((message) => message)
|
||||
.map((message) => ({ role: message.role, content: message.content })),
|
||||
.map((message) => ({
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
...(message.files && {
|
||||
images: message.files
|
||||
.filter((file) => file.type === 'image')
|
||||
.map((file) => file.url.slice(file.url.indexOf(',') + 1))
|
||||
})
|
||||
})),
|
||||
options: {
|
||||
seed: $settings.seed ?? undefined,
|
||||
temperature: $settings.temperature ?? undefined,
|
||||
|
@ -548,6 +560,7 @@
|
|||
bind:history
|
||||
bind:messages
|
||||
bind:autoScroll
|
||||
bottomPadding={files.length > 0}
|
||||
{sendPrompt}
|
||||
{regenerateResponse}
|
||||
/>
|
||||
|
@ -555,8 +568,8 @@
|
|||
</div>
|
||||
|
||||
<MessageInput
|
||||
bind:prompt
|
||||
bind:files
|
||||
bind:prompt
|
||||
bind:autoScroll
|
||||
suggestionPrompts={selectedModelfile?.suggestionPrompts ?? [
|
||||
{
|
||||
|
|
|
@ -51,17 +51,6 @@
|
|||
messages = [];
|
||||
}
|
||||
|
||||
// onMount(async () => {
|
||||
// let chat = await loadChat();
|
||||
|
||||
// await tick();
|
||||
// if (chat) {
|
||||
// loaded = true;
|
||||
// } else {
|
||||
// await goto('/');
|
||||
// }
|
||||
// });
|
||||
|
||||
$: if ($page.params.id) {
|
||||
(async () => {
|
||||
let chat = await loadChat();
|
||||
|
@ -173,7 +162,15 @@
|
|||
...messages
|
||||
]
|
||||
.filter((message) => message)
|
||||
.map((message) => ({ role: message.role, content: message.content })),
|
||||
.map((message) => ({
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
...(message.files && {
|
||||
images: message.files
|
||||
.filter((file) => file.type === 'image')
|
||||
.map((file) => file.url.slice(file.url.indexOf(',') + 1))
|
||||
})
|
||||
})),
|
||||
options: {
|
||||
seed: $settings.seed ?? undefined,
|
||||
temperature: $settings.temperature ?? undefined,
|
||||
|
@ -579,6 +576,7 @@
|
|||
bind:history
|
||||
bind:messages
|
||||
bind:autoScroll
|
||||
bottomPadding={files.length > 0}
|
||||
{sendPrompt}
|
||||
{regenerateResponse}
|
||||
/>
|
||||
|
@ -586,6 +584,7 @@
|
|||
</div>
|
||||
|
||||
<MessageInput
|
||||
bind:files
|
||||
bind:prompt
|
||||
bind:autoScroll
|
||||
suggestionPrompts={selectedModelfile?.suggestionPrompts ?? [
|
||||
|
|
Loading…
Reference in a new issue