enh: dm active user indicator

This commit is contained in:
Timothy Jaeryang Baek 2025-11-28 04:24:25 -05:00
parent d232e433e8
commit 4b6773885c
3 changed files with 35 additions and 4 deletions

View file

@ -125,6 +125,12 @@ class UserIdNameResponse(BaseModel):
name: str name: str
class UserIdNameStatusResponse(BaseModel):
id: str
name: str
is_active: bool
class UserInfoListResponse(BaseModel): class UserInfoListResponse(BaseModel):
users: list[UserInfoResponse] users: list[UserInfoResponse]
total: int total: int

View file

@ -14,6 +14,7 @@ from open_webui.socket.main import (
) )
from open_webui.models.users import ( from open_webui.models.users import (
UserIdNameResponse, UserIdNameResponse,
UserIdNameStatusResponse,
UserListResponse, UserListResponse,
UserModelResponse, UserModelResponse,
Users, Users,
@ -68,7 +69,7 @@ router = APIRouter()
class ChannelListItemResponse(ChannelModel): class ChannelListItemResponse(ChannelModel):
user_ids: Optional[list[str]] = None # 'dm' channels only user_ids: Optional[list[str]] = None # 'dm' channels only
users: Optional[list[UserIdNameResponse]] = None # 'dm' channels only users: Optional[list[UserIdNameStatusResponse]] = None # 'dm' channels only
last_message_at: Optional[int] = None # timestamp in epoch (time_ns) last_message_at: Optional[int] = None # timestamp in epoch (time_ns)
unread_count: int = 0 unread_count: int = 0
@ -97,7 +98,12 @@ async def get_channels(user=Depends(get_verified_user)):
for member in Channels.get_members_by_channel_id(channel.id) for member in Channels.get_members_by_channel_id(channel.id)
] ]
users = [ users = [
UserIdNameResponse(**user.model_dump()) UserIdNameStatusResponse(
**{
**user.model_dump(),
"is_active": get_active_status_by_user_id(user.id),
}
)
for user in Users.get_users_by_user_ids(user_ids) for user in Users.get_users_by_user_ids(user_ids)
] ]

View file

@ -5,6 +5,7 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
import { channels, mobile, showSidebar, user } from '$lib/stores'; import { channels, mobile, showSidebar, user } from '$lib/stores';
import { getUserActiveStatusById } from '$lib/apis/users';
import { updateChannelById, updateChannelMemberActiveStatusById } from '$lib/apis/channels'; import { updateChannelById, updateChannelMemberActiveStatusById } from '$lib/apis/channels';
import { WEBUI_API_BASE_URL } from '$lib/constants'; import { WEBUI_API_BASE_URL } from '$lib/constants';
@ -83,8 +84,9 @@
<div> <div>
{#if channel?.type === 'dm'} {#if channel?.type === 'dm'}
{#if channel?.users} {#if channel?.users}
<div class="flex ml-[1px] mr-0.5"> {@const channelMembers = channel.users.filter((u) => u.id !== $user?.id)}
{#each channel.users.filter((u) => u.id !== $user?.id).slice(0, 2) as u, index} <div class="flex ml-[1px] mr-0.5 relative">
{#each channelMembers.slice(0, 2) as u, index}
<img <img
src={`${WEBUI_API_BASE_URL}/users/${u.id}/profile/image`} src={`${WEBUI_API_BASE_URL}/users/${u.id}/profile/image`}
alt={u.name} alt={u.name}
@ -94,6 +96,23 @@
: ''}" : ''}"
/> />
{/each} {/each}
{#if channelMembers.length === 1}
<div class="absolute bottom-0 right-0">
<span class="relative flex size-2">
{#if channelMembers[0]?.is_active}
<span
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75"
></span>
{/if}
<span
class="relative inline-flex size-2 rounded-full {channelMembers[0]?.is_active
? 'bg-green-500'
: 'bg-gray-300 dark:bg-gray-700'} border-[1.5px] border-white dark:border-gray-900"
></span>
</span>
</div>
{/if}
</div> </div>
{:else} {:else}
<Users className="size-4 ml-1 mr-0.5" strokeWidth="2" /> <Users className="size-4 ml-1 mr-0.5" strokeWidth="2" />