From e65562656612821bdad71a18514b6deedf84cd32 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Tue, 3 Nov 2026 19:00:57 +0100 Subject: [PATCH] fix: inbox notifications stay unread while you are viewing the channel Mentions and replies in a channel, including ones from apps, showed up as unread in the inbox even when that channel was open on screen. Now a new message arriving in a visible channel, and opening the channel, marks that channel's inbox items as read. Notifications for channels you are not looking at, or in a hidden tab, stay unread. --- backend/open_webui/models/channels.py | 7 ++++++- backend/open_webui/models/notifications.py | 21 ++++++++++++++++++++- src/lib/components/channel/Channel.svelte | 4 ++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/models/channels.py b/backend/open_webui/models/channels.py index 1f0d15f47b..0d26f1cddd 100644 --- a/backend/open_webui/models/channels.py +++ b/backend/open_webui/models/channels.py @@ -3,7 +3,7 @@ import time import uuid from typing import Optional -from open_webui.models.notifications import emit_changes +from open_webui.models.notifications import emit_changes, mark_resource_notifications_read from open_webui.internal.db import Base, JSONField, get_async_db_context from open_webui.models.access_grants import ( AccessGrantModel, @@ -678,7 +678,12 @@ class ChannelTable: membership.last_read_at = int(time.time_ns()) membership.updated_at = int(time.time_ns()) + changed = await mark_resource_notifications_read( + user_id=user_id, resource_type='channel', resource_id=channel_id, db=db + ) await db.commit() + if changed: + await emit_changes({user_id}) return True async def update_member_active_status( diff --git a/backend/open_webui/models/notifications.py b/backend/open_webui/models/notifications.py index 80f5f83066..da1888c35a 100644 --- a/backend/open_webui/models/notifications.py +++ b/backend/open_webui/models/notifications.py @@ -6,7 +6,7 @@ from uuid import uuid4 from open_webui.internal.db import Base from pydantic import BaseModel, ConfigDict, Field -from sqlalchemy import JSON, BigInteger, CheckConstraint, Column, Index, Text, UniqueConstraint, delete, update +from sqlalchemy import JSON, BigInteger, CheckConstraint, Column, Index, Text, UniqueConstraint, delete, func, update log = logging.getLogger(__name__) @@ -157,6 +157,25 @@ async def delete_notifications(*, resource_type=None, resource_ids=None, object_ return set(recipients) +async def mark_resource_notifications_read(*, user_id: str, resource_type: str, resource_id: str, db) -> bool: + now = time.time_ns() + result = await db.execute( + update(Notification) + .where( + Notification.user_id == user_id, + Notification.resource_type == resource_type, + Notification.resource_id == resource_id, + Notification.read_at.is_(None), + ) + .values( + seen_at=func.coalesce(Notification.seen_at, now), + read_at=now, + updated_at=now, + ) + ) + return result.rowcount > 0 + + async def delete_user_notifications(user_id, db): await db.execute(delete(Notification).where(Notification.user_id == user_id)) await db.execute(update(Notification).where(Notification.actor_id == user_id).values(actor_id=None)) diff --git a/src/lib/components/channel/Channel.svelte b/src/lib/components/channel/Channel.svelte index aa9a892c6e..b69b15ad22 100644 --- a/src/lib/components/channel/Channel.svelte +++ b/src/lib/components/channel/Channel.svelte @@ -205,6 +205,10 @@ const data = event?.data?.data ?? null; if (type === 'message') { + if (document.visibilityState === 'visible') { + updateLastReadAt(id); + } + if ((data?.parent_id ?? null) === null) { const idx = (messages ?? []).findIndex( (message) =>