diff --git a/backend/open_webui/models/channels.py b/backend/open_webui/models/channels.py index 179ed83fd2..2aee4fe03e 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, update_notifications_read_at_by_resource_id from open_webui.internal.db import Base, JSONField, get_async_db_context from open_webui.models.access_grants import ( AccessGrantModel, @@ -705,7 +705,12 @@ class ChannelTable: membership.last_read_at = int(time.time_ns()) membership.updated_at = int(time.time_ns()) + notifications_updated = await update_notifications_read_at_by_resource_id( + user_id=user_id, resource_type='channel', resource_id=channel_id, db=db + ) await db.commit() + if notifications_updated: + 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..f81d985756 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 update_notifications_read_at_by_resource_id(*, 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 be0676d992..b448a237c3 100644 --- a/src/lib/components/channel/Channel.svelte +++ b/src/lib/components/channel/Channel.svelte @@ -225,6 +225,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) =>