Skip to content

Commit 51621ba

Browse files
committed
feat/enh: user status
1 parent dba86bc commit 51621ba

File tree

10 files changed

+422
-8
lines changed

10 files changed

+422
-8
lines changed

backend/open_webui/models/users.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,13 @@ class UserGroupIdsListResponse(BaseModel):
170170
total: int
171171

172172

173-
class UserInfoResponse(BaseModel):
173+
class UserStatus(BaseModel):
174+
status_emoji: Optional[str] = None
175+
status_message: Optional[str] = None
176+
status_expires_at: Optional[int] = None
177+
178+
179+
class UserInfoResponse(UserStatus):
174180
id: str
175181
name: str
176182
email: str
@@ -493,6 +499,21 @@ def update_user_role_by_id(self, id: str, role: str) -> Optional[UserModel]:
493499
except Exception:
494500
return None
495501

502+
def update_user_status_by_id(
503+
self, id: str, form_data: UserStatus
504+
) -> Optional[UserModel]:
505+
try:
506+
with get_db() as db:
507+
db.query(User).filter_by(id=id).update(
508+
{**form_data.model_dump(exclude_none=True)}
509+
)
510+
db.commit()
511+
512+
user = db.query(User).filter_by(id=id).first()
513+
return UserModel.model_validate(user)
514+
except Exception:
515+
return None
516+
496517
def update_user_profile_image_url_by_id(
497518
self, id: str, profile_image_url: str
498519
) -> Optional[UserModel]:

backend/open_webui/routers/auths.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@
1717
SignupForm,
1818
UpdatePasswordForm,
1919
)
20-
from open_webui.models.users import UserProfileImageResponse, Users, UpdateProfileForm
20+
from open_webui.models.users import (
21+
UserProfileImageResponse,
22+
Users,
23+
UpdateProfileForm,
24+
UserStatus,
25+
)
2126
from open_webui.models.groups import Groups
2227
from open_webui.models.oauth_sessions import OAuthSessions
2328

@@ -82,7 +87,7 @@ class SessionUserResponse(Token, UserProfileImageResponse):
8287
permissions: Optional[dict] = None
8388

8489

85-
class SessionUserInfoResponse(SessionUserResponse):
90+
class SessionUserInfoResponse(SessionUserResponse, UserStatus):
8691
bio: Optional[str] = None
8792
gender: Optional[str] = None
8893
date_of_birth: Optional[datetime.date] = None
@@ -139,6 +144,9 @@ async def get_session_user(
139144
"bio": user.bio,
140145
"gender": user.gender,
141146
"date_of_birth": user.date_of_birth,
147+
"status_emoji": user.status_emoji,
148+
"status_message": user.status_message,
149+
"status_expires_at": user.status_expires_at,
142150
"permissions": user_permissions,
143151
}
144152

backend/open_webui/routers/users.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
UserInfoListResponse,
2222
UserInfoListResponse,
2323
UserRoleUpdateForm,
24+
UserStatus,
2425
Users,
2526
UserSettings,
2627
UserUpdateForm,
@@ -299,6 +300,43 @@ async def update_user_settings_by_session_user(
299300
)
300301

301302

303+
############################
304+
# GetUserStatusBySessionUser
305+
############################
306+
307+
308+
@router.get("/user/status")
309+
async def get_user_status_by_session_user(user=Depends(get_verified_user)):
310+
user = Users.get_user_by_id(user.id)
311+
if user:
312+
return user
313+
else:
314+
raise HTTPException(
315+
status_code=status.HTTP_400_BAD_REQUEST,
316+
detail=ERROR_MESSAGES.USER_NOT_FOUND,
317+
)
318+
319+
320+
############################
321+
# UpdateUserStatusBySessionUser
322+
############################
323+
324+
325+
@router.post("/user/status/update")
326+
async def update_user_status_by_session_user(
327+
form_data: UserStatus, user=Depends(get_verified_user)
328+
):
329+
user = Users.get_user_by_id(user.id)
330+
if user:
331+
user = Users.update_user_status_by_id(user.id, form_data)
332+
return user
333+
else:
334+
raise HTTPException(
335+
status_code=status.HTTP_400_BAD_REQUEST,
336+
detail=ERROR_MESSAGES.USER_NOT_FOUND,
337+
)
338+
339+
302340
############################
303341
# GetUserInfoBySessionUser
304342
############################
@@ -350,9 +388,10 @@ async def update_user_info_by_session_user(
350388
############################
351389

352390

353-
class UserActiveResponse(BaseModel):
391+
class UserActiveResponse(UserStatus):
354392
name: str
355393
profile_image_url: Optional[str] = None
394+
356395
is_active: bool
357396
model_config = ConfigDict(extra="allow")
358397

@@ -377,8 +416,7 @@ async def get_user_by_id(user_id: str, user=Depends(get_verified_user)):
377416
if user:
378417
return UserActiveResponse(
379418
**{
380-
"id": user.id,
381-
"name": user.name,
419+
**user.model_dump(),
382420
"is_active": Users.is_user_active(user_id),
383421
}
384422
)

src/lib/apis/users/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,36 @@ export const getUserById = async (token: string, userId: string) => {
327327
return res;
328328
};
329329

330+
export const updateUserStatus = async (token: string, formData: object) => {
331+
let error = null;
332+
333+
const res = await fetch(`${WEBUI_API_BASE_URL}/users/user/status/update`, {
334+
method: 'POST',
335+
headers: {
336+
'Content-Type': 'application/json',
337+
Authorization: `Bearer ${token}`
338+
},
339+
body: JSON.stringify({
340+
...formData
341+
})
342+
})
343+
.then(async (res) => {
344+
if (!res.ok) throw await res.json();
345+
return res.json();
346+
})
347+
.catch((err) => {
348+
console.error(err);
349+
error = err.detail;
350+
return null;
351+
});
352+
353+
if (error) {
354+
throw error;
355+
}
356+
357+
return res;
358+
};
359+
330360
export const getUserInfo = async (token: string) => {
331361
let error = null;
332362
const res = await fetch(`${WEBUI_API_BASE_URL}/users/user/info`, {

src/lib/components/channel/Messages/Message/UserStatus.svelte

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import ChatBubble from '$lib/components/icons/ChatBubble.svelte';
1212
import ChatBubbleOval from '$lib/components/icons/ChatBubbleOval.svelte';
1313
import { goto } from '$app/navigation';
14+
import Emoji from '$lib/components/common/Emoji.svelte';
15+
import Tooltip from '$lib/components/common/Tooltip.svelte';
1416
1517
export let user = null;
1618
@@ -71,6 +73,25 @@
7173
</div>
7274
</div>
7375

76+
{#if user?.status_emoji || user?.status_message}
77+
<div class="mx-2 mt-2">
78+
<Tooltip content={user?.status_message}>
79+
<div
80+
class="mb-1 w-full gap-2 px-2.5 py-1.5 rounded-xl bg-gray-50 dark:text-white dark:bg-gray-900/50 text-black transition text-xs flex items-center"
81+
>
82+
{#if user?.status_emoji}
83+
<div class=" self-center shrink-0">
84+
<Emoji className="size-4" shortCode={user?.status_emoji} />
85+
</div>
86+
{/if}
87+
<div class=" self-center line-clamp-2 flex-1 text-left">
88+
{user?.status_message}
89+
</div>
90+
</div>
91+
</Tooltip>
92+
</div>
93+
{/if}
94+
7495
{#if $_user?.id !== user.id}
7596
<hr class="border-gray-100/50 dark:border-gray-800/50 my-2.5" />
7697

src/lib/components/channel/Messages/Message/UserStatusLinkPreview.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
{#if user}
2828
<LinkPreview.Content
29-
class="w-full max-w-[260px] rounded-2xl border border-gray-100 dark:border-gray-800 z-[99999] bg-white dark:bg-gray-850 dark:text-white shadow-lg transition"
29+
class="w-full max-w-[260px] rounded-2xl border border-gray-100 dark:border-gray-800 z-[9999] bg-white dark:bg-gray-850 dark:text-white shadow-lg transition"
3030
{side}
3131
{align}
3232
{sideOffset}

src/lib/components/layout/Sidebar.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,7 @@
718718
{#if $user !== undefined && $user !== null}
719719
<UserMenu
720720
role={$user?.role}
721+
profile={true}
721722
showActiveUsers={false}
722723
on:show={(e) => {
723724
if (e.detail === 'archived-chat') {
@@ -1281,6 +1282,7 @@
12811282
{#if $user !== undefined && $user !== null}
12821283
<UserMenu
12831284
role={$user?.role}
1285+
profile={true}
12841286
showActiveUsers={false}
12851287
on:show={(e) => {
12861288
if (e.detail === 'archived-chat') {

src/lib/components/layout/Sidebar/ChatItem.svelte

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,14 @@
476476
on:mouseenter={() => {
477477
ignoreBlur = true;
478478
}}
479+
on:click={(e) => {
480+
e.preventDefault();
481+
e.stopImmediatePropagation();
482+
e.stopPropagation();
483+
484+
generateTitleHandler();
485+
ignoreBlur = false;
486+
}}
479487
>
480488
<Sparkles strokeWidth="2" />
481489
</button>

0 commit comments

Comments
 (0)