feat: switch ai mode on mobile (#7391)

* fix: the default page name should be empty when creating

* feat: switch ai model on mobile
This commit is contained in:
Lucas 2025-02-17 16:08:38 +08:00 committed by GitHub
parent 15b4d496fd
commit 55fbb7522b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 164 additions and 29 deletions

View File

@ -3,16 +3,19 @@ import 'package:appflowy/env/env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/mobile/presentation/setting/ai/ai_settings_group.dart';
import 'package:appflowy/mobile/presentation/setting/cloud/cloud_setting_group.dart';
import 'package:appflowy/mobile/presentation/setting/user_session_setting_group.dart';
import 'package:appflowy/mobile/presentation/setting/workspace/workspace_setting_group.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileHomeSettingPage extends StatefulWidget {
const MobileHomeSettingPage({
@ -70,29 +73,47 @@ class _MobileHomeSettingPageState extends State<MobileHomeSettingPage> {
Widget _buildSettingsWidget(UserProfilePB userProfile) {
// show the third-party sign in buttons if user logged in with local session and auth is enabled.
final showThirdPartyLogin =
final isLocalAuthEnabled =
userProfile.authenticator == AuthenticatorPB.Local && isAuthEnabled;
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
PersonalInfoSettingGroup(
userProfile: userProfile,
'';
return BlocProvider(
create: (context) => UserWorkspaceBloc(userProfile: userProfile)
..add(const UserWorkspaceEvent.initial()),
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
builder: (context, state) {
final currentWorkspaceId = state.currentWorkspace?.workspaceId ?? '';
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
PersonalInfoSettingGroup(
userProfile: userProfile,
),
const WorkspaceSettingGroup(),
const AppearanceSettingGroup(),
const LanguageSettingGroup(),
if (Env.enableCustomCloud) const CloudSettingGroup(),
if (isAuthEnabled)
AiSettingsGroup(
key: ValueKey(currentWorkspaceId),
userProfile: userProfile,
workspaceId: currentWorkspaceId,
currentWorkspaceMemberRole: state.currentWorkspace?.role,
),
const SupportSettingGroup(),
const AboutSettingGroup(),
UserSessionSettingGroup(
userProfile: userProfile,
showThirdPartyLogin: isLocalAuthEnabled,
),
const VSpace(20),
],
),
),
const WorkspaceSettingGroup(),
const AppearanceSettingGroup(),
const LanguageSettingGroup(),
if (Env.enableCustomCloud) const CloudSettingGroup(),
const SupportSettingGroup(),
const AboutSettingGroup(),
UserSessionSettingGroup(
userProfile: userProfile,
showThirdPartyLogin: showThirdPartyLogin,
),
const VSpace(20),
],
),
);
},
),
);
}

View File

@ -0,0 +1,106 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_group_widget.dart';
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pbenum.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
class AiSettingsGroup extends StatelessWidget {
const AiSettingsGroup({
super.key,
required this.userProfile,
required this.workspaceId,
this.currentWorkspaceMemberRole,
});
final UserProfilePB userProfile;
final String workspaceId;
final AFRolePB? currentWorkspaceMemberRole;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return BlocProvider(
create: (context) => SettingsAIBloc(
userProfile,
workspaceId,
currentWorkspaceMemberRole,
)..add(const SettingsAIEvent.started()),
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
builder: (context, state) {
return MobileSettingGroup(
groupTitle: LocaleKeys.settings_aiPage_title.tr(),
settingItemList: [
MobileSettingItem(
name: LocaleKeys.settings_aiPage_keys_llmModelType.tr(),
trailing: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: FlowyText(
state.selectedAIModel,
color: theme.colorScheme.onSurface,
overflow: TextOverflow.ellipsis,
),
),
const Icon(Icons.chevron_right),
],
),
),
onTap: () => _onLLMModelTypeTap(context, state),
),
// enable AI search if needed
// MobileSettingItem(
// name: LocaleKeys.settings_aiPage_keys_enableAISearchTitle.tr(),
// trailing: const Icon(
// Icons.chevron_right,
// ),
// onTap: () => context.push(AppFlowyCloudPage.routeName),
// ),
],
);
},
),
);
}
void _onLLMModelTypeTap(BuildContext context, SettingsAIState state) {
final availableModels = state.availableModels;
showMobileBottomSheet(
context,
showHeader: true,
showDragHandle: true,
showDivider: false,
title: LocaleKeys.settings_aiPage_keys_llmModelType.tr(),
builder: (_) {
return Column(
children: availableModels
.mapIndexed(
(index, model) => FlowyOptionTile.checkbox(
text: model,
showTopBorder: index == 0,
isSelected: state.selectedAIModel == model,
onTap: () {
context
.read<SettingsAIBloc>()
.add(SettingsAIEvent.selectModel(model));
context.pop();
},
),
)
.toList(),
);
},
);
}
}

View File

@ -91,7 +91,9 @@ class FavoriteBloc extends Bloc<FavoriteEvent, FavoriteState> {
await _service.toggleFavorite(view.item.id);
await _service.toggleFavorite(view.item.id);
}
add(const FavoriteEvent.fetchFavorites());
if (!isClosed) {
add(const FavoriteEvent.fetchFavorites());
}
isReordering = false;
},
);

View File

@ -57,8 +57,8 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
}
void _dispatch() {
on<SettingsAIEvent>((event, emit) {
event.when(
on<SettingsAIEvent>((event, emit) async {
await event.when(
started: () {
_userListener.start(
onProfileUpdated: _onProfileUpdated,
@ -83,13 +83,14 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
!(state.aiSettings?.disableSearchIndexing ?? false),
);
},
selectModel: (String model) {
_updateUserWorkspaceSetting(model: model);
selectModel: (String model) async {
await _updateUserWorkspaceSetting(model: model);
},
didLoadAISetting: (UseAISettingPB settings) {
emit(
state.copyWith(
aiSettings: settings,
selectedAIModel: settings.aiModel,
enableSearchIndexing: !settings.disableSearchIndexing,
),
);
@ -129,10 +130,10 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
});
}
void _updateUserWorkspaceSetting({
Future<FlowyResult<void, FlowyError>> _updateUserWorkspaceSetting({
bool? disableSearchIndexing,
String? model,
}) {
}) async {
final payload = UpdateUserWorkspaceSettingPB(
workspaceId: workspaceId,
);
@ -142,7 +143,12 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
if (model != null) {
payload.aiModel = model;
}
UserEventUpdateWorkspaceSetting(payload).send();
final result = await UserEventUpdateWorkspaceSetting(payload).send();
result.fold(
(ok) => Log.info('Update workspace setting success'),
(err) => Log.error('Update workspace setting failed: $err'),
);
return result;
}
void _onProfileUpdated(