Skip to content

Commit 55fbb75

Browse files
authored
feat: switch ai mode on mobile (#7391)
* fix: the default page name should be empty when creating * feat: switch ai model on mobile
1 parent 15b4d49 commit 55fbb75

File tree

4 files changed

+164
-29
lines changed

4 files changed

+164
-29
lines changed

frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ import 'package:appflowy/env/env.dart';
33
import 'package:appflowy/generated/locale_keys.g.dart';
44
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
55
import 'package:appflowy/mobile/presentation/presentation.dart';
6+
import 'package:appflowy/mobile/presentation/setting/ai/ai_settings_group.dart';
67
import 'package:appflowy/mobile/presentation/setting/cloud/cloud_setting_group.dart';
78
import 'package:appflowy/mobile/presentation/setting/user_session_setting_group.dart';
89
import 'package:appflowy/mobile/presentation/setting/workspace/workspace_setting_group.dart';
910
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
1011
import 'package:appflowy/startup/startup.dart';
1112
import 'package:appflowy/user/application/auth/auth_service.dart';
13+
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
1214
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
1315
import 'package:easy_localization/easy_localization.dart';
1416
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
1517
import 'package:flutter/material.dart';
18+
import 'package:flutter_bloc/flutter_bloc.dart';
1619

1720
class MobileHomeSettingPage extends StatefulWidget {
1821
const MobileHomeSettingPage({
@@ -70,29 +73,47 @@ class _MobileHomeSettingPageState extends State<MobileHomeSettingPage> {
7073
Widget _buildSettingsWidget(UserProfilePB userProfile) {
7174
// show the third-party sign in buttons if user logged in with local session and auth is enabled.
7275

73-
final showThirdPartyLogin =
76+
final isLocalAuthEnabled =
7477
userProfile.authenticator == AuthenticatorPB.Local && isAuthEnabled;
75-
return SingleChildScrollView(
76-
child: Padding(
77-
padding: const EdgeInsets.all(16),
78-
child: Column(
79-
children: [
80-
PersonalInfoSettingGroup(
81-
userProfile: userProfile,
82-
),
83-
const WorkspaceSettingGroup(),
84-
const AppearanceSettingGroup(),
85-
const LanguageSettingGroup(),
86-
if (Env.enableCustomCloud) const CloudSettingGroup(),
87-
const SupportSettingGroup(),
88-
const AboutSettingGroup(),
89-
UserSessionSettingGroup(
90-
userProfile: userProfile,
91-
showThirdPartyLogin: showThirdPartyLogin,
78+
'';
79+
80+
return BlocProvider(
81+
create: (context) => UserWorkspaceBloc(userProfile: userProfile)
82+
..add(const UserWorkspaceEvent.initial()),
83+
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
84+
builder: (context, state) {
85+
final currentWorkspaceId = state.currentWorkspace?.workspaceId ?? '';
86+
return SingleChildScrollView(
87+
child: Padding(
88+
padding: const EdgeInsets.all(16),
89+
child: Column(
90+
children: [
91+
PersonalInfoSettingGroup(
92+
userProfile: userProfile,
93+
),
94+
const WorkspaceSettingGroup(),
95+
const AppearanceSettingGroup(),
96+
const LanguageSettingGroup(),
97+
if (Env.enableCustomCloud) const CloudSettingGroup(),
98+
if (isAuthEnabled)
99+
AiSettingsGroup(
100+
key: ValueKey(currentWorkspaceId),
101+
userProfile: userProfile,
102+
workspaceId: currentWorkspaceId,
103+
currentWorkspaceMemberRole: state.currentWorkspace?.role,
104+
),
105+
const SupportSettingGroup(),
106+
const AboutSettingGroup(),
107+
UserSessionSettingGroup(
108+
userProfile: userProfile,
109+
showThirdPartyLogin: isLocalAuthEnabled,
110+
),
111+
const VSpace(20),
112+
],
113+
),
92114
),
93-
const VSpace(20),
94-
],
95-
),
115+
);
116+
},
96117
),
97118
);
98119
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import 'package:appflowy/generated/locale_keys.g.dart';
2+
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
3+
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_group_widget.dart';
4+
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart';
5+
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
6+
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
7+
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
8+
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pbenum.dart';
9+
import 'package:collection/collection.dart';
10+
import 'package:easy_localization/easy_localization.dart';
11+
import 'package:flowy_infra_ui/style_widget/text.dart';
12+
import 'package:flutter/material.dart';
13+
import 'package:flutter_bloc/flutter_bloc.dart';
14+
import 'package:go_router/go_router.dart';
15+
16+
class AiSettingsGroup extends StatelessWidget {
17+
const AiSettingsGroup({
18+
super.key,
19+
required this.userProfile,
20+
required this.workspaceId,
21+
this.currentWorkspaceMemberRole,
22+
});
23+
24+
final UserProfilePB userProfile;
25+
final String workspaceId;
26+
final AFRolePB? currentWorkspaceMemberRole;
27+
28+
@override
29+
Widget build(BuildContext context) {
30+
final theme = Theme.of(context);
31+
return BlocProvider(
32+
create: (context) => SettingsAIBloc(
33+
userProfile,
34+
workspaceId,
35+
currentWorkspaceMemberRole,
36+
)..add(const SettingsAIEvent.started()),
37+
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
38+
builder: (context, state) {
39+
return MobileSettingGroup(
40+
groupTitle: LocaleKeys.settings_aiPage_title.tr(),
41+
settingItemList: [
42+
MobileSettingItem(
43+
name: LocaleKeys.settings_aiPage_keys_llmModelType.tr(),
44+
trailing: ConstrainedBox(
45+
constraints: const BoxConstraints(maxWidth: 200),
46+
child: Row(
47+
mainAxisSize: MainAxisSize.min,
48+
children: [
49+
Flexible(
50+
child: FlowyText(
51+
state.selectedAIModel,
52+
color: theme.colorScheme.onSurface,
53+
overflow: TextOverflow.ellipsis,
54+
),
55+
),
56+
const Icon(Icons.chevron_right),
57+
],
58+
),
59+
),
60+
onTap: () => _onLLMModelTypeTap(context, state),
61+
),
62+
// enable AI search if needed
63+
// MobileSettingItem(
64+
// name: LocaleKeys.settings_aiPage_keys_enableAISearchTitle.tr(),
65+
// trailing: const Icon(
66+
// Icons.chevron_right,
67+
// ),
68+
// onTap: () => context.push(AppFlowyCloudPage.routeName),
69+
// ),
70+
],
71+
);
72+
},
73+
),
74+
);
75+
}
76+
77+
void _onLLMModelTypeTap(BuildContext context, SettingsAIState state) {
78+
final availableModels = state.availableModels;
79+
showMobileBottomSheet(
80+
context,
81+
showHeader: true,
82+
showDragHandle: true,
83+
showDivider: false,
84+
title: LocaleKeys.settings_aiPage_keys_llmModelType.tr(),
85+
builder: (_) {
86+
return Column(
87+
children: availableModels
88+
.mapIndexed(
89+
(index, model) => FlowyOptionTile.checkbox(
90+
text: model,
91+
showTopBorder: index == 0,
92+
isSelected: state.selectedAIModel == model,
93+
onTap: () {
94+
context
95+
.read<SettingsAIBloc>()
96+
.add(SettingsAIEvent.selectModel(model));
97+
context.pop();
98+
},
99+
),
100+
)
101+
.toList(),
102+
);
103+
},
104+
);
105+
}
106+
}

frontend/appflowy_flutter/lib/workspace/application/favorite/favorite_bloc.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ class FavoriteBloc extends Bloc<FavoriteEvent, FavoriteState> {
9191
await _service.toggleFavorite(view.item.id);
9292
await _service.toggleFavorite(view.item.id);
9393
}
94-
add(const FavoriteEvent.fetchFavorites());
94+
if (!isClosed) {
95+
add(const FavoriteEvent.fetchFavorites());
96+
}
9597
isReordering = false;
9698
},
9799
);

frontend/appflowy_flutter/lib/workspace/application/settings/ai/settings_ai_bloc.dart

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
5757
}
5858

5959
void _dispatch() {
60-
on<SettingsAIEvent>((event, emit) {
61-
event.when(
60+
on<SettingsAIEvent>((event, emit) async {
61+
await event.when(
6262
started: () {
6363
_userListener.start(
6464
onProfileUpdated: _onProfileUpdated,
@@ -83,13 +83,14 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
8383
!(state.aiSettings?.disableSearchIndexing ?? false),
8484
);
8585
},
86-
selectModel: (String model) {
87-
_updateUserWorkspaceSetting(model: model);
86+
selectModel: (String model) async {
87+
await _updateUserWorkspaceSetting(model: model);
8888
},
8989
didLoadAISetting: (UseAISettingPB settings) {
9090
emit(
9191
state.copyWith(
9292
aiSettings: settings,
93+
selectedAIModel: settings.aiModel,
9394
enableSearchIndexing: !settings.disableSearchIndexing,
9495
),
9596
);
@@ -129,10 +130,10 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
129130
});
130131
}
131132

132-
void _updateUserWorkspaceSetting({
133+
Future<FlowyResult<void, FlowyError>> _updateUserWorkspaceSetting({
133134
bool? disableSearchIndexing,
134135
String? model,
135-
}) {
136+
}) async {
136137
final payload = UpdateUserWorkspaceSettingPB(
137138
workspaceId: workspaceId,
138139
);
@@ -142,7 +143,12 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
142143
if (model != null) {
143144
payload.aiModel = model;
144145
}
145-
UserEventUpdateWorkspaceSetting(payload).send();
146+
final result = await UserEventUpdateWorkspaceSetting(payload).send();
147+
result.fold(
148+
(ok) => Log.info('Update workspace setting success'),
149+
(err) => Log.error('Update workspace setting failed: $err'),
150+
);
151+
return result;
146152
}
147153

148154
void _onProfileUpdated(

0 commit comments

Comments
 (0)