Skip to content

Commit

Permalink
feat: ai settings page
Browse files Browse the repository at this point in the history
  • Loading branch information
Xazin committed Jun 19, 2024
1 parent fa86480 commit 368264b
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'package:appflowy/user/application/user_listener.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'settings_ai_bloc.freezed.dart';

class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
SettingsAIBloc(this.userProfile)
: _userListener = UserListener(userProfile: userProfile),
_userService = UserBackendService(userId: userProfile.id),
super(SettingsAIState(userProfile: userProfile)) {
_dispatch();
}

final UserBackendService _userService;
final UserListener _userListener;
final UserProfilePB userProfile;

@override
Future<void> close() async {
await _userListener.stop();
return super.close();
}

void _dispatch() {
on<SettingsAIEvent>((event, emit) {
event.when(
started: () {
_userListener.start(onProfileUpdated: _onProfileUpdated);
},
didReceiveUserProfile: (userProfile) =>
emit(state.copyWith(userProfile: userProfile)),
updateUserOpenAIKey: (openAIKey) {
_userService.updateUserProfile(openAIKey: openAIKey).then((result) {
result.fold(
(l) => null,
(err) => Log.error(err),
);
});
},
updateUserStabilityAIKey: (stabilityAIKey) {
_userService
.updateUserProfile(stabilityAiKey: stabilityAIKey)
.then((result) {
result.fold(
(l) => null,
(err) => Log.error(err),
);
});
},
);
});
}

void _onProfileUpdated(
FlowyResult<UserProfilePB, FlowyError> userProfileOrFailed,
) =>
userProfileOrFailed.fold(
(newUserProfile) =>
add(SettingsAIEvent.didReceiveUserProfile(newUserProfile)),
(err) => Log.error(err),
);
}

@freezed
class SettingsAIEvent with _$SettingsAIEvent {
const factory SettingsAIEvent.started() = _Started;

const factory SettingsAIEvent.updateUserOpenAIKey(String openAIKey) =
_UpdateUserOpenaiKey;

const factory SettingsAIEvent.updateUserStabilityAIKey(
String stabilityAIKey,
) = _UpdateUserStabilityAIKey;

const factory SettingsAIEvent.didReceiveUserProfile(
UserProfilePB newUserProfile,
) = _DidReceiveUserProfile;
}

@freezed
class SettingsAIState with _$SettingsAIState {
const factory SettingsAIState({
required UserProfilePB userProfile,
}) = _SettingsAIState;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ enum SettingsPage {
account,
workspace,
manageData,
shortcuts,
ai,
plan,
billing,
// OLD
notifications,
cloud,
shortcuts,
member,
featureFlags,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,24 +63,6 @@ class SettingsUserViewBloc extends Bloc<SettingsUserEvent, SettingsUserState> {
);
});
},
updateUserOpenAIKey: (openAIKey) {
_userService.updateUserProfile(openAIKey: openAIKey).then((result) {
result.fold(
(l) => null,
(err) => Log.error(err),
);
});
},
updateUserStabilityAIKey: (stabilityAIKey) {
_userService
.updateUserProfile(stabilityAiKey: stabilityAIKey)
.then((result) {
result.fold(
(l) => null,
(err) => Log.error(err),
);
});
},
updateUserEmail: (String email) {
_userService.updateUserProfile(email: email).then((result) {
result.fold(
Expand Down Expand Up @@ -127,11 +109,6 @@ class SettingsUserEvent with _$SettingsUserEvent {
const factory SettingsUserEvent.updateUserIcon({required String iconUrl}) =
_UpdateUserIcon;
const factory SettingsUserEvent.removeUserIcon() = _RemoveUserIcon;
const factory SettingsUserEvent.updateUserOpenAIKey(String openAIKey) =
_UpdateUserOpenaiKey;
const factory SettingsUserEvent.updateUserStabilityAIKey(
String stabilityAIKey,
) = _UpdateUserStabilityAIKey;
const factory SettingsUserEvent.didReceiveUserProfile(
UserProfilePB newUserProfile,
) = _DidReceiveUserProfile;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,39 +136,7 @@ class _SettingsAccountViewState extends State<SettingsAccountView> {
// ),
// ],
// ),
SettingsCategory(
title: LocaleKeys.settings_accountPage_keys_title.tr(),
children: [
SettingsInputField(
label:
LocaleKeys.settings_accountPage_keys_openAILabel.tr(),
tooltip:
LocaleKeys.settings_accountPage_keys_openAITooltip.tr(),
placeholder:
LocaleKeys.settings_accountPage_keys_openAIHint.tr(),
value: state.userProfile.openaiKey,
obscureText: true,
onSave: (key) => context
.read<SettingsUserViewBloc>()
.add(SettingsUserEvent.updateUserOpenAIKey(key)),
),
SettingsInputField(
label: LocaleKeys.settings_accountPage_keys_stabilityAILabel
.tr(),
tooltip: LocaleKeys
.settings_accountPage_keys_stabilityAITooltip
.tr(),
placeholder: LocaleKeys
.settings_accountPage_keys_stabilityAIHint
.tr(),
value: state.userProfile.stabilityAiKey,
obscureText: true,
onSave: (key) => context
.read<SettingsUserViewBloc>()
.add(SettingsUserEvent.updateUserStabilityAIKey(key)),
),
],
),

SettingsCategory(
title: LocaleKeys.settings_accountPage_login_title.tr(),
children: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import 'package:flutter/material.dart';

import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_dashed_divider.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_input_field.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class SettingsAIView extends StatelessWidget {
const SettingsAIView({
super.key,
required this.userProfile,
});

final UserProfilePB userProfile;

@override
Widget build(BuildContext context) {
return BlocProvider<SettingsAIBloc>(
create: (context) =>
SettingsAIBloc(userProfile)..add(const SettingsAIEvent.started()),
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
builder: (context, state) {
return SettingsBody(
title: LocaleKeys.settings_aiPage_title.tr(),
children: [
SettingsCategory(
title: LocaleKeys.settings_aiPage_keys_title.tr(),
children: [
SettingsInputField(
label: LocaleKeys.settings_aiPage_keys_openAILabel.tr(),
tooltip: LocaleKeys.settings_aiPage_keys_openAITooltip.tr(),
placeholder:
LocaleKeys.settings_aiPage_keys_openAIHint.tr(),
value: state.userProfile.openaiKey,
obscureText: true,
onSave: (key) => context
.read<SettingsAIBloc>()
.add(SettingsAIEvent.updateUserOpenAIKey(key)),
),
SettingsInputField(
label:
LocaleKeys.settings_aiPage_keys_stabilityAILabel.tr(),
tooltip:
LocaleKeys.settings_aiPage_keys_stabilityAITooltip.tr(),
placeholder:
LocaleKeys.settings_aiPage_keys_stabilityAIHint.tr(),
value: state.userProfile.stabilityAiKey,
obscureText: true,
onSave: (key) => context
.read<SettingsAIBloc>()
.add(SettingsAIEvent.updateUserStabilityAIKey(key)),
),
const SettingsDashedDivider(),
// TODO(Nathan): Propagate this value from `state`
const _AIChatToggle(value: false),
],
),
],
);
},
),
);
}
}

class _AIChatToggle extends StatelessWidget {
const _AIChatToggle({required this.value});

final bool value;

@override
Widget build(BuildContext context) {
return Row(
children: [
const Expanded(
child: FlowyText.regular(
'Enable AI Chat', // TODO(Nathan): Change and localize in `aiPage` in en.json
fontSize: 16,
),
),
const HSpace(16),
Toggle(
style: ToggleStyle.big,
value: value,
onChanged: (_) {
// TODO(Nathan): Add this event
// context
// .read<SettingsAIBloc>()
// .add(SettingsAIEvent.toggleAIChat());
},
),
],
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_ai_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_billing_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_manage_data_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_plan_view.dart';
Expand Down Expand Up @@ -113,6 +114,8 @@ class SettingsDialog extends StatelessWidget {
return SettingCloud(restartAppFlowy: () => restartApp());
case SettingsPage.shortcuts:
return const SettingsShortcutsView();
case SettingsPage.ai:
return SettingsAIView(userProfile: user);
case SettingsPage.member:
return WorkspaceMembersPage(userProfile: user);
case SettingsPage.plan:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ class SettingsMenu extends StatelessWidget {
icon: const FlowySvg(FlowySvgs.settings_shortcuts_m),
changeSelectedPage: changeSelectedPage,
),
SettingsMenuElement(
page: SettingsPage.ai,
selectedPage: currentPage,
label: LocaleKeys.settings_aiPage_menuLabel.tr(),
icon: const FlowySvg(
FlowySvgs.ai_summary_generate_s,
size: Size.square(24),
),
changeSelectedPage: changeSelectedPage,
),
if (FeatureFlag.planBilling.isOn &&
userProfile.authenticator ==
AuthenticatorPB.AppFlowyCloud &&
Expand Down
22 changes: 13 additions & 9 deletions frontend/resources/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -369,15 +369,6 @@
"change": "Change email"
}
},
"keys": {
"title": "AI API Keys",
"openAILabel": "OpenAI API key",
"openAITooltip": "You can find your Secret API key on the API key page",
"openAIHint": "Input your OpenAI API Key",
"stabilityAILabel": "Stability API key",
"stabilityAITooltip": "Your Stability API key, used to authenticate your requests",
"stabilityAIHint": "Input your Stability API Key"
},
"login": {
"title": "Account login",
"loginLabel": "Log in",
Expand Down Expand Up @@ -611,6 +602,19 @@
"couldNotLoadErrorMsg": "Could not load shortcuts, Try again",
"couldNotSaveErrorMsg": "Could not save shortcuts, Try again"
},
"aiPage": {
"title": "AI Settings",
"menuLabel": "AI Settings",
"keys": {
"title": "AI API Keys",
"openAILabel": "OpenAI API key",
"openAITooltip": "You can find your Secret API key on the API key page",
"openAIHint": "Input your OpenAI API Key",
"stabilityAILabel": "Stability API key",
"stabilityAITooltip": "Your Stability API key, used to authenticate your requests",
"stabilityAIHint": "Input your Stability API Key"
}
},
"planPage": {
"menuLabel": "Plan",
"title": "Pricing plan",
Expand Down

0 comments on commit 368264b

Please sign in to comment.