diff --git a/backend/open_webui/models/messages.py b/backend/open_webui/models/messages.py index 6ed49ba597..2351c4c54c 100644 --- a/backend/open_webui/models/messages.py +++ b/backend/open_webui/models/messages.py @@ -90,6 +90,7 @@ class MessageModel(BaseModel): class MessageForm(BaseModel): + temp_id: Optional[str] = None content: str reply_to_id: Optional[str] = None parent_id: Optional[str] = None diff --git a/backend/open_webui/routers/channels.py b/backend/open_webui/routers/channels.py index f6e3ebe47a..d492176a00 100644 --- a/backend/open_webui/routers/channels.py +++ b/backend/open_webui/routers/channels.py @@ -765,7 +765,7 @@ async def new_message_handler( "message_id": message.id, "data": { "type": "message", - "data": message.model_dump(), + "data": {"temp_id": form_data.temp_id, **message.model_dump()}, }, "user": UserNameResponse(**user.model_dump()).model_dump(), "channel": channel.model_dump(), diff --git a/src/lib/apis/channels/index.ts b/src/lib/apis/channels/index.ts index 7a954a7507..e7c6b61cf8 100644 --- a/src/lib/apis/channels/index.ts +++ b/src/lib/apis/channels/index.ts @@ -378,6 +378,7 @@ export const getChannelThreadMessages = async ( }; type MessageForm = { + temp_id?: string; reply_to_id?: string; parent_id?: string; content: string; diff --git a/src/lib/components/channel/Channel.svelte b/src/lib/components/channel/Channel.svelte index 4e3844409b..88e50ad80c 100644 --- a/src/lib/components/channel/Channel.svelte +++ b/src/lib/components/channel/Channel.svelte @@ -4,6 +4,7 @@ import { onDestroy, onMount, tick } from 'svelte'; import { goto } from '$app/navigation'; + import { v4 as uuidv4 } from 'uuid'; import { chatId, @@ -118,7 +119,8 @@ if (type === 'message') { if ((data?.parent_id ?? null) === null) { - messages = [data, ...messages]; + const tempId = data?.temp_id ?? null; + messages = [{ ...data, temp_id: null }, ...messages.filter((m) => m?.temp_id !== tempId)]; if (typingUsers.find((user) => user.id === event.user.id)) { typingUsers = typingUsers.filter((user) => user.id !== event.user.id); @@ -183,11 +185,30 @@ return; } - const res = await sendMessage(localStorage.token, id, { + const tempId = uuidv4(); + + const message = { + temp_id: tempId, content: content, data: data, reply_to_id: replyToMessage?.id ?? null - }).catch((error) => { + }; + + const ts = Date.now() * 1000000; // nanoseconds + messages = [ + { + ...message, + id: tempId, + user_id: $user?.id, + user: $user, + reply_to_message: replyToMessage ?? null, + created_at: ts, + updated_at: ts + }, + ...messages + ]; + + const res = await sendMessage(localStorage.token, id, message).catch((error) => { toast.error(`${error}`); return null; }); diff --git a/src/lib/components/channel/Messages.svelte b/src/lib/components/channel/Messages.svelte index b8e6dbb9ea..05dabfd749 100644 --- a/src/lib/components/channel/Messages.svelte +++ b/src/lib/components/channel/Messages.svelte @@ -128,11 +128,12 @@ {message} {thread} replyToMessage={replyToMessage?.id === message.id} - disabled={!channel?.write_access} + disabled={!channel?.write_access || message?.temp_id} + pending={!!message?.temp_id} showUserProfile={messageIdx === 0 || messageList.at(messageIdx - 1)?.user_id !== message.user_id || messageList.at(messageIdx - 1)?.meta?.model_id !== message?.meta?.model_id || - message?.reply_to_message} + message?.reply_to_message !== null} onDelete={() => { messages = messages.filter((m) => m.id !== message.id); diff --git a/src/lib/components/channel/Messages/Message.svelte b/src/lib/components/channel/Messages/Message.svelte index 5ed17336c9..c47bf5a233 100644 --- a/src/lib/components/channel/Messages/Message.svelte +++ b/src/lib/components/channel/Messages/Message.svelte @@ -47,6 +47,7 @@ export let replyToMessage = false; export let disabled = false; + export let pending = false; export let onDelete: Function = () => {}; export let onEdit: Function = () => {}; @@ -380,7 +381,7 @@ {:else} -
+
{#if (message?.content ?? '').trim() === '' && message?.meta?.model_id} {:else}