diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index a9e18ec02a..a5525873ec 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -191,6 +191,12 @@ "create": "Create", "folderPath": "Path to store your folder", "locationCannotBeEmpty": "Path cannot be empty" + }, + "user": { + "name": "Name", + "icon": "Icon", + "selectAnIcon": "Select an icon", + "pleaseInputYourOpenAIKey": "please input your OpenAI key" } }, "grid": { diff --git a/frontend/app_flowy/lib/user/application/user_service.dart b/frontend/app_flowy/lib/user/application/user_service.dart index 7a40bb5281..cb20191d8a 100644 --- a/frontend/app_flowy/lib/user/application/user_service.dart +++ b/frontend/app_flowy/lib/user/application/user_service.dart @@ -21,6 +21,7 @@ class UserService { String? password, String? email, String? iconUrl, + String? openAIKey, }) { var payload = UpdateUserProfilePayloadPB.create()..id = userId; @@ -40,6 +41,10 @@ class UserService { payload.iconUrl = iconUrl; } + if (openAIKey != null) { + payload.openaiKey = openAIKey; + } + return UserEventUpdateUserProfile(payload).send(); } diff --git a/frontend/app_flowy/lib/workspace/application/user/settings_user_bloc.dart b/frontend/app_flowy/lib/workspace/application/user/settings_user_bloc.dart index 8507ff3266..f0168f4c0e 100644 --- a/frontend/app_flowy/lib/workspace/application/user/settings_user_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/user/settings_user_bloc.dart @@ -43,6 +43,14 @@ class SettingsUserViewBloc extends Bloc { ); }); }, + updateUserOpenaiKey: (openAIKey) { + _userService.updateUserProfile(openAIKey: openAIKey).then((result) { + result.fold( + (l) => null, + (err) => Log.error(err), + ); + }); + }, ); }); } @@ -73,6 +81,8 @@ class SettingsUserEvent with _$SettingsUserEvent { const factory SettingsUserEvent.updateUserName(String name) = _UpdateUserName; const factory SettingsUserEvent.updateUserIcon(String iconUrl) = _UpdateUserIcon; + const factory SettingsUserEvent.updateUserOpenaiKey(String openAIKey) = + _UpdateUserOpenaiKey; const factory SettingsUserEvent.didReceiveUserProfile( UserProfilePB newUserProfile) = _DidReceiveUserProfile; } diff --git a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart index 9173f865b3..1e4e7ef259 100644 --- a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart +++ b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart @@ -7,6 +7,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; import 'package:flowy_infra/image.dart'; +import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'dart:convert'; @@ -28,7 +30,9 @@ class SettingsUserView extends StatelessWidget { children: [ _renderUserNameInput(context), const VSpace(20), - _renderCurrentIcon(context) + _renderCurrentIcon(context), + const VSpace(20), + _renderCurrentOpenaiKey(context) ], ), ), @@ -49,6 +53,12 @@ class SettingsUserView extends StatelessWidget { } return _CurrentIcon(iconUrl); } + + Widget _renderCurrentOpenaiKey(BuildContext context) { + String openAIKey = + context.read().state.userProfile.openaiKey; + return _OpenaiKeyInput(openAIKey); + } } @visibleForTesting @@ -62,15 +72,41 @@ class UserNameInput extends StatelessWidget { @override Widget build(BuildContext context) { return TextField( - controller: TextEditingController()..text = name, - decoration: const InputDecoration( - labelText: 'Name', - ), - onSubmitted: (val) { - context - .read() - .add(SettingsUserEvent.updateUserName(val)); - }); + controller: TextEditingController()..text = name, + decoration: InputDecoration( + labelText: LocaleKeys.settings_user_name.tr(), + ), + onSubmitted: (val) { + context + .read() + .add(SettingsUserEvent.updateUserName(val)); + }, + ); + } +} + +class _OpenaiKeyInput extends StatelessWidget { + final String openAIKey; + const _OpenaiKeyInput( + this.openAIKey, { + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return TextField( + controller: TextEditingController()..text = openAIKey, + decoration: InputDecoration( + labelText: 'Openai Key', + hintText: LocaleKeys.settings_user_pleaseInputYourOpenAIKey.tr(), + ), + onSubmitted: (val) { + // TODO: validate key + context + .read() + .add(SettingsUserEvent.updateUserOpenaiKey(val)); + }, + ); } } @@ -96,7 +132,7 @@ class _CurrentIcon extends StatelessWidget { builder: (BuildContext context) { return SimpleDialog( title: FlowyText.medium( - 'Select an Icon', + LocaleKeys.settings_user_selectAnIcon.tr(), fontSize: FontSizes.s16, ), children: [ @@ -112,11 +148,11 @@ class _CurrentIcon extends StatelessWidget { }, child: Column( children: [ - const Align( + Align( alignment: Alignment.topLeft, child: Text( - "Icon", - style: TextStyle(color: Colors.grey), + LocaleKeys.settings_user_icon.tr(), + style: const TextStyle(color: Colors.grey), )), Align( alignment: Alignment.centerLeft, diff --git a/frontend/rust-lib/flowy-sqlite/migrations/2023-02-13-102237_user-add-openai_key/down.sql b/frontend/rust-lib/flowy-sqlite/migrations/2023-02-13-102237_user-add-openai_key/down.sql new file mode 100644 index 0000000000..ee523bb123 --- /dev/null +++ b/frontend/rust-lib/flowy-sqlite/migrations/2023-02-13-102237_user-add-openai_key/down.sql @@ -0,0 +1 @@ +ALTER TABLE user_table DROP COLUMN openai_key; \ No newline at end of file diff --git a/frontend/rust-lib/flowy-sqlite/migrations/2023-02-13-102237_user-add-openai_key/up.sql b/frontend/rust-lib/flowy-sqlite/migrations/2023-02-13-102237_user-add-openai_key/up.sql new file mode 100644 index 0000000000..f4882693d9 --- /dev/null +++ b/frontend/rust-lib/flowy-sqlite/migrations/2023-02-13-102237_user-add-openai_key/up.sql @@ -0,0 +1 @@ +ALTER TABLE user_table ADD COLUMN openai_key TEXT NOT NULL DEFAULT ''; \ No newline at end of file diff --git a/frontend/rust-lib/flowy-sqlite/src/schema.rs b/frontend/rust-lib/flowy-sqlite/src/schema.rs index 9ab84552b2..ef57bbaecd 100644 --- a/frontend/rust-lib/flowy-sqlite/src/schema.rs +++ b/frontend/rust-lib/flowy-sqlite/src/schema.rs @@ -146,6 +146,7 @@ diesel::table! { email -> Text, workspace -> Text, icon_url -> Text, + openai_key -> Text, } } diff --git a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs index 2ef29beb13..5e17b7df30 100644 --- a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs +++ b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs @@ -2,7 +2,8 @@ use crate::errors::ErrorCode; use flowy_derive::ProtoBuf; use std::convert::TryInto; use user_model::{ - UpdateUserProfileParams, UserEmail, UserIcon, UserId, UserName, UserPassword, UserProfile, + UpdateUserProfileParams, UserEmail, UserIcon, UserId, UserName, UserOpenaiKey, UserPassword, + UserProfile, }; #[derive(Default, ProtoBuf)] @@ -33,6 +34,9 @@ pub struct UserProfilePB { #[pb(index = 5)] pub icon_url: String, + + #[pb(index = 6)] + pub openai_key: String, } impl std::convert::From for UserProfilePB { @@ -43,6 +47,7 @@ impl std::convert::From for UserProfilePB { name: user_profile.name, token: user_profile.token, icon_url: user_profile.icon_url, + openai_key: user_profile.openai_key, } } } @@ -63,6 +68,9 @@ pub struct UpdateUserProfilePayloadPB { #[pb(index = 5, one_of)] pub icon_url: Option, + + #[pb(index = 6, one_of)] + pub openai_key: Option, } impl UpdateUserProfilePayloadPB { @@ -92,6 +100,11 @@ impl UpdateUserProfilePayloadPB { self.icon_url = Some(icon_url.to_owned()); self } + + pub fn openai_key(mut self, openai_key: &str) -> Self { + self.openai_key = Some(openai_key.to_owned()); + self + } } impl TryInto for UpdateUserProfilePayloadPB { @@ -120,12 +133,18 @@ impl TryInto for UpdateUserProfilePayloadPB { Some(icon_url) => Some(UserIcon::parse(icon_url)?.0), }; + let openai_key = match self.openai_key { + None => None, + Some(openai_key) => Some(UserOpenaiKey::parse(openai_key)?.0), + }; + Ok(UpdateUserProfileParams { id, name, email, password, icon_url, + openai_key, }) } } diff --git a/frontend/rust-lib/flowy-user/src/services/database.rs b/frontend/rust-lib/flowy-user/src/services/database.rs index 49d08d44a5..d54fe4aa94 100644 --- a/frontend/rust-lib/flowy-user/src/services/database.rs +++ b/frontend/rust-lib/flowy-user/src/services/database.rs @@ -84,6 +84,7 @@ pub struct UserTable { pub(crate) email: String, pub(crate) workspace: String, // deprecated pub(crate) icon_url: String, + pub(crate) openai_key: String, } impl UserTable { @@ -95,6 +96,7 @@ impl UserTable { token, icon_url: "".to_owned(), workspace: "".to_owned(), + openai_key: "".to_owned(), } } @@ -124,6 +126,7 @@ impl std::convert::From for UserProfile { name: table.name, token: table.token, icon_url: table.icon_url, + openai_key: table.openai_key, } } } @@ -136,6 +139,7 @@ pub struct UserTableChangeset { pub name: Option, pub email: Option, pub icon_url: Option, + pub openai_key: Option, } impl UserTableChangeset { @@ -146,6 +150,7 @@ impl UserTableChangeset { name: params.name, email: params.email, icon_url: params.icon_url, + openai_key: params.openai_key, } } } diff --git a/shared-lib/user-model/src/lib.rs b/shared-lib/user-model/src/lib.rs index 44983f0e00..6856c7c971 100644 --- a/shared-lib/user-model/src/lib.rs +++ b/shared-lib/user-model/src/lib.rs @@ -42,6 +42,7 @@ pub struct UserProfile { pub name: String, pub token: String, pub icon_url: String, + pub openai_key: String, } #[derive(Serialize, Deserialize, Default, Clone, Debug)] @@ -51,6 +52,7 @@ pub struct UpdateUserProfileParams { pub email: Option, pub password: Option, pub icon_url: Option, + pub openai_key: Option, } impl UpdateUserProfileParams { @@ -61,6 +63,7 @@ impl UpdateUserProfileParams { email: None, password: None, icon_url: None, + openai_key: None, } } @@ -83,4 +86,9 @@ impl UpdateUserProfileParams { self.icon_url = Some(icon_url.to_owned()); self } + + pub fn openai_key(mut self, openai_key: &str) -> Self { + self.openai_key = Some(openai_key.to_owned()); + self + } } diff --git a/shared-lib/user-model/src/parser/mod.rs b/shared-lib/user-model/src/parser/mod.rs index 792af0c146..c1b08df698 100644 --- a/shared-lib/user-model/src/parser/mod.rs +++ b/shared-lib/user-model/src/parser/mod.rs @@ -3,6 +3,7 @@ mod user_email; mod user_icon; mod user_id; mod user_name; +mod user_openai_key; mod user_password; mod user_workspace; @@ -10,5 +11,6 @@ pub use user_email::*; pub use user_icon::*; pub use user_id::*; pub use user_name::*; +pub use user_openai_key::*; pub use user_password::*; pub use user_workspace::*; diff --git a/shared-lib/user-model/src/parser/user_openai_key.rs b/shared-lib/user-model/src/parser/user_openai_key.rs new file mode 100644 index 0000000000..17a87ae048 --- /dev/null +++ b/shared-lib/user-model/src/parser/user_openai_key.rs @@ -0,0 +1,16 @@ +use crate::errors::UserErrorCode; + +#[derive(Debug)] +pub struct UserOpenaiKey(pub String); + +impl UserOpenaiKey { + pub fn parse(s: String) -> Result { + Ok(Self(s)) + } +} + +impl AsRef for UserOpenaiKey { + fn as_ref(&self) -> &str { + &self.0 + } +}