Skip to content

Commit 46532a8

Browse files
authored
Merge pull request #7602 from AppFlowy-IO/remove_default_model
chore: remove default model name
2 parents 7463e4e + 4c39908 commit 46532a8

10 files changed

Lines changed: 174 additions & 100 deletions

File tree

frontend/appflowy_flutter/lib/ai/widgets/prompt_input/desktop_prompt_text_field.dart

Lines changed: 85 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ class _SelectModelButtonState extends State<SelectModelButton> {
739739
);
740740
},
741741
child: _CurrentModelButton(
742+
key: ValueKey(state.availableModels?.selectedModel.name),
742743
modelName: state.availableModels?.selectedModel.name ?? "",
743744
onTap: () => popoverController.show(),
744745
),
@@ -760,29 +761,94 @@ class _PopoverSelectModel extends StatelessWidget {
760761
Widget build(BuildContext context) {
761762
return BlocBuilder<SelectModelBloc, SelectModelState>(
762763
builder: (context, state) {
763-
return ListView.builder(
764-
shrinkWrap: true,
765-
itemCount: state.availableModels?.models.length ?? 0,
764+
if (state.availableModels == null ||
765+
state.availableModels!.models.isEmpty) {
766+
return const SizedBox.shrink();
767+
}
768+
769+
// Separate models into local and cloud models
770+
final localModels = state.availableModels!.models
771+
.where((model) => model.isLocal)
772+
.toList();
773+
774+
final cloudModels = state.availableModels!.models
775+
.where((model) => !model.isLocal)
776+
.toList();
777+
778+
return Padding(
766779
padding: const EdgeInsets.fromLTRB(8, 4, 8, 12),
767-
itemBuilder: (context, index) {
768-
return _ModelItem(
769-
model: state.availableModels!.models[index],
770-
onTap: () {
771-
context.read<SelectModelBloc>().add(
772-
SelectModelEvent.selectModel(
773-
state.availableModels!.models[index],
774-
),
775-
);
776-
onClose();
777-
},
778-
);
779-
},
780+
child: Column(
781+
mainAxisSize: MainAxisSize.min,
782+
crossAxisAlignment: CrossAxisAlignment.start,
783+
children: [
784+
// Local AI Models Section
785+
if (localModels.isNotEmpty) ...[
786+
_ModelSectionHeader(
787+
title: LocaleKeys.chat_changeFormat_localModel.tr(),
788+
),
789+
const SizedBox(height: 4),
790+
...localModels.map(
791+
(model) => _ModelItem(
792+
model: model,
793+
onTap: () {
794+
context.read<SelectModelBloc>().add(
795+
SelectModelEvent.selectModel(model),
796+
);
797+
onClose();
798+
},
799+
),
800+
),
801+
const SizedBox(height: 8),
802+
],
803+
804+
// Cloud AI Models Section
805+
if (cloudModels.isNotEmpty) ...[
806+
if (localModels.isNotEmpty)
807+
_ModelSectionHeader(
808+
title: LocaleKeys.chat_changeFormat_cloudModel.tr(),
809+
),
810+
const VSpace(4),
811+
...cloudModels.map(
812+
(model) => _ModelItem(
813+
model: model,
814+
onTap: () {
815+
context.read<SelectModelBloc>().add(
816+
SelectModelEvent.selectModel(model),
817+
);
818+
onClose();
819+
},
820+
),
821+
),
822+
],
823+
],
824+
),
780825
);
781826
},
782827
);
783828
}
784829
}
785830

831+
class _ModelSectionHeader extends StatelessWidget {
832+
const _ModelSectionHeader({
833+
required this.title,
834+
});
835+
836+
final String title;
837+
838+
@override
839+
Widget build(BuildContext context) {
840+
return Padding(
841+
padding: const EdgeInsets.only(top: 4, bottom: 2),
842+
child: FlowyText(
843+
title,
844+
fontSize: 12,
845+
color: Theme.of(context).hintColor,
846+
fontWeight: FontWeight.w500,
847+
),
848+
);
849+
}
850+
}
851+
786852
class _ModelItem extends StatelessWidget {
787853
const _ModelItem({
788854
required this.model,
@@ -794,10 +860,8 @@ class _ModelItem extends StatelessWidget {
794860

795861
@override
796862
Widget build(BuildContext context) {
797-
var modelName = model.name;
798-
if (model.isLocal) {
799-
modelName += " (${LocaleKeys.chat_changeFormat_localModel.tr()})";
800-
}
863+
final modelName = model.name;
864+
801865
return FlowyTextButton(
802866
modelName,
803867
fillColor: Colors.transparent,
@@ -810,6 +874,7 @@ class _CurrentModelButton extends StatelessWidget {
810874
const _CurrentModelButton({
811875
required this.modelName,
812876
required this.onTap,
877+
super.key,
813878
});
814879

815880
final String modelName;

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ class AiSettingsGroup extends StatelessWidget {
8787
children: availableModels
8888
.mapIndexed(
8989
(index, model) => FlowyOptionTile.checkbox(
90-
text: model,
90+
text: model.name,
9191
showTopBorder: index == 0,
92-
isSelected: state.selectedAIModel == model,
92+
isSelected: state.selectedAIModel == model.name,
9393
onTap: () {
9494
context
9595
.read<SettingsAIBloc>()
96-
.add(SettingsAIEvent.selectModel(model));
96+
.add(SettingsAIEvent.selectModel(model.name));
9797
context.pop();
9898
},
9999
),

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

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import 'dart:convert';
2-
31
import 'package:appflowy/user/application/user_listener.dart';
42
import 'package:appflowy/user/application/user_service.dart';
53
import 'package:appflowy_backend/dispatch/dispatch.dart';
64
import 'package:appflowy_backend/log.dart';
5+
import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
76
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
87
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
98
import 'package:appflowy_result/appflowy_result.dart';
@@ -95,32 +94,22 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
9594
),
9695
);
9796
},
98-
didLoadAvailableModels: (String models) {
99-
final dynamic decodedJson = jsonDecode(models);
100-
Log.info("Available models: $decodedJson");
101-
if (decodedJson is Map<String, dynamic>) {
102-
final models = ModelList.fromJson(decodedJson).models;
103-
if (models.isEmpty) {
104-
// If available models is empty, then we just show the
105-
// Default
106-
emit(state.copyWith(availableModels: ["Default"]));
107-
return;
108-
}
109-
110-
if (!models.contains(state.selectedAIModel)) {
111-
// Use first model as default model if current selected model
112-
// is not available
113-
final selectedModel = models[0];
114-
_updateUserWorkspaceSetting(model: selectedModel);
115-
emit(
116-
state.copyWith(
117-
availableModels: models,
118-
selectedAIModel: selectedModel,
119-
),
120-
);
121-
} else {
122-
emit(state.copyWith(availableModels: models));
123-
}
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+
);
124113
}
125114
},
126115
refreshMember: (member) {
@@ -203,7 +192,7 @@ class SettingsAIEvent with _$SettingsAIEvent {
203192
) = _DidReceiveUserProfile;
204193

205194
const factory SettingsAIEvent.didLoadAvailableModels(
206-
String models,
195+
List<AvailableModelPB> models,
207196
) = _DidLoadAvailableModels;
208197
}
209198

@@ -214,7 +203,7 @@ class SettingsAIState with _$SettingsAIState {
214203
UseAISettingPB? aiSettings,
215204
@Default("Default") String selectedAIModel,
216205
AFRolePB? currentWorkspaceMemberRole,
217-
@Default(["Default"]) List<String> availableModels,
206+
@Default([]) List<AvailableModelPB> availableModels,
218207
@Default(true) bool enableSearchIndexing,
219208
}) = _SettingsAIState;
220209
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ class AIModelSelection extends StatelessWidget {
3838
.map(
3939
(model) => buildDropdownMenuEntry<String>(
4040
context,
41-
value: model,
42-
label: model,
41+
value: model.name,
42+
label: model.name,
4343
),
4444
)
4545
.toList(),

frontend/appflowy_flutter/macos/Podfile.lock

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -144,34 +144,34 @@ EXTERNAL SOURCES:
144144
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
145145

146146
SPEC CHECKSUMS:
147-
app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468
148-
appflowy_backend: 464aeb3e5c6966a41641a2111e5ead72ce2695f7
149-
auto_updater_macos: 3a42f1a06be6981f1a18be37e6e7bf86aa732118
150-
bitsdojo_window_macos: 7959fb0ca65a3ccda30095c181ecb856fae48ea9
151-
connectivity_plus: e74b9f74717d2d99d45751750e266e55912baeb5
152-
desktop_drop: e0b672a7d84c0a6cbc378595e82cdb15f2970a43
153-
device_info_plus: a56e6e74dbbd2bb92f2da12c64ddd4f67a749041
154-
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
155-
flowy_infra_ui: 8760ff42a789de40bf5007a5f176b454722a341e
147+
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
148+
appflowy_backend: 865496343de667fc8c600e04b9fd05234e130cf9
149+
auto_updater_macos: 3e3462c418fe4e731917eacd8d28eef7af84086d
150+
bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00
151+
connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747
152+
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
153+
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
154+
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
155+
flowy_infra_ui: 03301a39ad118771adbf051a664265c61c507f38
156156
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
157157
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
158-
hotkey_manager: b443f35f4d772162937aa73fd8995e579f8ac4e2
159-
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
160-
local_notifier: ebf072651e35ae5e47280ad52e2707375cb2ae4e
161-
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
162-
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
158+
hotkey_manager: c32bf0bfe8f934b7bc17ab4ad5c4c142960b023c
159+
irondash_engine_context: da62996ee25616d2f01bbeb85dc115d813359478
160+
local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff
161+
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
162+
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
163163
ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda
164-
screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f
164+
screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161
165165
Sentry: 1fe34e9c2cbba1e347623610d26db121dcb569f1
166-
sentry_flutter: e24b397f9a61fa5bbefd8279c3b2242ca86faa90
167-
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
168-
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
166+
sentry_flutter: a39c2a2d67d5e5b9cb0b94a4985c76dd5b3fc737
167+
share_plus: 1fa619de8392a4398bfaf176d441853922614e89
168+
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
169169
Sparkle: 5f8960a7a119aa7d45dacc0d5837017170bc5675
170-
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
171-
super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189
172-
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
173-
webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c
174-
window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c
170+
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
171+
super_native_extensions: 85efee3a7495b46b04befcfc86ed12069264ebf3
172+
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
173+
webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4
174+
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
175175

176176
PODFILE CHECKSUM: 0532f3f001ca3110b8be345d6491fff690e95823
177177

frontend/resources/translations/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@
248248
"blankDescription": "Format response",
249249
"defaultDescription": "Auto mode",
250250
"localModel": "Local Model",
251+
"cloudModel": "Cloud Model",
251252
"switchModel": "Switch model",
252253
"textWithImageDescription": "@:chat.changeFormat.text with image",
253254
"numberWithImageDescription": "@:chat.changeFormat.number with image",

frontend/rust-lib/flowy-ai/src/ai_manager.rs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::util::ai_available_models_key;
2020
use collab_integrate::persistence::collab_metadata_sql::{
2121
batch_insert_collab_metadata, batch_select_collab_metadata, AFCollabMetadata,
2222
};
23+
use flowy_ai_pub::cloud::ai_dto::AvailableModel;
2324
use flowy_storage_pub::storage::StorageService;
2425
use lib_infra::async_trait::async_trait;
2526
use lib_infra::util::timestamp;
@@ -57,7 +58,7 @@ pub trait AIExternalService: Send + Sync + 'static {
5758

5859
#[derive(Debug, Default)]
5960
struct ServerModelsCache {
60-
models: Vec<String>,
61+
models: Vec<AvailableModel>,
6162
timestamp: Option<i64>,
6263
}
6364

@@ -274,10 +275,9 @@ impl AIManager {
274275
Ok(model)
275276
}
276277

277-
pub async fn get_server_available_models(&self) -> FlowyResult<Vec<String>> {
278+
pub async fn get_server_available_models(&self) -> FlowyResult<Vec<AvailableModel>> {
278279
let workspace_id = self.user_service.workspace_id()?;
279-
280-
let now = timestamp(); // This is safer than using SystemTime which could fail
280+
let now = timestamp();
281281

282282
// First, try reading from the cache with expiration check
283283
let should_fetch = {
@@ -298,16 +298,9 @@ impl AIManager {
298298
.await
299299
{
300300
Ok(list) => {
301-
let models = list
302-
.models
303-
.into_iter()
304-
.map(|m| m.name)
305-
.collect::<Vec<String>>();
306-
307-
// Update the cache with new timestamp - handle potential errors
301+
let models = list.models;
308302
if let Err(err) = self.update_models_cache(&models, now).await {
309303
error!("Failed to update models cache: {}", err);
310-
// Still return the fetched models even if caching failed
311304
}
312305

313306
Ok(models)
@@ -328,7 +321,11 @@ impl AIManager {
328321
}
329322
}
330323

331-
async fn update_models_cache(&self, models: &[String], timestamp: i64) -> FlowyResult<()> {
324+
async fn update_models_cache(
325+
&self,
326+
models: &[AvailableModel],
327+
timestamp: i64,
328+
) -> FlowyResult<()> {
332329
match self.server_models.try_write() {
333330
Ok(mut cache) => {
334331
cache.models = models.to_vec();
@@ -360,8 +357,8 @@ impl AIManager {
360357
.get_server_available_models()
361358
.await?
362359
.into_iter()
363-
.map(|name| AIModelPB {
364-
name,
360+
.map(|m| AIModelPB {
361+
name: m.name,
365362
is_local: false,
366363
})
367364
.collect();

0 commit comments

Comments
 (0)