diff --git a/backend/open_webui/models/channels.py b/backend/open_webui/models/channels.py
index dc13d362d8..e95b6f6c95 100644
--- a/backend/open_webui/models/channels.py
+++ b/backend/open_webui/models/channels.py
@@ -1,4 +1,5 @@
import json
+import secrets
import time
import uuid
from typing import Optional
@@ -245,6 +246,11 @@ class CreateChannelForm(ChannelForm):
type: Optional[str] = None
+class ChannelWebhookForm(BaseModel):
+ name: str
+ profile_image_url: Optional[str] = None
+
+
class ChannelTable:
def _collect_unique_user_ids(
@@ -945,5 +951,115 @@ class ChannelTable:
db.commit()
return True
+ ####################
+ # Webhook Methods
+ ####################
+
+ def insert_webhook(
+ self,
+ channel_id: str,
+ user_id: str,
+ form_data: ChannelWebhookForm,
+ db: Optional[Session] = None,
+ ) -> Optional[ChannelWebhookModel]:
+ with get_db_context(db) as db:
+ webhook = ChannelWebhookModel(
+ id=str(uuid.uuid4()),
+ channel_id=channel_id,
+ user_id=user_id,
+ name=form_data.name,
+ profile_image_url=form_data.profile_image_url,
+ token=secrets.token_urlsafe(32),
+ last_used_at=None,
+ created_at=int(time.time_ns()),
+ updated_at=int(time.time_ns()),
+ )
+ db.add(ChannelWebhook(**webhook.model_dump()))
+ db.commit()
+ return webhook
+
+ def get_webhooks_by_channel_id(
+ self, channel_id: str, db: Optional[Session] = None
+ ) -> list[ChannelWebhookModel]:
+ with get_db_context(db) as db:
+ webhooks = (
+ db.query(ChannelWebhook)
+ .filter(ChannelWebhook.channel_id == channel_id)
+ .all()
+ )
+ return [ChannelWebhookModel.model_validate(w) for w in webhooks]
+
+ def get_webhook_by_id(
+ self, webhook_id: str, db: Optional[Session] = None
+ ) -> Optional[ChannelWebhookModel]:
+ with get_db_context(db) as db:
+ webhook = (
+ db.query(ChannelWebhook)
+ .filter(ChannelWebhook.id == webhook_id)
+ .first()
+ )
+ return ChannelWebhookModel.model_validate(webhook) if webhook else None
+
+ def get_webhook_by_id_and_token(
+ self, webhook_id: str, token: str, db: Optional[Session] = None
+ ) -> Optional[ChannelWebhookModel]:
+ with get_db_context(db) as db:
+ webhook = (
+ db.query(ChannelWebhook)
+ .filter(
+ ChannelWebhook.id == webhook_id,
+ ChannelWebhook.token == token,
+ )
+ .first()
+ )
+ return ChannelWebhookModel.model_validate(webhook) if webhook else None
+
+ def update_webhook_by_id(
+ self,
+ webhook_id: str,
+ form_data: ChannelWebhookForm,
+ db: Optional[Session] = None,
+ ) -> Optional[ChannelWebhookModel]:
+ with get_db_context(db) as db:
+ webhook = (
+ db.query(ChannelWebhook)
+ .filter(ChannelWebhook.id == webhook_id)
+ .first()
+ )
+ if not webhook:
+ return None
+ webhook.name = form_data.name
+ webhook.profile_image_url = form_data.profile_image_url
+ webhook.updated_at = int(time.time_ns())
+ db.commit()
+ return ChannelWebhookModel.model_validate(webhook)
+
+ def update_webhook_last_used_at(
+ self, webhook_id: str, db: Optional[Session] = None
+ ) -> bool:
+ with get_db_context(db) as db:
+ webhook = (
+ db.query(ChannelWebhook)
+ .filter(ChannelWebhook.id == webhook_id)
+ .first()
+ )
+ if not webhook:
+ return False
+ webhook.last_used_at = int(time.time_ns())
+ db.commit()
+ return True
+
+ def delete_webhook_by_id(
+ self, webhook_id: str, db: Optional[Session] = None
+ ) -> bool:
+ with get_db_context(db) as db:
+ result = (
+ db.query(ChannelWebhook)
+ .filter(ChannelWebhook.id == webhook_id)
+ .delete()
+ )
+ db.commit()
+ return result > 0
+
Channels = ChannelTable()
diff --git a/backend/open_webui/models/messages.py b/backend/open_webui/models/messages.py
index a4c91f3acb..0851107b0b 100644
--- a/backend/open_webui/models/messages.py
+++ b/backend/open_webui/models/messages.py
@@ -199,11 +199,32 @@ class MessageTable:
if include_thread_replies:
thread_replies = self.get_thread_replies_by_message_id(id, db=db)
- user = Users.get_user_by_id(message.user_id, db=db)
+ # Check if message was sent by webhook (webhook info in meta takes precedence)
+ webhook_info = message.meta.get("webhook") if message.meta else None
+ if webhook_info and webhook_info.get("id"):
+ # Look up webhook by ID to get current name
+ webhook = Channels.get_webhook_by_id(webhook_info.get("id"), db=db)
+ if webhook:
+ user_info = {
+ "id": webhook.id,
+ "name": webhook.name,
+ "role": "webhook",
+ }
+ else:
+ # Webhook was deleted, use placeholder
+ user_info = {
+ "id": webhook_info.get("id"),
+ "name": "Deleted Webhook",
+ "role": "webhook",
+ }
+ else:
+ user = Users.get_user_by_id(message.user_id, db=db)
+ user_info = user.model_dump() if user else None
+
return MessageResponse.model_validate(
{
**MessageModel.model_validate(message).model_dump(),
- "user": user.model_dump() if user else None,
+ "user": user_info,
"reply_to_message": (
reply_to_message.model_dump() if reply_to_message else None
),
@@ -235,10 +256,29 @@ class MessageTable:
if message.reply_to_id
else None
)
+
+ webhook_info = message.meta.get("webhook") if message.meta else None
+ user_info = None
+ if webhook_info and webhook_info.get("id"):
+ webhook = Channels.get_webhook_by_id(webhook_info.get("id"), db=db)
+ if webhook:
+ user_info = {
+ "id": webhook.id,
+ "name": webhook.name,
+ "role": "webhook",
+ }
+ else:
+ user_info = {
+ "id": webhook_info.get("id"),
+ "name": "Deleted Webhook",
+ "role": "webhook",
+ }
+
messages.append(
MessageReplyToResponse.model_validate(
{
**MessageModel.model_validate(message).model_dump(),
+ "user": user_info,
"reply_to_message": (
reply_to_message.model_dump()
if reply_to_message
@@ -284,10 +324,29 @@ class MessageTable:
if message.reply_to_id
else None
)
+
+ webhook_info = message.meta.get("webhook") if message.meta else None
+ user_info = None
+ if webhook_info and webhook_info.get("id"):
+ webhook = Channels.get_webhook_by_id(webhook_info.get("id"), db=db)
+ if webhook:
+ user_info = {
+ "id": webhook.id,
+ "name": webhook.name,
+ "role": "webhook",
+ }
+ else:
+ user_info = {
+ "id": webhook_info.get("id"),
+ "name": "Deleted Webhook",
+ "role": "webhook",
+ }
+
messages.append(
MessageReplyToResponse.model_validate(
{
**MessageModel.model_validate(message).model_dump(),
+ "user": user_info,
"reply_to_message": (
reply_to_message.model_dump()
if reply_to_message
@@ -334,10 +393,29 @@ class MessageTable:
if message.reply_to_id
else None
)
+
+ webhook_info = message.meta.get("webhook") if message.meta else None
+ user_info = None
+ if webhook_info and webhook_info.get("id"):
+ webhook = Channels.get_webhook_by_id(webhook_info.get("id"), db=db)
+ if webhook:
+ user_info = {
+ "id": webhook.id,
+ "name": webhook.name,
+ "role": "webhook",
+ }
+ else:
+ user_info = {
+ "id": webhook_info.get("id"),
+ "name": "Deleted Webhook",
+ "role": "webhook",
+ }
+
messages.append(
MessageReplyToResponse.model_validate(
{
**MessageModel.model_validate(message).model_dump(),
+ "user": user_info,
"reply_to_message": (
reply_to_message.model_dump()
if reply_to_message
diff --git a/backend/open_webui/routers/channels.py b/backend/open_webui/routers/channels.py
index a2ff614524..d501818b76 100644
--- a/backend/open_webui/routers/channels.py
+++ b/backend/open_webui/routers/channels.py
@@ -1,9 +1,12 @@
import json
import logging
+import base64
+import io
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Request, status, BackgroundTasks
+from fastapi.responses import Response, StreamingResponse, FileResponse
from pydantic import BaseModel
from pydantic import field_validator
@@ -29,6 +32,8 @@ from open_webui.models.channels import (
ChannelForm,
ChannelResponse,
CreateChannelForm,
+ ChannelWebhookModel,
+ ChannelWebhookForm,
)
from open_webui.models.messages import (
Messages,
@@ -43,6 +48,7 @@ from open_webui.utils.files import get_image_base64_from_file_id
from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
from open_webui.constants import ERROR_MESSAGES
+from open_webui.env import STATIC_DIR
from open_webui.utils.models import (
@@ -822,6 +828,11 @@ async def get_channel_messages(
thread_replies[0].created_at if thread_replies else None
)
+ # Use message.user if present (for webhooks), otherwise look up by user_id
+ user_info = message.user
+ if user_info is None and message.user_id in users:
+ user_info = UserNameResponse(**users[message.user_id].model_dump())
+
messages.append(
MessageUserResponse(
**{
@@ -831,7 +842,7 @@ async def get_channel_messages(
"reactions": Messages.get_reactions_by_message_id(
message.id, db=db
),
- "user": UserNameResponse(**users[message.user_id].model_dump()),
+ "user": user_info,
}
)
)
@@ -889,6 +900,19 @@ async def get_pinned_channel_messages(
messages = []
for message in message_list:
+ # Check for webhook identity in meta
+ webhook_info = message.meta.get("webhook") if message.meta else None
+ if webhook_info:
+ user_info = UserNameResponse(
+ id=webhook_info.get("id"),
+ name=webhook_info.get("name"),
+ role="webhook",
+ )
+ elif message.user_id in users:
+ user_info = UserNameResponse(**users[message.user_id].model_dump())
+ else:
+ user_info = None
+
messages.append(
MessageWithReactionsResponse(
**{
@@ -896,7 +920,7 @@ async def get_pinned_channel_messages(
"reactions": Messages.get_reactions_by_message_id(
message.id, db=db
),
- "user": UserNameResponse(**users[message.user_id].model_dump()),
+ "user": user_info,
}
)
)
@@ -1476,6 +1500,11 @@ async def get_channel_thread_messages(
messages = []
for message in message_list:
+ # Use message.user if present (for webhooks), otherwise look up by user_id
+ user_info = message.user
+ if user_info is None and message.user_id in users:
+ user_info = UserNameResponse(**users[message.user_id].model_dump())
+
messages.append(
MessageUserResponse(
**{
@@ -1485,7 +1514,7 @@ async def get_channel_thread_messages(
"reactions": Messages.get_reactions_by_message_id(
message.id, db=db
),
- "user": UserNameResponse(**users[message.user_id].model_dump()),
+ "user": user_info,
}
)
)
@@ -1835,3 +1864,262 @@ async def delete_message_by_id(
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
)
+
+
+############################
+# Webhooks
+############################
+
+
+@router.get("/webhooks/{webhook_id}/profile/image")
+async def get_webhook_profile_image(
+ webhook_id: str,
+ user=Depends(get_verified_user),
+ db: Session = Depends(get_session),
+):
+ """Get webhook profile image by webhook ID."""
+ webhook = Channels.get_webhook_by_id(webhook_id, db=db)
+ if not webhook:
+ # Return default favicon if webhook not found
+ return FileResponse(f"{STATIC_DIR}/favicon.png")
+
+ if webhook.profile_image_url:
+ # Check if it's url or base64
+ if webhook.profile_image_url.startswith("http"):
+ return Response(
+ status_code=status.HTTP_302_FOUND,
+ headers={"Location": webhook.profile_image_url},
+ )
+ elif webhook.profile_image_url.startswith("data:image"):
+ try:
+ header, base64_data = webhook.profile_image_url.split(",", 1)
+ image_data = base64.b64decode(base64_data)
+ image_buffer = io.BytesIO(image_data)
+ media_type = header.split(";")[0].lstrip("data:")
+
+ return StreamingResponse(
+ image_buffer,
+ media_type=media_type,
+ headers={"Content-Disposition": "inline"},
+ )
+ except Exception as e:
+ pass
+
+ # Return default favicon if no profile image
+ return FileResponse(f"{STATIC_DIR}/favicon.png")
+
+@router.get("/{id}/webhooks", response_model=list[ChannelWebhookModel])
+async def get_channel_webhooks(
+ request: Request,
+ id: str,
+ user=Depends(get_verified_user),
+ db: Session = Depends(get_session),
+):
+ check_channels_access(request)
+ channel = Channels.get_channel_by_id(id, db=db)
+ if not channel:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
+ )
+
+ # Only channel managers can view webhooks
+ if (
+ not Channels.is_user_channel_manager(channel.id, user.id, db=db)
+ and user.role != "admin"
+ ):
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.UNAUTHORIZED
+ )
+
+ return Channels.get_webhooks_by_channel_id(id, db=db)
+
+
+@router.post("/{id}/webhooks/create", response_model=ChannelWebhookModel)
+async def create_channel_webhook(
+ request: Request,
+ id: str,
+ form_data: ChannelWebhookForm,
+ user=Depends(get_verified_user),
+ db: Session = Depends(get_session),
+):
+ check_channels_access(request)
+ channel = Channels.get_channel_by_id(id, db=db)
+ if not channel:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
+ )
+
+ # Only channel managers can create webhooks
+ if (
+ not Channels.is_user_channel_manager(channel.id, user.id, db=db)
+ and user.role != "admin"
+ ):
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.UNAUTHORIZED
+ )
+
+ webhook = Channels.insert_webhook(id, user.id, form_data, db=db)
+ if not webhook:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
+ )
+
+ return webhook
+
+
+@router.post("/{id}/webhooks/{webhook_id}/update", response_model=ChannelWebhookModel)
+async def update_channel_webhook(
+ request: Request,
+ id: str,
+ webhook_id: str,
+ form_data: ChannelWebhookForm,
+ user=Depends(get_verified_user),
+ db: Session = Depends(get_session),
+):
+ check_channels_access(request)
+ channel = Channels.get_channel_by_id(id, db=db)
+ if not channel:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
+ )
+
+ # Only channel managers can update webhooks
+ if (
+ not Channels.is_user_channel_manager(channel.id, user.id, db=db)
+ and user.role != "admin"
+ ):
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.UNAUTHORIZED
+ )
+
+ webhook = Channels.get_webhook_by_id(webhook_id, db=db)
+ if not webhook or webhook.channel_id != id:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
+ )
+
+ updated = Channels.update_webhook_by_id(webhook_id, form_data, db=db)
+ if not updated:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
+ )
+
+ return updated
+
+
+@router.delete("/{id}/webhooks/{webhook_id}/delete", response_model=bool)
+async def delete_channel_webhook(
+ request: Request,
+ id: str,
+ webhook_id: str,
+ user=Depends(get_verified_user),
+ db: Session = Depends(get_session),
+):
+ check_channels_access(request)
+ channel = Channels.get_channel_by_id(id, db=db)
+ if not channel:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
+ )
+
+ # Only channel managers can delete webhooks
+ if (
+ not Channels.is_user_channel_manager(channel.id, user.id, db=db)
+ and user.role != "admin"
+ ):
+ raise HTTPException(
+ status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.UNAUTHORIZED
+ )
+
+ webhook = Channels.get_webhook_by_id(webhook_id, db=db)
+ if not webhook or webhook.channel_id != id:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
+ )
+
+ return Channels.delete_webhook_by_id(webhook_id, db=db)
+
+
+############################
+# Public Webhook Endpoint
+############################
+
+
+class WebhookMessageForm(BaseModel):
+ content: str
+
+
+@router.post("/webhooks/{webhook_id}/{token}")
+async def post_webhook_message(
+ request: Request,
+ webhook_id: str,
+ token: str,
+ form_data: WebhookMessageForm,
+ db: Session = Depends(get_session),
+):
+ """Public endpoint to post messages via webhook. No authentication required."""
+ check_channels_access(request)
+
+ # Validate webhook
+ webhook = Channels.get_webhook_by_id_and_token(webhook_id, token, db=db)
+ if not webhook:
+ raise HTTPException(
+ status_code=status.HTTP_401_UNAUTHORIZED,
+ detail="Invalid webhook URL",
+ )
+
+ channel = Channels.get_channel_by_id(webhook.channel_id, db=db)
+ if not channel:
+ raise HTTPException(
+ status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
+ )
+
+ # Create message with webhook identity stored in meta
+ message = Messages.insert_new_message(
+ MessageForm(content=form_data.content, meta={"webhook": {"id": webhook.id}}),
+ webhook.channel_id,
+ webhook.user_id, # Required for DB but webhook info in meta takes precedence
+ db=db,
+ )
+
+ if not message:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail="Failed to create message",
+ )
+
+ # Update last_used_at
+ Channels.update_webhook_last_used_at(webhook_id, db=db)
+
+ # Get full message and emit event
+ message = Messages.get_message_by_id(message.id, db=db)
+
+ event_data = {
+ "channel_id": channel.id,
+ "message_id": message.id,
+ "data": {
+ "type": "message",
+ "data": {
+ **message.model_dump(),
+ "user": {
+ "id": webhook.id,
+ "name": webhook.name,
+ "role": "webhook",
+ },
+ },
+ },
+ "user": {
+ "id": webhook.id,
+ "name": webhook.name,
+ "role": "webhook",
+ },
+ "channel": channel.model_dump(),
+ }
+
+ await sio.emit(
+ "events:channel",
+ event_data,
+ to=f"channel:{channel.id}",
+ )
+
+ return {"success": True, "message_id": message.id}
diff --git a/src/lib/apis/channels/index.ts b/src/lib/apis/channels/index.ts
index 44817e97ef..225d8cd7cf 100644
--- a/src/lib/apis/channels/index.ts
+++ b/src/lib/apis/channels/index.ts
@@ -763,3 +763,155 @@ export const deleteMessage = async (token: string = '', channel_id: string, mess
return res;
};
+
+// Webhook API functions
+
+type WebhookForm = {
+ name: string;
+ profile_image_url?: string;
+};
+
+export const getChannelWebhooks = async (token: string = '', channel_id: string) => {
+ let error = null;
+
+ const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}/webhooks`, {
+ method: 'GET',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ authorization: `Bearer ${token}`
+ }
+ })
+ .then(async (res) => {
+ if (!res.ok) throw await res.json();
+ return res.json();
+ })
+ .then((json) => {
+ return json;
+ })
+ .catch((err) => {
+ error = err.detail;
+ console.error(err);
+ return null;
+ });
+
+ if (error) {
+ throw error;
+ }
+
+ return res;
+};
+
+export const createChannelWebhook = async (
+ token: string = '',
+ channel_id: string,
+ formData: WebhookForm
+) => {
+ let error = null;
+
+ const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}/webhooks/create`, {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ authorization: `Bearer ${token}`
+ },
+ body: JSON.stringify({ ...formData })
+ })
+ .then(async (res) => {
+ if (!res.ok) throw await res.json();
+ return res.json();
+ })
+ .then((json) => {
+ return json;
+ })
+ .catch((err) => {
+ error = err.detail;
+ console.error(err);
+ return null;
+ });
+
+ if (error) {
+ throw error;
+ }
+
+ return res;
+};
+
+export const updateChannelWebhook = async (
+ token: string = '',
+ channel_id: string,
+ webhook_id: string,
+ formData: WebhookForm
+) => {
+ let error = null;
+
+ const res = await fetch(
+ `${WEBUI_API_BASE_URL}/channels/${channel_id}/webhooks/${webhook_id}/update`,
+ {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ authorization: `Bearer ${token}`
+ },
+ body: JSON.stringify({ ...formData })
+ }
+ )
+ .then(async (res) => {
+ if (!res.ok) throw await res.json();
+ return res.json();
+ })
+ .then((json) => {
+ return json;
+ })
+ .catch((err) => {
+ error = err.detail;
+ console.error(err);
+ return null;
+ });
+
+ if (error) {
+ throw error;
+ }
+
+ return res;
+};
+
+export const deleteChannelWebhook = async (
+ token: string = '',
+ channel_id: string,
+ webhook_id: string
+) => {
+ let error = null;
+
+ const res = await fetch(
+ `${WEBUI_API_BASE_URL}/channels/${channel_id}/webhooks/${webhook_id}/delete`,
+ {
+ method: 'DELETE',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ authorization: `Bearer ${token}`
+ }
+ }
+ )
+ .then(async (res) => {
+ if (!res.ok) throw await res.json();
+ return res.json();
+ })
+ .then((json) => {
+ return json;
+ })
+ .catch((err) => {
+ error = err.detail;
+ console.error(err);
+ return null;
+ });
+
+ if (error) {
+ throw error;
+ }
+
+ return res;
+};
diff --git a/src/lib/components/channel/Channel.svelte b/src/lib/components/channel/Channel.svelte
index 3229b65f90..3f650662ae 100644
--- a/src/lib/components/channel/Channel.svelte
+++ b/src/lib/components/channel/Channel.svelte
@@ -119,8 +119,8 @@
if (type === 'message') {
if ((data?.parent_id ?? null) === null) {
- const tempId = data?.temp_id ?? null;
- messages = [{ ...data, temp_id: null }, ...messages.filter((m) => m?.temp_id !== tempId)];
+ const tempId = data?.temp_id ?? null;
+ messages = [{ ...data, temp_id: null }, ...messages.filter((m) => !tempId || m?.temp_id !== tempId)];
if (typingUsers.find((user) => user.id === event.user.id)) {
typingUsers = typingUsers.filter((user) => user.id !== event.user.id);
diff --git a/src/lib/components/channel/Messages.svelte b/src/lib/components/channel/Messages.svelte
index 7ad8798297..55f5df1238 100644
--- a/src/lib/components/channel/Messages.svelte
+++ b/src/lib/components/channel/Messages.svelte
@@ -131,8 +131,9 @@
replyToMessage={replyToMessage?.id === message.id}
disabled={!channel?.write_access || message?.temp_id}
pending={!!message?.temp_id}
- showUserProfile={messageIdx === 0 ||
+ showUserProfile={messageIdx === 0 ||
messageList.at(messageIdx - 1)?.user_id !== message.user_id ||
+ messageList.at(messageIdx - 1)?.user?.id !== message.user?.id ||
messageList.at(messageIdx - 1)?.meta?.model_id !== message?.meta?.model_id ||
message?.reply_to_message !== null}
onDelete={() => {
diff --git a/src/lib/components/channel/Messages/Message.svelte b/src/lib/components/channel/Messages/Message.svelte
index 9a636eabc9..72e81d9b95 100644
--- a/src/lib/components/channel/Messages/Message.svelte
+++ b/src/lib/components/channel/Messages/Message.svelte
@@ -245,7 +245,7 @@
/>
{:else}
@@ -277,10 +277,10 @@
alt={message.meta.model_name ?? message.meta.model_id}
class="size-8 translate-y-1 ml-0.5 object-cover rounded-full"
/>
- {:else}
+ {:else}