Skip to content

Commit ba1767e

Browse files
authored
Merge pull request #7628 from AppFlowy-IO/local_ai_global_config
chore: implement local ai config
2 parents 7372f55 + 10048da commit ba1767e

17 files changed

Lines changed: 173 additions & 188 deletions

File tree

frontend/appflowy_flutter/lib/ai/service/ai_input_control.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class AIModelStateNotifier {
1414
: _isDesktop = UniversalPlatform.isDesktop,
1515
_localAIListener =
1616
UniversalPlatform.isDesktop ? LocalAIStateListener() : null,
17-
_aiModelSwitchListener = AIModelSwitchListener(chatId: objectId);
17+
_aiModelSwitchListener = AIModelSwitchListener(objectId: objectId);
1818

1919
final String objectId;
2020
final bool _isDesktop;

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ class _MobileHomeSettingPageState extends State<MobileHomeSettingPage> {
100100
key: ValueKey(currentWorkspaceId),
101101
userProfile: userProfile,
102102
workspaceId: currentWorkspaceId,
103-
currentWorkspaceMemberRole: state.currentWorkspace?.role,
104103
),
105104
const SupportSettingGroup(),
106105
const AboutSettingGroup(),

frontend/appflowy_flutter/lib/mobile/presentation/setting/ai/ai_settings_group.dart

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_item
55
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
66
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
77
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';
108
import 'package:easy_localization/easy_localization.dart';
119
import 'package:flowy_infra_ui/style_widget/text.dart';
1210
import 'package:flutter/material.dart';
@@ -18,12 +16,10 @@ class AiSettingsGroup extends StatelessWidget {
1816
super.key,
1917
required this.userProfile,
2018
required this.workspaceId,
21-
this.currentWorkspaceMemberRole,
2219
});
2320

2421
final UserProfilePB userProfile;
2522
final String workspaceId;
26-
final AFRolePB? currentWorkspaceMemberRole;
2723

2824
@override
2925
Widget build(BuildContext context) {
@@ -32,7 +28,6 @@ class AiSettingsGroup extends StatelessWidget {
3228
create: (context) => SettingsAIBloc(
3329
userProfile,
3430
workspaceId,
35-
currentWorkspaceMemberRole,
3631
)..add(const SettingsAIEvent.started()),
3732
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
3833
builder: (context, state) {
@@ -48,7 +43,7 @@ class AiSettingsGroup extends StatelessWidget {
4843
children: [
4944
Flexible(
5045
child: FlowyText(
51-
state.selectedAIModel,
46+
state.availableModels?.selectedModel.name ?? "",
5247
color: theme.colorScheme.onSurface,
5348
overflow: TextOverflow.ellipsis,
5449
),
@@ -84,16 +79,19 @@ class AiSettingsGroup extends StatelessWidget {
8479
title: LocaleKeys.settings_aiPage_keys_llmModelType.tr(),
8580
builder: (_) {
8681
return Column(
87-
children: availableModels
88-
.mapIndexed(
89-
(index, model) => FlowyOptionTile.checkbox(
90-
text: model.name,
91-
showTopBorder: index == 0,
92-
isSelected: state.selectedAIModel == model.name,
82+
children: (availableModels?.models ?? [])
83+
.asMap()
84+
.entries
85+
.map(
86+
(entry) => FlowyOptionTile.checkbox(
87+
text: entry.value.name,
88+
showTopBorder: entry.key == 0,
89+
isSelected:
90+
availableModels?.selectedModel.name == entry.value.name,
9391
onTap: () {
9492
context
9593
.read<SettingsAIBloc>()
96-
.add(SettingsAIEvent.selectModel(model.name));
94+
.add(SettingsAIEvent.selectModel(entry.value));
9795
context.pop();
9896
},
9997
),

frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_model_switch_listener.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ import 'package:appflowy_result/appflowy_result.dart';
1212
typedef OnUpdateSelectedModel = void Function(AIModelPB model);
1313

1414
class AIModelSwitchListener {
15-
AIModelSwitchListener({required this.chatId}) {
16-
_parser = ChatNotificationParser(id: chatId, callback: _callback);
15+
AIModelSwitchListener({required this.objectId}) {
16+
_parser = ChatNotificationParser(id: objectId, callback: _callback);
1717
_subscription = RustStreamReceiver.listen(
1818
(observable) => _parser?.parse(observable),
1919
);
2020
}
2121

22-
final String chatId;
22+
final String objectId;
2323
StreamSubscription<SubscribeObject>? _subscription;
2424
ChatNotificationParser? _parser;
2525

Lines changed: 27 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,47 @@
1+
import 'package:appflowy/plugins/ai_chat/application/ai_model_switch_listener.dart';
12
import 'package:appflowy/user/application/user_listener.dart';
2-
import 'package:appflowy/user/application/user_service.dart';
33
import 'package:appflowy_backend/dispatch/dispatch.dart';
44
import 'package:appflowy_backend/log.dart';
55
import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
66
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
77
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
88
import 'package:appflowy_result/appflowy_result.dart';
99
import 'package:bloc/bloc.dart';
10-
import 'package:collection/collection.dart';
1110
import 'package:freezed_annotation/freezed_annotation.dart';
1211

1312
part 'settings_ai_bloc.freezed.dart';
14-
part 'settings_ai_bloc.g.dart';
1513

1614
class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
1715
SettingsAIBloc(
1816
this.userProfile,
1917
this.workspaceId,
20-
AFRolePB? currentWorkspaceMemberRole,
2118
) : _userListener = UserListener(userProfile: userProfile),
22-
_userService = UserBackendService(userId: userProfile.id),
19+
_aiModelSwitchListener =
20+
AIModelSwitchListener(objectId: "ai_models_global_active_model"),
2321
super(
2422
SettingsAIState(
25-
selectedAIModel: userProfile.aiModel,
2623
userProfile: userProfile,
27-
currentWorkspaceMemberRole: currentWorkspaceMemberRole,
2824
),
2925
) {
26+
_aiModelSwitchListener.start(
27+
onUpdateSelectedModel: (model) {
28+
if (!isClosed) {
29+
_loadModelList();
30+
}
31+
},
32+
);
3033
_dispatch();
31-
32-
if (currentWorkspaceMemberRole == null) {
33-
_userService.getWorkspaceMember().then((result) {
34-
result.fold(
35-
(member) {
36-
if (!isClosed) {
37-
add(SettingsAIEvent.refreshMember(member));
38-
}
39-
},
40-
(err) {
41-
Log.error(err);
42-
},
43-
);
44-
});
45-
}
4634
}
4735

4836
final UserListener _userListener;
4937
final UserProfilePB userProfile;
50-
final UserBackendService _userService;
5138
final String workspaceId;
39+
final AIModelSwitchListener _aiModelSwitchListener;
5240

5341
@override
5442
Future<void> close() async {
5543
await _userListener.stop();
44+
await _aiModelSwitchListener.stop();
5645
return super.close();
5746
}
5847

@@ -68,7 +57,6 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
6857
}
6958
},
7059
);
71-
_loadUserWorkspaceSetting();
7260
_loadModelList();
7361
},
7462
didReceiveUserProfile: (userProfile) {
@@ -83,41 +71,25 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
8371
!(state.aiSettings?.disableSearchIndexing ?? false),
8472
);
8573
},
86-
selectModel: (String model) async {
87-
await _updateUserWorkspaceSetting(model: model);
74+
selectModel: (AIModelPB model) async {
75+
if (!model.isLocal) {
76+
await _updateUserWorkspaceSetting(model: model.name);
77+
}
8878
},
8979
didLoadAISetting: (UseAISettingPB settings) {
9080
emit(
9181
state.copyWith(
9282
aiSettings: settings,
93-
selectedAIModel: settings.aiModel,
9483
enableSearchIndexing: !settings.disableSearchIndexing,
9584
),
9685
);
9786
},
98-
didLoadAvailableModels: (List<AvailableModelPB> models) {
99-
if (state.selectedAIModel.isEmpty) {
100-
final defaultModel =
101-
models.firstWhereOrNull((model) => model.isDefault);
102-
if (defaultModel != null) {
103-
_updateUserWorkspaceSetting(model: defaultModel.name);
104-
emit(
105-
state.copyWith(
106-
availableModels: models,
107-
selectedAIModel: defaultModel.name,
108-
),
109-
);
110-
}
111-
} else {
112-
emit(
113-
state.copyWith(
114-
availableModels: models,
115-
),
116-
);
117-
}
118-
},
119-
refreshMember: (member) {
120-
emit(state.copyWith(currentWorkspaceMemberRole: member.role));
87+
didLoadAvailableModels: (AvailableModelsPB models) {
88+
emit(
89+
state.copyWith(
90+
availableModels: models,
91+
),
92+
);
12193
},
12294
);
12395
});
@@ -152,24 +124,11 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
152124
(err) => Log.error(err),
153125
);
154126

155-
void _loadUserWorkspaceSetting() {
156-
final payload = UserWorkspaceIdPB(workspaceId: workspaceId);
157-
UserEventGetWorkspaceSetting(payload).send().then((result) {
158-
result.fold((settings) {
159-
if (!isClosed) {
160-
add(SettingsAIEvent.didLoadAISetting(settings));
161-
}
162-
}, (err) {
163-
Log.error(err);
164-
});
165-
});
166-
}
167-
168127
void _loadModelList() {
169128
AIEventGetServerAvailableModels().send().then((result) {
170-
result.fold((config) {
129+
result.fold((models) {
171130
if (!isClosed) {
172-
add(SettingsAIEvent.didLoadAvailableModels(config.models));
131+
add(SettingsAIEvent.didLoadAvailableModels(models));
173132
}
174133
}, (err) {
175134
Log.error(err);
@@ -186,17 +145,15 @@ class SettingsAIEvent with _$SettingsAIEvent {
186145
) = _DidLoadWorkspaceSetting;
187146

188147
const factory SettingsAIEvent.toggleAISearch() = _toggleAISearch;
189-
const factory SettingsAIEvent.refreshMember(WorkspaceMemberPB member) =
190-
_RefreshMember;
191148

192-
const factory SettingsAIEvent.selectModel(String model) = _SelectAIModel;
149+
const factory SettingsAIEvent.selectModel(AIModelPB model) = _SelectAIModel;
193150

194151
const factory SettingsAIEvent.didReceiveUserProfile(
195152
UserProfilePB newUserProfile,
196153
) = _DidReceiveUserProfile;
197154

198155
const factory SettingsAIEvent.didLoadAvailableModels(
199-
List<AvailableModelPB> models,
156+
AvailableModelsPB models,
200157
) = _DidLoadAvailableModels;
201158
}
202159

@@ -205,23 +162,7 @@ class SettingsAIState with _$SettingsAIState {
205162
const factory SettingsAIState({
206163
required UserProfilePB userProfile,
207164
UseAISettingPB? aiSettings,
208-
@Default("Default") String selectedAIModel,
209-
AFRolePB? currentWorkspaceMemberRole,
210-
@Default([]) List<AvailableModelPB> availableModels,
165+
AvailableModelsPB? availableModels,
211166
@Default(true) bool enableSearchIndexing,
212167
}) = _SettingsAIState;
213168
}
214-
215-
@JsonSerializable()
216-
class ModelList {
217-
ModelList({
218-
required this.models,
219-
});
220-
221-
factory ModelList.fromJson(Map<String, dynamic> json) =>
222-
_$ModelListFromJson(json);
223-
224-
final List<String> models;
225-
226-
Map<String, dynamic> toJson() => _$ModelListToJson(this);
227-
}

frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/model_selection.dart

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
12
import 'package:flutter/material.dart';
23

34
import 'package:appflowy/generated/locale_keys.g.dart';
@@ -15,6 +16,10 @@ class AIModelSelection extends StatelessWidget {
1516
Widget build(BuildContext context) {
1617
return BlocBuilder<SettingsAIBloc, SettingsAIState>(
1718
builder: (context, state) {
19+
if (state.availableModels == null) {
20+
return const SizedBox.shrink();
21+
}
22+
1823
return Padding(
1924
padding: const EdgeInsets.symmetric(vertical: 6),
2025
child: Row(
@@ -28,17 +33,17 @@ class AIModelSelection extends StatelessWidget {
2833
),
2934
const Spacer(),
3035
Flexible(
31-
child: SettingsDropdown<String>(
36+
child: SettingsDropdown<AIModelPB>(
3237
key: const Key('_AIModelSelection'),
3338
onChanged: (model) => context
3439
.read<SettingsAIBloc>()
3540
.add(SettingsAIEvent.selectModel(model)),
36-
selectedOption: state.selectedAIModel,
37-
options: state.availableModels
41+
selectedOption: state.availableModels!.selectedModel,
42+
options: state.availableModels!.models
3843
.map(
39-
(model) => buildDropdownMenuEntry<String>(
44+
(model) => buildDropdownMenuEntry<AIModelPB>(
4045
context,
41-
value: model.name,
46+
value: model,
4247
label: model.name,
4348
),
4449
)

frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@ class SettingsAIView extends StatelessWidget {
4242
@override
4343
Widget build(BuildContext context) {
4444
return BlocProvider<SettingsAIBloc>(
45-
create: (_) =>
46-
SettingsAIBloc(userProfile, workspaceId, currentWorkspaceMemberRole)
47-
..add(const SettingsAIEvent.started()),
45+
create: (_) => SettingsAIBloc(userProfile, workspaceId)
46+
..add(const SettingsAIEvent.started()),
4847
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
4948
builder: (context, state) {
5049
final children = <Widget>[

frontend/rust-lib/Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/rust-lib/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,5 +152,5 @@ collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFl
152152
# To update the commit ID, run:
153153
# scripts/tool/update_local_ai_rev.sh new_rev_id
154154
# ⚠️⚠️⚠️️
155-
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "cd991507870d225e76df41164deee356cd9921a7" }
156-
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "cd991507870d225e76df41164deee356cd9921a7" }
155+
appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "52ad76f21f8f3a7b510dae029836b5fe86479e5a" }
156+
appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "52ad76f21f8f3a7b510dae029836b5fe86479e5a" }

frontend/rust-lib/build-tool/flowy-codegen/src/ts_event/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ pub fn parse_event_crate(event_crate: &TsEventCrate) -> Vec<EventASTContext> {
153153
attrs
154154
.iter()
155155
.filter(|attr| !attr.attrs.event_attrs.ignore)
156-
.into_iter()
157156
.map(|variant| EventASTContext::from(&variant.attrs))
158157
.collect::<Vec<_>>()
159158
},

0 commit comments

Comments
 (0)