From 418da74756430c97a35a34d1b58d8ad1d6d3add8 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Fri, 26 Jan 2024 20:27:45 -0800 Subject: [PATCH 1/4] feat: profile image update backend --- backend/apps/web/models/auths.py | 4 +++ backend/apps/web/models/users.py | 14 ++++++++++ backend/apps/web/routers/auths.py | 45 +++++++++++++++++++++++-------- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/backend/apps/web/models/auths.py b/backend/apps/web/models/auths.py index 00c66a2c..2124e620 100644 --- a/backend/apps/web/models/auths.py +++ b/backend/apps/web/models/auths.py @@ -63,6 +63,10 @@ class SigninForm(BaseModel): password: str +class ProfileImageUrlForm(BaseModel): + profile_image_url: str + + class UpdatePasswordForm(BaseModel): password: str new_password: str diff --git a/backend/apps/web/models/users.py b/backend/apps/web/models/users.py index f86697f4..d387c8b5 100644 --- a/backend/apps/web/models/users.py +++ b/backend/apps/web/models/users.py @@ -108,6 +108,20 @@ class UsersTable: except: return None + def update_user_profile_image_url_by_id( + self, id: str, profile_image_url: str + ) -> Optional[UserModel]: + try: + query = User.update(profile_image_url=profile_image_url).where( + User.id == id + ) + query.execute() + + user = User.get(User.id == id) + return UserModel(**model_to_dict(user)) + except: + return None + def update_user_by_id(self, id: str, updated: dict) -> Optional[UserModel]: try: query = User.update(**updated).where(User.id == id) diff --git a/backend/apps/web/routers/auths.py b/backend/apps/web/routers/auths.py index a0772223..6a2f3895 100644 --- a/backend/apps/web/routers/auths.py +++ b/backend/apps/web/routers/auths.py @@ -11,6 +11,7 @@ import uuid from apps.web.models.auths import ( SigninForm, SignupForm, + ProfileImageUrlForm, UpdatePasswordForm, UserResponse, SigninResponse, @@ -40,14 +41,36 @@ async def get_session_user(user=Depends(get_current_user)): } +############################ +# Update Profile Image Url +############################ + + +@router.post("/update/profile", response_model=UserResponse) +async def update_profile_image_url( + form_data: ProfileImageUrlForm, session_user=Depends(get_current_user) +): + if session_user: + user = Users.update_user_profile_image_url_by_id( + session_user.id, form_data.profile_image_url + ) + if user: + return user + else: + raise HTTPException(400, detail=ERROR_MESSAGES.DEFAULT()) + else: + raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED) + + ############################ # Update Password ############################ @router.post("/update/password", response_model=bool) -async def update_password(form_data: UpdatePasswordForm, - session_user=Depends(get_current_user)): +async def update_password( + form_data: UpdatePasswordForm, session_user=Depends(get_current_user) +): if session_user: user = Auths.authenticate_user(session_user.email, form_data.password) @@ -93,18 +116,19 @@ async def signin(form_data: SigninForm): async def signup(request: Request, form_data: SignupForm): if not request.app.state.ENABLE_SIGNUP: raise HTTPException(400, detail=ERROR_MESSAGES.ACCESS_PROHIBITED) - + if not validate_email_format(form_data.email.lower()): raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT) - + if Users.get_user_by_email(form_data.email.lower()): raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN) - + try: role = "admin" if Users.get_num_users() == 0 else "pending" hashed = get_password_hash(form_data.password) - user = Auths.insert_new_auth(form_data.email.lower(), - hashed, form_data.name, role) + user = Auths.insert_new_auth( + form_data.email.lower(), hashed, form_data.name, role + ) if user: token = create_token(data={"email": user.email}) @@ -120,11 +144,10 @@ async def signup(request: Request, form_data: SignupForm): "profile_image_url": user.profile_image_url, } else: - raise HTTPException( - 500, detail=ERROR_MESSAGES.CREATE_USER_ERROR) + raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR) except Exception as err: - raise HTTPException(500, - detail=ERROR_MESSAGES.DEFAULT(err)) + raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err)) + ############################ # ToggleSignUp From 3ce8f3e8fbf150d078a50ade048bf81722d81275 Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Fri, 26 Jan 2024 21:22:25 -0800 Subject: [PATCH 2/4] feat: profile update frontend integration --- backend/apps/web/models/auths.py | 5 + backend/apps/web/routers/auths.py | 13 +- src/lib/apis/auths/index.ts | 31 +++ .../components/chat/Settings/Account.svelte | 168 ++++++++++++++ .../Settings/Account/UpdatePassword.svelte | 106 +++++++++ src/lib/components/chat/SettingsModal.svelte | 215 +----------------- 6 files changed, 324 insertions(+), 214 deletions(-) create mode 100644 src/lib/components/chat/Settings/Account.svelte create mode 100644 src/lib/components/chat/Settings/Account/UpdatePassword.svelte diff --git a/backend/apps/web/models/auths.py b/backend/apps/web/models/auths.py index 2124e620..367db3ff 100644 --- a/backend/apps/web/models/auths.py +++ b/backend/apps/web/models/auths.py @@ -67,6 +67,11 @@ class ProfileImageUrlForm(BaseModel): profile_image_url: str +class UpdateProfileForm(BaseModel): + profile_image_url: str + name: str + + class UpdatePasswordForm(BaseModel): password: str new_password: str diff --git a/backend/apps/web/routers/auths.py b/backend/apps/web/routers/auths.py index 6a2f3895..f45c67ac 100644 --- a/backend/apps/web/routers/auths.py +++ b/backend/apps/web/routers/auths.py @@ -11,7 +11,7 @@ import uuid from apps.web.models.auths import ( SigninForm, SignupForm, - ProfileImageUrlForm, + UpdateProfileForm, UpdatePasswordForm, UserResponse, SigninResponse, @@ -42,17 +42,18 @@ async def get_session_user(user=Depends(get_current_user)): ############################ -# Update Profile Image Url +# Update Profile ############################ @router.post("/update/profile", response_model=UserResponse) -async def update_profile_image_url( - form_data: ProfileImageUrlForm, session_user=Depends(get_current_user) +async def update_profile( + form_data: UpdateProfileForm, session_user=Depends(get_current_user) ): if session_user: - user = Users.update_user_profile_image_url_by_id( - session_user.id, form_data.profile_image_url + user = Users.update_user_by_id( + session_user.id, + {"profile_image_url": form_data.profile_image_url, "name": form_data.name}, ) if user: return user diff --git a/src/lib/apis/auths/index.ts b/src/lib/apis/auths/index.ts index 8734a588..5f16f83f 100644 --- a/src/lib/apis/auths/index.ts +++ b/src/lib/apis/auths/index.ts @@ -89,6 +89,37 @@ export const userSignUp = async (name: string, email: string, password: string) return res; }; +export const updateUserProfile = async (token: string, name: string, profileImageUrl: string) => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/auths/update/profile`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + }, + body: JSON.stringify({ + name: name, + profile_image_url: profileImageUrl + }) + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.log(err); + error = err.detail; + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const updateUserPassword = async (token: string, password: string, newPassword: string) => { let error = null; diff --git a/src/lib/components/chat/Settings/Account.svelte b/src/lib/components/chat/Settings/Account.svelte new file mode 100644 index 00000000..e9196a1f --- /dev/null +++ b/src/lib/components/chat/Settings/Account.svelte @@ -0,0 +1,168 @@ + + +
+
+ { + const files = e?.target?.files ?? []; + let reader = new FileReader(); + reader.onload = (event) => { + let originalImageUrl = `${event.target.result}`; + + const img = new Image(); + img.src = originalImageUrl; + + img.onload = function () { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + + // Calculate the aspect ratio of the image + const aspectRatio = img.width / img.height; + + // Calculate the new width and height to fit within 100x100 + let newWidth, newHeight; + if (aspectRatio > 1) { + newWidth = 100 * aspectRatio; + newHeight = 100; + } else { + newWidth = 100; + newHeight = 100 / aspectRatio; + } + + // Set the canvas size + canvas.width = 100; + canvas.height = 100; + + // Calculate the position to center the image + const offsetX = (100 - newWidth) / 2; + const offsetY = (100 - newHeight) / 2; + + // Draw the image on the canvas + ctx.drawImage(img, offsetX, offsetY, newWidth, newHeight); + + // Get the base64 representation of the compressed image + const compressedSrc = canvas.toDataURL('image/jpeg'); + + // Display the compressed image + profileImageUrl = compressedSrc; + + e.target.files = null; + }; + }; + + if ( + files.length > 0 && + ['image/gif', 'image/jpeg', 'image/png'].includes(files[0]['type']) + ) { + reader.readAsDataURL(files[0]); + } + }} + /> + +
Profile
+ +
+
+ +
+ +
+
+
Name
+ +
+ +
+
+
+
+ +
+ +
+ +
+ +
+
diff --git a/src/lib/components/chat/Settings/Account/UpdatePassword.svelte b/src/lib/components/chat/Settings/Account/UpdatePassword.svelte new file mode 100644 index 00000000..38c25028 --- /dev/null +++ b/src/lib/components/chat/Settings/Account/UpdatePassword.svelte @@ -0,0 +1,106 @@ + + +
{ + updatePasswordHandler(); + }} +> +
+
Change Password
+ +
+ + {#if show} +
+
+
Current Password
+ +
+ +
+
+ +
+
New Password
+ +
+ +
+
+ +
+
Confirm Password
+ +
+ +
+
+
+ +
+ +
+ {/if} +
diff --git a/src/lib/components/chat/SettingsModal.svelte b/src/lib/components/chat/SettingsModal.svelte index f04e9e5f..e348c807 100644 --- a/src/lib/components/chat/SettingsModal.svelte +++ b/src/lib/components/chat/SettingsModal.svelte @@ -36,6 +36,8 @@ import { resetVectorDB } from '$lib/apis/rag'; import { setDefaultPromptSuggestions } from '$lib/apis/configs'; import { getBackendConfig } from '$lib/apis'; + import UpdatePassword from './Settings/Account/UpdatePassword.svelte'; + import Account from './Settings/Account.svelte'; export let show = false; @@ -126,6 +128,7 @@ let authContent = ''; // Account + let profileImageUrl = ''; let currentPassword = ''; let newPassword = ''; let newPasswordConfirm = ''; @@ -559,31 +562,6 @@ return models; }; - const updatePasswordHandler = async () => { - if (newPassword === newPasswordConfirm) { - const res = await updateUserPassword(localStorage.token, currentPassword, newPassword).catch( - (error) => { - toast.error(error); - return null; - } - ); - - if (res) { - toast.success('Successfully updated.'); - } - - currentPassword = ''; - newPassword = ''; - newPasswordConfirm = ''; - } else { - toast.error( - `The passwords you entered don't quite match. Please double-check and try again.` - ); - newPassword = ''; - newPasswordConfirm = ''; - } - }; - onMount(async () => { console.log('settings', $user.role === 'admin'); if ($user.role === 'admin') { @@ -616,7 +594,6 @@ responseAutoCopy = settings.responseAutoCopy ?? false; titleAutoGenerateModel = settings.titleAutoGenerateModel ?? ''; gravatarEmail = settings.gravatarEmail ?? ''; - speakVoice = settings.speakVoice ?? ''; const getVoicesLoop = setInterval(async () => { @@ -631,12 +608,6 @@ saveChatHistory = settings.saveChatHistory ?? true; - authEnabled = settings.authHeader !== undefined ? true : false; - if (authEnabled) { - authType = settings.authHeader.split(' ')[0]; - authContent = settings.authHeader.split(' ')[1]; - } - ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => { return ''; }); @@ -2040,184 +2011,12 @@ {/if} - {:else if selectedTab === 'auth'} -
{ - console.log('auth save'); - saveSettings({ - authHeader: authEnabled ? `${authType} ${authContent}` : undefined - }); + {:else if selectedTab === 'account'} + { show = false; }} - > -
-
-
-
Authorization Header
- - -
-
- - {#if authEnabled} -
- -
-
- - -
- -
-
-
- Toggle between 'Basic' - and 'Bearer' by - clicking on the label next to the input. -
-
- -
- -
-
Preview Authorization Header
-