From ecd3b4ebd41563632bd6a4793a52425a2df32fe6 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Mon, 23 Dec 2024 14:43:58 -0700 Subject: [PATCH] enh: channel file upload --- .../7826ab40b532_update_file_table.py | 26 ++ backend/open_webui/models/files.py | 5 + src/lib/components/channel/Channel.svelte | 13 +- .../components/channel/MessageInput.svelte | 356 +++++++++++++++++- .../channel/MessageInput/InputMenu.svelte | 77 ++++ .../channel/Messages/Message.svelte | 23 ++ src/lib/components/common/Image.svelte | 2 +- 7 files changed, 477 insertions(+), 25 deletions(-) create mode 100644 backend/open_webui/migrations/versions/7826ab40b532_update_file_table.py create mode 100644 src/lib/components/channel/MessageInput/InputMenu.svelte diff --git a/backend/open_webui/migrations/versions/7826ab40b532_update_file_table.py b/backend/open_webui/migrations/versions/7826ab40b532_update_file_table.py new file mode 100644 index 000000000..c8afe9d51 --- /dev/null +++ b/backend/open_webui/migrations/versions/7826ab40b532_update_file_table.py @@ -0,0 +1,26 @@ +"""Update file table + +Revision ID: 7826ab40b532 +Revises: 57c599a3cb57 +Create Date: 2024-12-23 03:00:00.000000 + +""" + +from alembic import op +import sqlalchemy as sa + +revision = "7826ab40b532" +down_revision = "57c599a3cb57" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column( + "file", + sa.Column("access_control", sa.JSON(), nullable=True), + ) + + +def downgrade(): + op.drop_column("file", "access_control") diff --git a/backend/open_webui/models/files.py b/backend/open_webui/models/files.py index 4050b0140..91dea5444 100644 --- a/backend/open_webui/models/files.py +++ b/backend/open_webui/models/files.py @@ -27,6 +27,8 @@ class File(Base): data = Column(JSON, nullable=True) meta = Column(JSON, nullable=True) + access_control = Column(JSON, nullable=True) + created_at = Column(BigInteger) updated_at = Column(BigInteger) @@ -44,6 +46,8 @@ class FileModel(BaseModel): data: Optional[dict] = None meta: Optional[dict] = None + access_control: Optional[dict] = None + created_at: Optional[int] # timestamp in epoch updated_at: Optional[int] # timestamp in epoch @@ -90,6 +94,7 @@ class FileForm(BaseModel): path: str data: dict = {} meta: dict = {} + access_control: Optional[dict] = None class FilesTable: diff --git a/src/lib/components/channel/Channel.svelte b/src/lib/components/channel/Channel.svelte index da12e3d2d..51c813765 100644 --- a/src/lib/components/channel/Channel.svelte +++ b/src/lib/components/channel/Channel.svelte @@ -80,15 +80,17 @@ } }; - const submitHandler = async ({ content }) => { + const submitHandler = async ({ content, data }) => { if (!content) { return; } - const res = await sendMessage(localStorage.token, id, { content: content }).catch((error) => { - toast.error(error); - return null; - }); + const res = await sendMessage(localStorage.token, id, { content: content, data: data }).catch( + (error) => { + toast.error(error); + return null; + } + ); if (res) { messagesContainerElement.scrollTop = messagesContainerElement.scrollHeight; @@ -108,6 +110,7 @@ class="h-screen max-h-[100dvh] {$showSidebar ? 'md:max-w-[calc(100%-260px)]' : ''} w-full max-w-full flex flex-col" + id="channel-container" > diff --git a/src/lib/components/channel/MessageInput.svelte b/src/lib/components/channel/MessageInput.svelte index 89e32f3ab..e16480908 100644 --- a/src/lib/components/channel/MessageInput.svelte +++ b/src/lib/components/channel/MessageInput.svelte @@ -1,43 +1,282 @@ + +
@@ -69,6 +308,22 @@
+ { + if (inputFiles && inputFiles.length > 0) { + inputFilesHandler(Array.from(inputFiles)); + } else { + toast.error($i18n.t(`File not found.`)); + } + + filesInputElement.value = ''; + }} +/>
@@ -101,24 +356,87 @@ class="flex-1 flex flex-col relative w-full rounded-3xl px-1 bg-gray-50 dark:bg-gray-400/5 dark:text-gray-100" dir={$settings?.chatDirection ?? 'LTR'} > + {#if files.length > 0} +
+ {#each files as file, fileIdx} + {#if file.type === 'image'} +
+
+ input +
+
+ +
+
+ {:else} + { + files.splice(fileIdx, 1); + files = files; + }} + on:click={() => { + console.log(file); + }} + /> + {/if} + {/each} +
+ {/if} +
- + + + + +
{#if $settings?.richTextInput ?? true} diff --git a/src/lib/components/channel/MessageInput/InputMenu.svelte b/src/lib/components/channel/MessageInput/InputMenu.svelte new file mode 100644 index 000000000..2363b75ec --- /dev/null +++ b/src/lib/components/channel/MessageInput/InputMenu.svelte @@ -0,0 +1,77 @@ + + + { + if (e.detail === false) { + onClose(); + } + }} +> + + + + +
+ + {#if !$mobile} + { + screenCaptureHandler(); + }} + > + +
{$i18n.t('Capture')}
+
+ {/if} + + { + uploadFilesHandler(); + }} + > + +
{$i18n.t('Upload Files')}
+
+
+
+
diff --git a/src/lib/components/channel/Messages/Message.svelte b/src/lib/components/channel/Messages/Message.svelte index 8b468ab7b..2182e0a30 100644 --- a/src/lib/components/channel/Messages/Message.svelte +++ b/src/lib/components/channel/Messages/Message.svelte @@ -23,6 +23,8 @@ import Pencil from '$lib/components/icons/Pencil.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte'; import Textarea from '$lib/components/common/Textarea.svelte'; + import Image from '$lib/components/common/Image.svelte'; + import FileItem from '$lib/components/common/FileItem.svelte'; export let message; export let showUserProfile = true; @@ -142,6 +144,27 @@ {/if} + {#if (message?.data?.files ?? []).length > 0} +
+ {#each message?.data?.files as file} +
+ {#if file.type === 'image'} + {file.name} + {:else} + + {/if} +
+ {/each} +
+ {/if} + {#if edit}