Skip to content

Commit 815bb11

Browse files
committed
chore: implement local ai config
1 parent 878323a commit 815bb11

17 files changed

Lines changed: 173 additions & 184 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 & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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';
@@ -10,48 +10,38 @@ import 'package:bloc/bloc.dart';
1010
import 'package:freezed_annotation/freezed_annotation.dart';
1111

1212
part 'settings_ai_bloc.freezed.dart';
13-
part 'settings_ai_bloc.g.dart';
1413

1514
class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
1615
SettingsAIBloc(
1716
this.userProfile,
1817
this.workspaceId,
19-
AFRolePB? currentWorkspaceMemberRole,
2018
) : _userListener = UserListener(userProfile: userProfile),
21-
_userService = UserBackendService(userId: userProfile.id),
19+
_aiModelSwitchListener =
20+
AIModelSwitchListener(objectId: "ai_models_global_active_model"),
2221
super(
2322
SettingsAIState(
24-
selectedAIModel: userProfile.aiModel,
2523
userProfile: userProfile,
26-
currentWorkspaceMemberRole: currentWorkspaceMemberRole,
2724
),
2825
) {
26+
_aiModelSwitchListener.start(
27+
onUpdateSelectedModel: (model) {
28+
if (!isClosed) {
29+
_loadModelList();
30+
}
31+
},
32+
);
2933
_dispatch();
30-
31-
if (currentWorkspaceMemberRole == null) {
32-
_userService.getWorkspaceMember().then((result) {
33-
result.fold(
34-
(member) {
35-
if (!isClosed) {
36-
add(SettingsAIEvent.refreshMember(member));
37-
}
38-
},
39-
(err) {
40-
Log.error(err);
41-
},
42-
);
43-
});
44-
}
4534
}
4635

4736
final UserListener _userListener;
4837
final UserProfilePB userProfile;
49-
final UserBackendService _userService;
5038
final String workspaceId;
39+
final AIModelSwitchListener _aiModelSwitchListener;
5140

5241
@override
5342
Future<void> close() async {
5443
await _userListener.stop();
44+
await _aiModelSwitchListener.stop();
5545
return super.close();
5646
}
5747

@@ -67,7 +57,6 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
6757
}
6858
},
6959
);
70-
_loadUserWorkspaceSetting();
7160
_loadModelList();
7261
},
7362
didReceiveUserProfile: (userProfile) {
@@ -82,38 +71,25 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
8271
!(state.aiSettings?.disableSearchIndexing ?? false),
8372
);
8473
},
85-
selectModel: (String model) async {
86-
await _updateUserWorkspaceSetting(model: model);
74+
selectModel: (AIModelPB model) async {
75+
if (!model.isLocal) {
76+
await _updateUserWorkspaceSetting(model: model.name);
77+
}
8778
},
8879
didLoadAISetting: (UseAISettingPB settings) {
8980
emit(
9081
state.copyWith(
9182
aiSettings: settings,
92-
selectedAIModel: settings.aiModel,
9383
enableSearchIndexing: !settings.disableSearchIndexing,
9484
),
9585
);
9686
},
97-
didLoadAvailableModels: (List<AvailableModelPB> models) {
98-
if (state.selectedAIModel.isEmpty) {
99-
final m = models.firstWhere((model) => model.isDefault);
100-
_updateUserWorkspaceSetting(model: m.name);
101-
emit(
102-
state.copyWith(
103-
availableModels: models,
104-
selectedAIModel: m.name,
105-
),
106-
);
107-
} else {
108-
emit(
109-
state.copyWith(
110-
availableModels: models,
111-
),
112-
);
113-
}
114-
},
115-
refreshMember: (member) {
116-
emit(state.copyWith(currentWorkspaceMemberRole: member.role));
87+
didLoadAvailableModels: (AvailableModelsPB models) {
88+
emit(
89+
state.copyWith(
90+
availableModels: models,
91+
),
92+
);
11793
},
11894
);
11995
});
@@ -148,24 +124,11 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
148124
(err) => Log.error(err),
149125
);
150126

151-
void _loadUserWorkspaceSetting() {
152-
final payload = UserWorkspaceIdPB(workspaceId: workspaceId);
153-
UserEventGetWorkspaceSetting(payload).send().then((result) {
154-
result.fold((settings) {
155-
if (!isClosed) {
156-
add(SettingsAIEvent.didLoadAISetting(settings));
157-
}
158-
}, (err) {
159-
Log.error(err);
160-
});
161-
});
162-
}
163-
164127
void _loadModelList() {
165128
AIEventGetServerAvailableModels().send().then((result) {
166-
result.fold((config) {
129+
result.fold((models) {
167130
if (!isClosed) {
168-
add(SettingsAIEvent.didLoadAvailableModels(config.models));
131+
add(SettingsAIEvent.didLoadAvailableModels(models));
169132
}
170133
}, (err) {
171134
Log.error(err);
@@ -182,17 +145,15 @@ class SettingsAIEvent with _$SettingsAIEvent {
182145
) = _DidLoadWorkspaceSetting;
183146

184147
const factory SettingsAIEvent.toggleAISearch() = _toggleAISearch;
185-
const factory SettingsAIEvent.refreshMember(WorkspaceMemberPB member) =
186-
_RefreshMember;
187148

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

190151
const factory SettingsAIEvent.didReceiveUserProfile(
191152
UserProfilePB newUserProfile,
192153
) = _DidReceiveUserProfile;
193154

194155
const factory SettingsAIEvent.didLoadAvailableModels(
195-
List<AvailableModelPB> models,
156+
AvailableModelsPB models,
196157
) = _DidLoadAvailableModels;
197158
}
198159

@@ -201,23 +162,7 @@ class SettingsAIState with _$SettingsAIState {
201162
const factory SettingsAIState({
202163
required UserProfilePB userProfile,
203164
UseAISettingPB? aiSettings,
204-
@Default("Default") String selectedAIModel,
205-
AFRolePB? currentWorkspaceMemberRole,
206-
@Default([]) List<AvailableModelPB> availableModels,
165+
AvailableModelsPB? availableModels,
207166
@Default(true) bool enableSearchIndexing,
208167
}) = _SettingsAIState;
209168
}
210-
211-
@JsonSerializable()
212-
class ModelList {
213-
ModelList({
214-
required this.models,
215-
});
216-
217-
factory ModelList.fromJson(Map<String, dynamic> json) =>
218-
_$ModelListFromJson(json);
219-
220-
final List<String> models;
221-
222-
Map<String, dynamic> toJson() => _$ModelListToJson(this);
223-
}

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)