Compare commits
4
Commits
select_source
...
0.6.7.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
495601b683 | ||
|
|
f23787fec7 | ||
|
|
a66fba2753 | ||
|
|
2c5407da02 |
No files matched your search
@@ -1,4 +1,8 @@
|
||||
# Release Notes
|
||||
## Version 0.6.7.1 - 14/08/2024
|
||||
### Bug Fixes
|
||||
- Fixed the issue where workspace id was not compatible with settings page.
|
||||
|
||||
## Version 0.6.7 - 13/08/2024
|
||||
### New Features
|
||||
- Redesigned the icon picker design on Desktop.
|
||||
|
||||
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
APPFLOWY_VERSION = "0.6.7"
|
||||
APPFLOWY_VERSION = "0.6.7.1"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
MACOSX_DEPLOYMENT_TARGET = "11.0"
|
||||
|
||||
@@ -47,31 +47,28 @@ void main() {
|
||||
await tester.openSettingsPage(SettingsPage.account);
|
||||
|
||||
await tester.enterUserName(name);
|
||||
await tester.tapEscButton();
|
||||
|
||||
// wait 2 seconds for the sync to finish
|
||||
await tester.pumpAndSettle(const Duration(seconds: 6));
|
||||
});
|
||||
await tester.logout();
|
||||
|
||||
|
||||
testWidgets('get user icon and name from server', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
email: email,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.account);
|
||||
|
||||
// Verify name
|
||||
final profileSetting =
|
||||
tester.widget(find.byType(UserProfileSetting)) as UserProfileSetting;
|
||||
|
||||
expect(profileSetting.name, name);
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
});
|
||||
});
|
||||
testWidgets('get user icon and name from server', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
email: email,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.account);
|
||||
|
||||
// Verify name
|
||||
final profileSetting =
|
||||
tester.widget(find.byType(UserProfileSetting)) as UserProfileSetting;
|
||||
|
||||
expect(profileSetting.name, name);
|
||||
});
|
||||
}
|
||||
@@ -461,22 +461,22 @@ void main() {
|
||||
tester.assertChecklistEditorVisible(visible: true);
|
||||
|
||||
// create a new task with enter
|
||||
await tester.createNewChecklistTask(name: "task 0", enter: true);
|
||||
await tester.createNewChecklistTask(name: "task 1", enter: true);
|
||||
|
||||
// assert that the task is displayed
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task 0",
|
||||
name: "task 1",
|
||||
isChecked: false,
|
||||
);
|
||||
|
||||
// update the task's name
|
||||
await tester.renameChecklistTask(index: 0, name: "task 1");
|
||||
await tester.renameChecklistTask(index: 0, name: "task 11");
|
||||
|
||||
// assert that the task's name is updated
|
||||
tester.assertChecklistTaskInEditor(
|
||||
index: 0,
|
||||
name: "task 1",
|
||||
name: "task 11",
|
||||
isChecked: false,
|
||||
);
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ extension AppFlowySettings on WidgetTester {
|
||||
of: find.byType(UserProfileSetting),
|
||||
matching: find.byFlowySvg(FlowySvgs.edit_s),
|
||||
);
|
||||
await tap(editUsernameFinder);
|
||||
await tap(editUsernameFinder, warnIfMissed: false);
|
||||
await pumpAndSettle();
|
||||
|
||||
final userNameFinder = find.descendant(
|
||||
|
||||
+10
-5
@@ -15,8 +15,11 @@ part 'local_ai_on_boarding_bloc.freezed.dart';
|
||||
|
||||
class LocalAIOnBoardingBloc
|
||||
extends Bloc<LocalAIOnBoardingEvent, LocalAIOnBoardingState> {
|
||||
LocalAIOnBoardingBloc(this.userProfile)
|
||||
: super(const LocalAIOnBoardingState()) {
|
||||
LocalAIOnBoardingBloc(
|
||||
this.userProfile,
|
||||
this.member,
|
||||
this.workspaceId,
|
||||
) : super(const LocalAIOnBoardingState()) {
|
||||
_userService = UserBackendService(userId: userProfile.id);
|
||||
_successListenable = getIt<SubscriptionSuccessListenable>();
|
||||
_successListenable.addListener(_onPaymentSuccessful);
|
||||
@@ -36,6 +39,8 @@ class LocalAIOnBoardingBloc
|
||||
}
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceMemberPB member;
|
||||
final String workspaceId;
|
||||
late final IUserBackendService _userService;
|
||||
late final SubscriptionSuccessListenable _successListenable;
|
||||
|
||||
@@ -48,7 +53,7 @@ class LocalAIOnBoardingBloc
|
||||
addSubscription: (plan) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
final result = await _userService.createSubscription(
|
||||
userProfile.workspaceId,
|
||||
workspaceId,
|
||||
plan,
|
||||
);
|
||||
|
||||
@@ -72,7 +77,7 @@ class LocalAIOnBoardingBloc
|
||||
);
|
||||
},
|
||||
(err) {
|
||||
Log.error("Failed to get subscription plans: $err");
|
||||
Log.warn("Failed to get subscription plans: $err");
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -86,7 +91,7 @@ class LocalAIOnBoardingBloc
|
||||
}
|
||||
|
||||
void _loadSubscriptionPlans() {
|
||||
final payload = UserWorkspaceIdPB()..workspaceId = userProfile.workspaceId;
|
||||
final payload = UserWorkspaceIdPB()..workspaceId = workspaceId;
|
||||
UserEventGetWorkspaceSubscriptionInfo(payload).send().then((result) {
|
||||
if (!isClosed) {
|
||||
add(LocalAIOnBoardingEvent.didGetSubscriptionPlans(result));
|
||||
|
||||
+10
-5
@@ -11,8 +11,11 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'settings_ai_bloc.freezed.dart';
|
||||
|
||||
class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
SettingsAIBloc(this.userProfile, WorkspaceMemberPB? member)
|
||||
: _userListener = UserListener(userProfile: userProfile),
|
||||
SettingsAIBloc(
|
||||
this.userProfile,
|
||||
this.workspaceId,
|
||||
WorkspaceMemberPB? member,
|
||||
) : _userListener = UserListener(userProfile: userProfile),
|
||||
_userService = UserBackendService(userId: userProfile.id),
|
||||
super(SettingsAIState(userProfile: userProfile, member: member)) {
|
||||
_dispatch();
|
||||
@@ -36,6 +39,7 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
final UserListener _userListener;
|
||||
final UserProfilePB userProfile;
|
||||
final UserBackendService _userService;
|
||||
final String workspaceId;
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
@@ -92,7 +96,7 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
AIModelPB? model,
|
||||
}) {
|
||||
final payload = UpdateUserWorkspaceSettingPB(
|
||||
workspaceId: userProfile.workspaceId,
|
||||
workspaceId: workspaceId,
|
||||
);
|
||||
if (disableSearchIndexing != null) {
|
||||
payload.disableSearchIndexing = disableSearchIndexing;
|
||||
@@ -112,7 +116,7 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
);
|
||||
|
||||
void _loadUserWorkspaceSetting() {
|
||||
final payload = UserWorkspaceIdPB(workspaceId: userProfile.workspaceId);
|
||||
final payload = UserWorkspaceIdPB(workspaceId: workspaceId);
|
||||
UserEventGetWorkspaceSetting(payload).send().then((result) {
|
||||
result.fold((settings) {
|
||||
if (!isClosed) {
|
||||
@@ -133,7 +137,8 @@ class SettingsAIEvent with _$SettingsAIEvent {
|
||||
) = _DidLoadWorkspaceSetting;
|
||||
|
||||
const factory SettingsAIEvent.toggleAISearch() = _toggleAISearch;
|
||||
const factory SettingsAIEvent.refreshMember(WorkspaceMemberPB member) = _RefreshMember;
|
||||
const factory SettingsAIEvent.refreshMember(WorkspaceMemberPB member) =
|
||||
_RefreshMember;
|
||||
|
||||
const factory SettingsAIEvent.selectModel(AIModelPB model) = _SelectAIModel;
|
||||
|
||||
|
||||
+9
-3
@@ -40,15 +40,17 @@ class SettingsAIView extends StatelessWidget {
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.member,
|
||||
required this.workspaceId,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceMemberPB? member;
|
||||
final String workspaceId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<SettingsAIBloc>(
|
||||
create: (_) => SettingsAIBloc(userProfile, member)
|
||||
create: (_) => SettingsAIBloc(userProfile, workspaceId, member)
|
||||
..add(const SettingsAIEvent.started()),
|
||||
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
@@ -63,6 +65,7 @@ class SettingsAIView extends StatelessWidget {
|
||||
_LocalAIOnBoarding(
|
||||
userProfile: userProfile,
|
||||
member: state.member!,
|
||||
workspaceId: workspaceId,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -127,9 +130,11 @@ class _LocalAIOnBoarding extends StatelessWidget {
|
||||
const _LocalAIOnBoarding({
|
||||
required this.userProfile,
|
||||
required this.member,
|
||||
required this.workspaceId,
|
||||
});
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceMemberPB member;
|
||||
final String workspaceId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -137,8 +142,9 @@ class _LocalAIOnBoarding extends StatelessWidget {
|
||||
return BillingGateGuard(
|
||||
builder: (context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LocalAIOnBoardingBloc(userProfile)
|
||||
..add(const LocalAIOnBoardingEvent.started()),
|
||||
create: (context) =>
|
||||
LocalAIOnBoardingBloc(userProfile, member, workspaceId)
|
||||
..add(const LocalAIOnBoardingEvent.started()),
|
||||
child: BlocBuilder<LocalAIOnBoardingBloc, LocalAIOnBoardingState>(
|
||||
builder: (context, state) {
|
||||
// Show the local AI settings if the user has purchased the AI Local plan
|
||||
|
||||
@@ -120,7 +120,11 @@ class SettingsDialog extends StatelessWidget {
|
||||
return const SettingsShortcutsView();
|
||||
case SettingsPage.ai:
|
||||
if (user.authenticator == AuthenticatorPB.AppFlowyCloud) {
|
||||
return SettingsAIView(userProfile: user, member: member);
|
||||
return SettingsAIView(
|
||||
userProfile: user,
|
||||
member: member,
|
||||
workspaceId: workspaceId,
|
||||
);
|
||||
} else {
|
||||
return const AIFeatureOnlySupportedWhenUsingAppFlowyCloud();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 0.6.7
|
||||
version: 0.6.7+1
|
||||
|
||||
environment:
|
||||
flutter: ">=3.22.0"
|
||||
|
||||
@@ -38,19 +38,18 @@ async fn af_cloud_add_workspace_member_test() {
|
||||
user_localhost_af_cloud().await;
|
||||
let test_1 = EventIntegrationTest::new().await;
|
||||
let user_1 = test_1.af_cloud_sign_up().await;
|
||||
let workspace_id_1 = test_1.get_current_workspace().await.id;
|
||||
|
||||
let test_2 = EventIntegrationTest::new().await;
|
||||
let user_2 = test_2.af_cloud_sign_up().await;
|
||||
|
||||
let members = test_1.get_workspace_members(&user_1.workspace_id).await;
|
||||
let members = test_1.get_workspace_members(&workspace_id_1).await;
|
||||
assert_eq!(members.len(), 1);
|
||||
assert_eq!(members[0].email, user_1.email);
|
||||
|
||||
test_1
|
||||
.add_workspace_member(&user_1.workspace_id, &test_2)
|
||||
.await;
|
||||
test_1.add_workspace_member(&workspace_id_1, &test_2).await;
|
||||
|
||||
let members = test_1.get_workspace_members(&user_1.workspace_id).await;
|
||||
let members = test_1.get_workspace_members(&workspace_id_1).await;
|
||||
assert_eq!(members.len(), 2);
|
||||
assert_eq!(members[0].email, user_1.email);
|
||||
assert_eq!(members[1].email, user_2.email);
|
||||
@@ -61,19 +60,18 @@ async fn af_cloud_delete_workspace_member_test() {
|
||||
user_localhost_af_cloud().await;
|
||||
let test_1 = EventIntegrationTest::new().await;
|
||||
let user_1 = test_1.af_cloud_sign_up().await;
|
||||
let workspace_id_1 = test_1.get_current_workspace().await.id;
|
||||
|
||||
let test_2 = EventIntegrationTest::new().await;
|
||||
let user_2 = test_2.af_cloud_sign_up().await;
|
||||
|
||||
test_1
|
||||
.add_workspace_member(&user_1.workspace_id, &test_2)
|
||||
.await;
|
||||
test_1.add_workspace_member(&workspace_id_1, &test_2).await;
|
||||
|
||||
test_1
|
||||
.delete_workspace_member(&user_1.workspace_id, &user_2.email)
|
||||
.delete_workspace_member(&workspace_id_1, &user_2.email)
|
||||
.await;
|
||||
|
||||
let members = test_1.get_workspace_members(&user_1.workspace_id).await;
|
||||
let members = test_1.get_workspace_members(&workspace_id_1).await;
|
||||
assert_eq!(members.len(), 1);
|
||||
assert_eq!(members[0].email, user_1.email);
|
||||
}
|
||||
@@ -82,21 +80,20 @@ async fn af_cloud_delete_workspace_member_test() {
|
||||
async fn af_cloud_leave_workspace_test() {
|
||||
user_localhost_af_cloud().await;
|
||||
let test_1 = EventIntegrationTest::new().await;
|
||||
let user_1 = test_1.af_cloud_sign_up().await;
|
||||
test_1.af_cloud_sign_up().await;
|
||||
let workspace_id_1 = test_1.get_current_workspace().await.id;
|
||||
|
||||
let test_2 = EventIntegrationTest::new().await;
|
||||
let user_2 = test_2.af_cloud_sign_up().await;
|
||||
|
||||
test_1
|
||||
.add_workspace_member(&user_1.workspace_id, &test_2)
|
||||
.await;
|
||||
test_1.add_workspace_member(&workspace_id_1, &test_2).await;
|
||||
|
||||
// test_2 should have 2 workspace
|
||||
let workspaces = get_synced_workspaces(&test_2, user_2.id).await;
|
||||
assert_eq!(workspaces.len(), 2);
|
||||
|
||||
// user_2 leaves the workspace
|
||||
test_2.leave_workspace(&user_1.workspace_id).await;
|
||||
test_2.leave_workspace(&workspace_id_1).await;
|
||||
|
||||
// user_2 should have 1 workspace
|
||||
let workspaces = get_synced_workspaces(&test_2, user_2.id).await;
|
||||
|
||||
@@ -162,9 +162,6 @@ async fn af_cloud_different_open_same_workspace_test() {
|
||||
let owner_profile = client_1.af_cloud_sign_up().await;
|
||||
let shared_workspace_id = client_1.get_current_workspace().await.id.clone();
|
||||
|
||||
// Verify that the workspace ID from the profile matches the current session's workspace ID.
|
||||
assert_eq!(shared_workspace_id, owner_profile.workspace_id);
|
||||
|
||||
// Define the number of additional clients
|
||||
let num_clients = 5;
|
||||
let mut clients = Vec::new();
|
||||
@@ -182,7 +179,7 @@ async fn af_cloud_different_open_same_workspace_test() {
|
||||
}
|
||||
|
||||
client_1
|
||||
.add_workspace_member(&owner_profile.workspace_id, &client)
|
||||
.add_workspace_member(&shared_workspace_id, &client)
|
||||
.await;
|
||||
clients.push((client, client_profile));
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ async fn anon_user_profile_get() {
|
||||
assert_eq!(user_profile.id, user.id);
|
||||
assert_eq!(user_profile.openai_key, user.openai_key);
|
||||
assert_eq!(user_profile.stability_ai_key, user.stability_ai_key);
|
||||
assert_eq!(user_profile.workspace_id, user.workspace_id);
|
||||
assert_eq!(user_profile.authenticator, AuthenticatorPB::Local);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,11 @@ impl AIManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn initialize(&self, _workspace_id: &str) -> Result<(), FlowyError> {
|
||||
self.local_ai_controller.refresh().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn open_chat(&self, chat_id: &str) -> Result<(), FlowyError> {
|
||||
trace!("open chat: {}", chat_id);
|
||||
self.chats.entry(chat_id.to_string()).or_insert_with(|| {
|
||||
|
||||
@@ -140,11 +140,7 @@ impl Chat {
|
||||
let _ = question_sink.send(StreamMessage::Done.to_string()).await;
|
||||
|
||||
// Save message to disk
|
||||
save_chat_message(
|
||||
self.user_service.sqlite_connection(uid)?,
|
||||
&self.chat_id,
|
||||
vec![question.clone()],
|
||||
)?;
|
||||
save_and_notify_message(uid, &self.chat_id, &self.user_service, question.clone())?;
|
||||
|
||||
let stop_stream = self.stop_stream.clone();
|
||||
let chat_id = self.chat_id.clone();
|
||||
@@ -222,7 +218,7 @@ impl Chat {
|
||||
let answer = cloud_service
|
||||
.create_answer(&workspace_id, &chat_id, &content, question_id, metadata)
|
||||
.await?;
|
||||
Self::save_answer(uid, &chat_id, &user_service, answer)?;
|
||||
save_and_notify_message(uid, &chat_id, &user_service, answer)?;
|
||||
Ok::<(), FlowyError>(())
|
||||
});
|
||||
|
||||
@@ -230,26 +226,6 @@ impl Chat {
|
||||
Ok(question_pb)
|
||||
}
|
||||
|
||||
fn save_answer(
|
||||
uid: i64,
|
||||
chat_id: &str,
|
||||
user_service: &Arc<dyn AIUserService>,
|
||||
answer: ChatMessage,
|
||||
) -> Result<(), FlowyError> {
|
||||
trace!("[Chat] save answer: answer={:?}", answer);
|
||||
save_chat_message(
|
||||
user_service.sqlite_connection(uid)?,
|
||||
chat_id,
|
||||
vec![answer.clone()],
|
||||
)?;
|
||||
let pb = ChatMessagePB::from(answer);
|
||||
make_notification(chat_id, ChatNotification::DidReceiveChatMessage)
|
||||
.payload(pb)
|
||||
.send();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load chat messages for a given `chat_id`.
|
||||
///
|
||||
/// 1. When opening a chat:
|
||||
@@ -375,7 +351,7 @@ impl Chat {
|
||||
{
|
||||
Ok(resp) => {
|
||||
// Save chat messages to local disk
|
||||
if let Err(err) = save_chat_message(
|
||||
if let Err(err) = save_chat_message_disk(
|
||||
user_service.sqlite_connection(uid)?,
|
||||
&chat_id,
|
||||
resp.messages.clone(),
|
||||
@@ -453,7 +429,7 @@ impl Chat {
|
||||
.get_answer(&workspace_id, &self.chat_id, question_message_id)
|
||||
.await?;
|
||||
|
||||
Self::save_answer(self.uid, &self.chat_id, &self.user_service, answer.clone())?;
|
||||
save_and_notify_message(self.uid, &self.chat_id, &self.user_service, answer.clone())?;
|
||||
let pb = ChatMessagePB::from(answer);
|
||||
Ok(pb)
|
||||
}
|
||||
@@ -527,7 +503,7 @@ impl Chat {
|
||||
}
|
||||
}
|
||||
|
||||
fn save_chat_message(
|
||||
fn save_chat_message_disk(
|
||||
conn: DBConnection,
|
||||
chat_id: &str,
|
||||
messages: Vec<ChatMessage>,
|
||||
@@ -581,3 +557,23 @@ impl StringBuffer {
|
||||
std::mem::take(&mut self.content)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn save_and_notify_message(
|
||||
uid: i64,
|
||||
chat_id: &str,
|
||||
user_service: &Arc<dyn AIUserService>,
|
||||
message: ChatMessage,
|
||||
) -> Result<(), FlowyError> {
|
||||
trace!("[Chat] save answer: answer={:?}", message);
|
||||
save_chat_message_disk(
|
||||
user_service.sqlite_connection(uid)?,
|
||||
chat_id,
|
||||
vec![message.clone()],
|
||||
)?;
|
||||
let pb = ChatMessagePB::from(message);
|
||||
make_notification(chat_id, ChatNotification::DidReceiveChatMessage)
|
||||
.payload(pb)
|
||||
.send();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -171,7 +171,7 @@ pub(crate) async fn refresh_local_ai_info_handler(
|
||||
let ai_manager = upgrade_ai_manager(ai_manager)?;
|
||||
let (tx, rx) = oneshot::channel::<Result<LLMModelInfo, FlowyError>>();
|
||||
tokio::spawn(async move {
|
||||
let model_info = ai_manager.local_ai_controller.refresh().await;
|
||||
let model_info = ai_manager.local_ai_controller.refresh_model_info().await;
|
||||
if model_info.is_err() {
|
||||
if let Some(llm_model) = ai_manager.local_ai_controller.get_current_model() {
|
||||
let model_info = LLMModelInfo {
|
||||
|
||||
@@ -49,6 +49,7 @@ pub struct LocalAIController {
|
||||
local_ai_resource: Arc<LocalAIResourceController>,
|
||||
current_chat_id: Mutex<Option<String>>,
|
||||
store_preferences: Arc<KVStorePreferences>,
|
||||
user_service: Arc<dyn AIUserService>,
|
||||
}
|
||||
|
||||
impl Deref for LocalAIController {
|
||||
@@ -74,7 +75,11 @@ impl LocalAIController {
|
||||
};
|
||||
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
|
||||
let llm_res = Arc::new(LocalAIResourceController::new(user_service, res_impl, tx));
|
||||
let llm_res = Arc::new(LocalAIResourceController::new(
|
||||
user_service.clone(),
|
||||
res_impl,
|
||||
tx,
|
||||
));
|
||||
let current_chat_id = Mutex::new(None);
|
||||
|
||||
let mut running_state_rx = local_ai.subscribe_running_state();
|
||||
@@ -101,6 +106,7 @@ impl LocalAIController {
|
||||
local_ai_resource: llm_res,
|
||||
current_chat_id,
|
||||
store_preferences,
|
||||
user_service,
|
||||
};
|
||||
|
||||
let rag_enabled = this.is_rag_enabled();
|
||||
@@ -142,7 +148,13 @@ impl LocalAIController {
|
||||
|
||||
this
|
||||
}
|
||||
pub async fn refresh(&self) -> FlowyResult<LLMModelInfo> {
|
||||
pub async fn refresh(&self) -> FlowyResult<()> {
|
||||
let is_enabled = self.is_enabled();
|
||||
self.enable_chat_plugin(is_enabled).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn refresh_model_info(&self) -> FlowyResult<LLMModelInfo> {
|
||||
self.local_ai_resource.refresh_llm_resource().await
|
||||
}
|
||||
|
||||
@@ -158,10 +170,16 @@ impl LocalAIController {
|
||||
|
||||
/// Indicate whether the local AI is enabled.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self
|
||||
.store_preferences
|
||||
.get_bool(APPFLOWY_LOCAL_AI_ENABLED)
|
||||
.unwrap_or(true)
|
||||
if let Ok(key) = self.local_ai_enabled_key() {
|
||||
self.store_preferences.get_bool(&key).unwrap_or(true)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn local_ai_enabled_key(&self) -> FlowyResult<String> {
|
||||
let workspace_id = self.user_service.workspace_id()?;
|
||||
Ok(format!("{}:{}", APPFLOWY_LOCAL_AI_ENABLED, workspace_id))
|
||||
}
|
||||
|
||||
/// Indicate whether the local AI chat is enabled. In the future, we can support multiple
|
||||
@@ -297,13 +315,9 @@ impl LocalAIController {
|
||||
}
|
||||
|
||||
pub async fn toggle_local_ai(&self) -> FlowyResult<bool> {
|
||||
let enabled = !self
|
||||
.store_preferences
|
||||
.get_bool(APPFLOWY_LOCAL_AI_ENABLED)
|
||||
.unwrap_or(true);
|
||||
self
|
||||
.store_preferences
|
||||
.set_bool(APPFLOWY_LOCAL_AI_ENABLED, enabled)?;
|
||||
let key = self.local_ai_enabled_key()?;
|
||||
let enabled = !self.store_preferences.get_bool(&key).unwrap_or(true);
|
||||
self.store_preferences.set_bool(&key, enabled)?;
|
||||
|
||||
// when enable local ai. we need to check if chat is enabled, if enabled, we need to init chat plugin
|
||||
// otherwise, we need to destroy the plugin
|
||||
@@ -312,9 +326,12 @@ impl LocalAIController {
|
||||
.store_preferences
|
||||
.get_bool(APPFLOWY_LOCAL_AI_CHAT_ENABLED)
|
||||
.unwrap_or(true);
|
||||
self.enable_chat_plugin(chat_enabled).await?;
|
||||
|
||||
if self.local_ai_resource.is_resource_ready() {
|
||||
self.enable_chat_plugin(chat_enabled).await?;
|
||||
}
|
||||
} else {
|
||||
self.enable_chat_plugin(false).await?;
|
||||
let _ = self.enable_chat_plugin(false).await;
|
||||
}
|
||||
Ok(enabled)
|
||||
}
|
||||
|
||||
@@ -53,12 +53,9 @@ pub struct UserProfilePB {
|
||||
pub encryption_type: EncryptionTypePB,
|
||||
|
||||
#[pb(index = 10)]
|
||||
pub workspace_id: String,
|
||||
|
||||
#[pb(index = 11)]
|
||||
pub stability_ai_key: String,
|
||||
|
||||
#[pb(index = 12)]
|
||||
#[pb(index = 11)]
|
||||
pub ai_model: AIModelPB,
|
||||
}
|
||||
|
||||
@@ -90,7 +87,6 @@ impl From<UserProfile> for UserProfilePB {
|
||||
authenticator: user_profile.authenticator.into(),
|
||||
encryption_sign,
|
||||
encryption_type: encryption_ty,
|
||||
workspace_id: user_profile.workspace_id,
|
||||
stability_ai_key: user_profile.stability_ai_key,
|
||||
ai_model: AIModelPB::from_str(&user_profile.ai_model).unwrap_or_default(),
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user