mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: support switch model (#5575)
* feat: ai settings page * chore: intergate client api * chore: replace open ai calls * chore: disable gen image from ai * chore: clippy * chore: remove learn about ai * chore: fix wanrings * chore: fix restart button title * chore: remove await * chore: remove loading indicator --------- Co-authored-by: nathan <nathan@appflowy.io> Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
parent
40312f4260
commit
54c9d12171
@ -46,7 +46,6 @@ void main() {
|
||||
await tester.ime.insertText(inputContent);
|
||||
expect(find.text(inputContent, findRichText: true), findsOneWidget);
|
||||
|
||||
// TODO(nathan): remove the await
|
||||
// 6 seconds for data sync
|
||||
await tester.waitForSeconds(6);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
|
||||
@ -103,8 +103,8 @@ Future<AppFlowyEditor> setUpOpenAITesting(WidgetTester tester) async {
|
||||
}
|
||||
|
||||
Future<void> mockOpenAIRepository() async {
|
||||
await getIt.unregister<OpenAIRepository>();
|
||||
getIt.registerFactoryAsync<OpenAIRepository>(
|
||||
await getIt.unregister<AIRepository>();
|
||||
getIt.registerFactoryAsync<AIRepository>(
|
||||
() => Future.value(
|
||||
MockOpenAIRepository(),
|
||||
),
|
||||
|
@ -44,7 +44,7 @@ class MockOpenAIRepository extends HttpOpenAIRepository {
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(OpenAIError error) onError,
|
||||
required void Function(AIError error) onError,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
|
@ -90,7 +90,7 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
|
||||
UploadImageType.local,
|
||||
UploadImageType.url,
|
||||
UploadImageType.unsplash,
|
||||
UploadImageType.openAI,
|
||||
// UploadImageType.openAI,
|
||||
UploadImageType.stabilityAI,
|
||||
],
|
||||
onSelectedLocalImage: (path) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -22,7 +22,7 @@ class OpenAIImageWidget extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _OpenAIImageWidgetState extends State<OpenAIImageWidget> {
|
||||
Future<FlowyResult<List<String>, OpenAIError>>? future;
|
||||
Future<FlowyResult<List<String>, AIError>>? future;
|
||||
String query = '';
|
||||
|
||||
@override
|
||||
@ -93,7 +93,7 @@ class _OpenAIImageWidgetState extends State<OpenAIImageWidget> {
|
||||
}
|
||||
|
||||
void _search() async {
|
||||
final openAI = await getIt.getAsync<OpenAIRepository>();
|
||||
final openAI = await getIt.getAsync<AIRepository>();
|
||||
setState(() {
|
||||
future = openAI.generateImage(
|
||||
prompt: query,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/embed_image_url_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/open_ai_image_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/stability_ai_image_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_file_widget.dart';
|
||||
@ -19,7 +18,7 @@ enum UploadImageType {
|
||||
url,
|
||||
unsplash,
|
||||
stabilityAI,
|
||||
openAI,
|
||||
// openAI,
|
||||
color;
|
||||
|
||||
String get description {
|
||||
@ -30,8 +29,8 @@ enum UploadImageType {
|
||||
return LocaleKeys.document_imageBlock_embedLink_label.tr();
|
||||
case UploadImageType.unsplash:
|
||||
return LocaleKeys.document_imageBlock_unsplash_label.tr();
|
||||
case UploadImageType.openAI:
|
||||
return LocaleKeys.document_imageBlock_ai_label.tr();
|
||||
// case UploadImageType.openAI:
|
||||
// return LocaleKeys.document_imageBlock_ai_label.tr();
|
||||
case UploadImageType.stabilityAI:
|
||||
return LocaleKeys.document_imageBlock_stability_ai_label.tr();
|
||||
case UploadImageType.color:
|
||||
@ -186,23 +185,23 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
|
||||
),
|
||||
),
|
||||
);
|
||||
case UploadImageType.openAI:
|
||||
return supportOpenAI
|
||||
? Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
constraints: constraints,
|
||||
child: OpenAIImageWidget(
|
||||
onSelectNetworkImage: widget.onSelectedAIImage,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: FlowyText(
|
||||
LocaleKeys.document_imageBlock_pleaseInputYourOpenAIKey.tr(),
|
||||
),
|
||||
);
|
||||
// case UploadImageType.openAI:
|
||||
// return supportOpenAI
|
||||
// ? Expanded(
|
||||
// child: Container(
|
||||
// padding: const EdgeInsets.all(8.0),
|
||||
// constraints: constraints,
|
||||
// child: OpenAIImageWidget(
|
||||
// onSelectNetworkImage: widget.onSelectedAIImage,
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// : Padding(
|
||||
// padding: const EdgeInsets.all(8.0),
|
||||
// child: FlowyText(
|
||||
// LocaleKeys.document_imageBlock_pleaseInputYourOpenAIKey.tr(),
|
||||
// ),
|
||||
// );
|
||||
case UploadImageType.stabilityAI:
|
||||
return supportStabilityAI
|
||||
? Expanded(
|
||||
|
@ -0,0 +1,32 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/text_completion.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
|
||||
abstract class AIRepository {
|
||||
Future<void> getStreamedCompletions({
|
||||
required String prompt,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
bool useAction = false,
|
||||
});
|
||||
|
||||
Future<void> streamCompletion({
|
||||
required String text,
|
||||
required CompletionTypePB completionType,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(String text) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
});
|
||||
|
||||
Future<FlowyResult<List<String>, AIError>> generateImage({
|
||||
required String prompt,
|
||||
int n = 1,
|
||||
});
|
||||
}
|
@ -3,12 +3,12 @@ part 'error.freezed.dart';
|
||||
part 'error.g.dart';
|
||||
|
||||
@freezed
|
||||
class OpenAIError with _$OpenAIError {
|
||||
const factory OpenAIError({
|
||||
class AIError with _$AIError {
|
||||
const factory AIError({
|
||||
String? code,
|
||||
required String message,
|
||||
}) = _OpenAIError;
|
||||
}) = _AIError;
|
||||
|
||||
factory OpenAIError.fromJson(Map<String, Object?> json) =>
|
||||
_$OpenAIErrorFromJson(json);
|
||||
factory AIError.fromJson(Map<String, Object?> json) =>
|
||||
_$AIErrorFromJson(json);
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/text_edit.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pbenum.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
@ -25,58 +26,7 @@ enum OpenAIRequestType {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class OpenAIRepository {
|
||||
/// Get completions from GPT-3
|
||||
///
|
||||
/// [prompt] is the prompt text
|
||||
/// [suffix] is the suffix text
|
||||
/// [maxTokens] is the maximum number of tokens to generate
|
||||
/// [temperature] is the temperature of the model
|
||||
///
|
||||
Future<FlowyResult<TextCompletionResponse, OpenAIError>> getCompletions({
|
||||
required String prompt,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = .3,
|
||||
});
|
||||
|
||||
Future<void> getStreamedCompletions({
|
||||
required String prompt,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(OpenAIError error) onError,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
bool useAction = false,
|
||||
});
|
||||
|
||||
/// Get edits from GPT-3
|
||||
///
|
||||
/// [input] is the input text
|
||||
/// [instruction] is the instruction text
|
||||
/// [temperature] is the temperature of the model
|
||||
///
|
||||
Future<FlowyResult<TextEditResponse, OpenAIError>> getEdits({
|
||||
required String input,
|
||||
required String instruction,
|
||||
double temperature = 0.3,
|
||||
});
|
||||
|
||||
/// Generate image from GPT-3
|
||||
///
|
||||
/// [prompt] is the prompt text
|
||||
/// [n] is the number of images to generate
|
||||
///
|
||||
/// the result is a list of urls
|
||||
Future<FlowyResult<List<String>, OpenAIError>> generateImage({
|
||||
required String prompt,
|
||||
int n = 1,
|
||||
});
|
||||
}
|
||||
|
||||
class HttpOpenAIRepository implements OpenAIRepository {
|
||||
class HttpOpenAIRepository implements AIRepository {
|
||||
const HttpOpenAIRepository({
|
||||
required this.client,
|
||||
required this.apiKey,
|
||||
@ -90,50 +40,13 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
@override
|
||||
Future<FlowyResult<TextCompletionResponse, OpenAIError>> getCompletions({
|
||||
required String prompt,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
}) async {
|
||||
final parameters = {
|
||||
'model': 'gpt-3.5-turbo-instruct',
|
||||
'prompt': prompt,
|
||||
'suffix': suffix,
|
||||
'max_tokens': maxTokens,
|
||||
'temperature': temperature,
|
||||
'stream': false,
|
||||
};
|
||||
|
||||
final response = await client.post(
|
||||
OpenAIRequestType.textCompletion.uri,
|
||||
headers: headers,
|
||||
body: json.encode(parameters),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return FlowyResult.success(
|
||||
TextCompletionResponse.fromJson(
|
||||
json.decode(
|
||||
utf8.decode(response.bodyBytes),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return FlowyResult.failure(
|
||||
OpenAIError.fromJson(json.decode(response.body)['error']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> getStreamedCompletions({
|
||||
required String prompt,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(OpenAIError error) onError,
|
||||
required void Function(AIError error) onError,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
@ -201,50 +114,14 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
||||
} else {
|
||||
final body = await response.stream.bytesToString();
|
||||
onError(
|
||||
OpenAIError.fromJson(json.decode(body)['error']),
|
||||
AIError.fromJson(json.decode(body)['error']),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FlowyResult<TextEditResponse, OpenAIError>> getEdits({
|
||||
required String input,
|
||||
required String instruction,
|
||||
double temperature = 0.3,
|
||||
int n = 1,
|
||||
}) async {
|
||||
final parameters = {
|
||||
'model': 'gpt-4',
|
||||
'input': input,
|
||||
'instruction': instruction,
|
||||
'temperature': temperature,
|
||||
'n': n,
|
||||
};
|
||||
|
||||
final response = await client.post(
|
||||
OpenAIRequestType.textEdit.uri,
|
||||
headers: headers,
|
||||
body: json.encode(parameters),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return FlowyResult.success(
|
||||
TextEditResponse.fromJson(
|
||||
json.decode(
|
||||
utf8.decode(response.bodyBytes),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return FlowyResult.failure(
|
||||
OpenAIError.fromJson(json.decode(response.body)['error']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FlowyResult<List<String>, OpenAIError>> generateImage({
|
||||
Future<FlowyResult<List<String>, AIError>> generateImage({
|
||||
required String prompt,
|
||||
int n = 1,
|
||||
}) async {
|
||||
@ -273,11 +150,23 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
||||
return FlowyResult.success(urls);
|
||||
} else {
|
||||
return FlowyResult.failure(
|
||||
OpenAIError.fromJson(json.decode(response.body)['error']),
|
||||
AIError.fromJson(json.decode(response.body)['error']),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return FlowyResult.failure(OpenAIError(message: error.toString()));
|
||||
return FlowyResult.failure(AIError(message: error.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> streamCompletion({
|
||||
required String text,
|
||||
required CompletionTypePB completionType,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(String text) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,20 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/build_context_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/text_robot.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/util/learn_more_action.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/discard_dialog.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
|
||||
import 'package:appflowy/user/application/ai_service.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AutoCompletionBlockKeys {
|
||||
@ -187,15 +185,11 @@ class _AutoCompletionBlockComponentState
|
||||
}
|
||||
|
||||
Future<void> _onGenerate() async {
|
||||
final loading = Loading(context);
|
||||
loading.start();
|
||||
|
||||
await _updateEditingText();
|
||||
|
||||
final userProfile = await UserBackendService.getCurrentUserProfile()
|
||||
.then((value) => value.toNullable());
|
||||
if (userProfile == null) {
|
||||
await loading.stop();
|
||||
if (mounted) {
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
@ -208,34 +202,28 @@ class _AutoCompletionBlockComponentState
|
||||
|
||||
final textRobot = TextRobot(editorState: editorState);
|
||||
BarrierDialog? barrierDialog;
|
||||
final openAIRepository = HttpOpenAIRepository(
|
||||
client: http.Client(),
|
||||
apiKey: userProfile.openaiKey,
|
||||
);
|
||||
await openAIRepository.getStreamedCompletions(
|
||||
prompt: controller.text,
|
||||
final aiRepository = AppFlowyAIService();
|
||||
await aiRepository.streamCompletion(
|
||||
text: controller.text,
|
||||
completionType: CompletionTypePB.ContinueWriting,
|
||||
onStart: () async {
|
||||
await loading.stop();
|
||||
if (mounted) {
|
||||
barrierDialog = BarrierDialog(context);
|
||||
barrierDialog?.show();
|
||||
await _makeSurePreviousNodeIsEmptyParagraphNode();
|
||||
}
|
||||
},
|
||||
onProcess: (response) async {
|
||||
if (response.choices.isNotEmpty) {
|
||||
final text = response.choices.first.text;
|
||||
await textRobot.autoInsertText(
|
||||
text,
|
||||
delay: Duration.zero,
|
||||
);
|
||||
}
|
||||
onProcess: (text) async {
|
||||
await textRobot.autoInsertText(
|
||||
text,
|
||||
delay: Duration.zero,
|
||||
);
|
||||
},
|
||||
onEnd: () async {
|
||||
await barrierDialog?.dismiss();
|
||||
barrierDialog?.dismiss();
|
||||
},
|
||||
onError: (error) async {
|
||||
await loading.stop();
|
||||
barrierDialog?.dismiss();
|
||||
if (mounted) {
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
@ -272,8 +260,6 @@ class _AutoCompletionBlockComponentState
|
||||
return;
|
||||
}
|
||||
|
||||
final loading = Loading(context);
|
||||
loading.start();
|
||||
// clear previous response
|
||||
final selection = startSelection;
|
||||
if (selection != null) {
|
||||
@ -292,7 +278,6 @@ class _AutoCompletionBlockComponentState
|
||||
final userProfile = await UserBackendService.getCurrentUserProfile()
|
||||
.then((value) => value.toNullable());
|
||||
if (userProfile == null) {
|
||||
await loading.stop();
|
||||
if (mounted) {
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
@ -303,28 +288,21 @@ class _AutoCompletionBlockComponentState
|
||||
return;
|
||||
}
|
||||
final textRobot = TextRobot(editorState: editorState);
|
||||
final openAIRepository = HttpOpenAIRepository(
|
||||
client: http.Client(),
|
||||
apiKey: userProfile.openaiKey,
|
||||
);
|
||||
await openAIRepository.getStreamedCompletions(
|
||||
prompt: _rewritePrompt(previousOutput),
|
||||
final aiResposity = AppFlowyAIService();
|
||||
await aiResposity.streamCompletion(
|
||||
text: _rewritePrompt(previousOutput),
|
||||
completionType: CompletionTypePB.ContinueWriting,
|
||||
onStart: () async {
|
||||
await loading.stop();
|
||||
await _makeSurePreviousNodeIsEmptyParagraphNode();
|
||||
},
|
||||
onProcess: (response) async {
|
||||
if (response.choices.isNotEmpty) {
|
||||
final text = response.choices.first.text;
|
||||
await textRobot.autoInsertText(
|
||||
text,
|
||||
delay: Duration.zero,
|
||||
);
|
||||
}
|
||||
onProcess: (text) async {
|
||||
await textRobot.autoInsertText(
|
||||
text,
|
||||
delay: Duration.zero,
|
||||
);
|
||||
},
|
||||
onEnd: () async {},
|
||||
onError: (error) async {
|
||||
await loading.stop();
|
||||
if (mounted) {
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
@ -462,23 +440,9 @@ class AutoCompletionHeader extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
LocaleKeys.document_plugins_autoGeneratorTitleName.tr(),
|
||||
fontSize: 14,
|
||||
),
|
||||
const Spacer(),
|
||||
FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: FlowyText.regular(
|
||||
LocaleKeys.document_plugins_autoGeneratorLearnMore.tr(),
|
||||
),
|
||||
onTap: () async {
|
||||
await openLearnMorePage();
|
||||
},
|
||||
),
|
||||
],
|
||||
return FlowyText.medium(
|
||||
LocaleKeys.document_plugins_autoGeneratorTitleName.tr(),
|
||||
fontSize: 14,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class Loading {
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> stop() async {
|
||||
void stop() {
|
||||
if (loadingContext != null) {
|
||||
Navigator.of(loadingContext!).pop();
|
||||
loadingContext = null;
|
||||
@ -54,5 +54,5 @@ class BarrierDialog {
|
||||
),
|
||||
);
|
||||
|
||||
Future<void> dismiss() async => Navigator.of(loadingContext).pop();
|
||||
void dismiss() => Navigator.of(loadingContext).pop();
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/util/learn_more_action.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/discard_dialog.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_action.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/ai_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/decoration.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@ -229,7 +229,11 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildHeaderWidget(context),
|
||||
FlowyText.medium(
|
||||
action.name,
|
||||
fontSize: 14,
|
||||
),
|
||||
// _buildHeaderWidget(context),
|
||||
const Space(0, 10),
|
||||
_buildResultWidget(context),
|
||||
const Space(0, 10),
|
||||
@ -238,27 +242,6 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeaderWidget(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
'${LocaleKeys.document_plugins_openAI.tr()}: ${action.name}',
|
||||
fontSize: 14,
|
||||
),
|
||||
const Spacer(),
|
||||
FlowyButton(
|
||||
useIntrinsicWidth: true,
|
||||
text: FlowyText.regular(
|
||||
LocaleKeys.document_plugins_autoGeneratorLearnMore.tr(),
|
||||
),
|
||||
onTap: () async {
|
||||
await openLearnMorePage();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildResultWidget(BuildContext context) {
|
||||
final loadingWidget = Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
@ -423,45 +406,33 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
|
||||
result = "";
|
||||
});
|
||||
}
|
||||
final openAIRepository = await getIt.getAsync<OpenAIRepository>();
|
||||
|
||||
var lines = content.split('\n\n');
|
||||
if (action == SmartEditAction.summarize) {
|
||||
lines = [lines.join('\n')];
|
||||
}
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
final element = lines[i];
|
||||
await openAIRepository.getStreamedCompletions(
|
||||
useAction: true,
|
||||
prompt: action.prompt(element),
|
||||
onStart: () async {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
},
|
||||
onProcess: (response) async {
|
||||
setState(() {
|
||||
if (response.choices.first.text != '\n') {
|
||||
result += response.choices.first.text;
|
||||
}
|
||||
});
|
||||
},
|
||||
onEnd: () async {
|
||||
setState(() {
|
||||
if (i != lines.length - 1) {
|
||||
result += '\n';
|
||||
}
|
||||
});
|
||||
},
|
||||
onError: (error) async {
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
error.message,
|
||||
showCancel: true,
|
||||
);
|
||||
await _onExit();
|
||||
},
|
||||
);
|
||||
}
|
||||
final aiResitory = await getIt.getAsync<AIRepository>();
|
||||
await aiResitory.streamCompletion(
|
||||
text: content,
|
||||
completionType: completionTypeFromInt(action),
|
||||
onStart: () async {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
},
|
||||
onProcess: (text) async {
|
||||
setState(() {
|
||||
result += text;
|
||||
});
|
||||
},
|
||||
onEnd: () async {
|
||||
setState(() {
|
||||
result += '\n';
|
||||
});
|
||||
},
|
||||
onError: (error) async {
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
error.message,
|
||||
showCancel: true,
|
||||
);
|
||||
await _onExit();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.da
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -32,7 +33,7 @@ class SmartEditActionList extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _SmartEditActionListState extends State<SmartEditActionList> {
|
||||
bool isOpenAIEnabled = false;
|
||||
bool isAIEnabled = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -40,8 +41,9 @@ class _SmartEditActionListState extends State<SmartEditActionList> {
|
||||
|
||||
UserBackendService.getCurrentUserProfile().then((value) {
|
||||
setState(() {
|
||||
isOpenAIEnabled = value.fold(
|
||||
(s) => s.openaiKey.isNotEmpty,
|
||||
isAIEnabled = value.fold(
|
||||
(userProfile) =>
|
||||
userProfile.authenticator == AuthenticatorPB.AppFlowyCloud,
|
||||
(_) => false,
|
||||
);
|
||||
});
|
||||
@ -60,9 +62,9 @@ class _SmartEditActionListState extends State<SmartEditActionList> {
|
||||
keepEditorFocusNotifier.increase();
|
||||
return FlowyIconButton(
|
||||
hoverColor: Colors.transparent,
|
||||
tooltipText: isOpenAIEnabled
|
||||
tooltipText: isAIEnabled
|
||||
? LocaleKeys.document_plugins_smartEdit.tr()
|
||||
: LocaleKeys.document_plugins_smartEditDisabled.tr(),
|
||||
: LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(),
|
||||
preferBelow: false,
|
||||
icon: const Icon(
|
||||
Icons.lightbulb_outline,
|
||||
@ -70,12 +72,12 @@ class _SmartEditActionListState extends State<SmartEditActionList> {
|
||||
color: Colors.white,
|
||||
),
|
||||
onPressed: () {
|
||||
if (isOpenAIEnabled) {
|
||||
if (isAIEnabled) {
|
||||
controller.show();
|
||||
} else {
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
LocaleKeys.document_plugins_smartEditDisabled.tr(),
|
||||
LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(),
|
||||
showCancel: true,
|
||||
);
|
||||
}
|
||||
|
@ -3,13 +3,14 @@ import 'package:appflowy/core/network_monitor.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_client.dart';
|
||||
import 'package:appflowy/plugins/trash/application/prelude.dart';
|
||||
import 'package:appflowy/shared/appflowy_cache_manager.dart';
|
||||
import 'package:appflowy/shared/custom_image_cache_manager.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart';
|
||||
import 'package:appflowy/user/application/ai_service.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/supabase_auth_service.dart';
|
||||
@ -85,15 +86,12 @@ void _resolveCommonService(
|
||||
() => mode.isTest ? MockApplicationDataStorage() : ApplicationDataStorage(),
|
||||
);
|
||||
|
||||
getIt.registerFactoryAsync<OpenAIRepository>(
|
||||
getIt.registerFactoryAsync<AIRepository>(
|
||||
() async {
|
||||
final result = await UserBackendService.getCurrentUserProfile();
|
||||
return result.fold(
|
||||
(s) {
|
||||
return HttpOpenAIRepository(
|
||||
client: http.Client(),
|
||||
apiKey: s.openaiKey,
|
||||
);
|
||||
return AppFlowyAIService();
|
||||
},
|
||||
(e) {
|
||||
throw Exception('Failed to get user profile: ${e.msg}');
|
||||
|
122
frontend/appflowy_flutter/lib/user/application/ai_service.dart
Normal file
122
frontend/appflowy_flutter/lib/user/application/ai_service.dart
Normal file
@ -0,0 +1,122 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/text_completion.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_action.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:fixnum/fixnum.dart' as fixnum;
|
||||
|
||||
class AppFlowyAIService implements AIRepository {
|
||||
@override
|
||||
Future<FlowyResult<List<String>, AIError>> generateImage({
|
||||
required String prompt,
|
||||
int n = 1,
|
||||
}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> getStreamedCompletions({
|
||||
required String prompt,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
bool useAction = false,
|
||||
}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<CompletionStream> streamCompletion({
|
||||
required String text,
|
||||
required CompletionTypePB completionType,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(String text) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(AIError error) onError,
|
||||
}) async {
|
||||
final stream = CompletionStream(
|
||||
onStart,
|
||||
onProcess,
|
||||
onEnd,
|
||||
onError,
|
||||
);
|
||||
final payload = CompleteTextPB(
|
||||
text: text,
|
||||
completionType: completionType,
|
||||
streamPort: fixnum.Int64(stream.nativePort),
|
||||
);
|
||||
|
||||
// ignore: unawaited_futures
|
||||
ChatEventCompleteText(payload).send();
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
CompletionTypePB completionTypeFromInt(SmartEditAction action) {
|
||||
switch (action) {
|
||||
case SmartEditAction.summarize:
|
||||
return CompletionTypePB.MakeShorter;
|
||||
case SmartEditAction.fixSpelling:
|
||||
return CompletionTypePB.SpellingAndGrammar;
|
||||
case SmartEditAction.improveWriting:
|
||||
return CompletionTypePB.ImproveWriting;
|
||||
case SmartEditAction.makeItLonger:
|
||||
return CompletionTypePB.MakeLonger;
|
||||
}
|
||||
}
|
||||
|
||||
class CompletionStream {
|
||||
CompletionStream(
|
||||
Future<void> Function() onStart,
|
||||
Future<void> Function(String text) onProcess,
|
||||
Future<void> Function() onEnd,
|
||||
void Function(AIError error) onError,
|
||||
) {
|
||||
_port.handler = _controller.add;
|
||||
_subscription = _controller.stream.listen(
|
||||
(event) async {
|
||||
if (event.startsWith("start:")) {
|
||||
await onStart();
|
||||
}
|
||||
|
||||
if (event.startsWith("data:")) {
|
||||
await onProcess(event.substring(5));
|
||||
}
|
||||
|
||||
if (event.startsWith("finish:")) {
|
||||
await onEnd();
|
||||
}
|
||||
if (event.startsWith("error:")) {
|
||||
onError(AIError(message: event.substring(6)));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final RawReceivePort _port = RawReceivePort();
|
||||
final StreamController<String> _controller = StreamController.broadcast();
|
||||
late StreamSubscription<String> _subscription;
|
||||
int get nativePort => _port.sendPort.nativePort;
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _controller.close();
|
||||
await _subscription.cancel();
|
||||
_port.close();
|
||||
}
|
||||
|
||||
StreamSubscription<String> listen(
|
||||
void Function(String event)? onData,
|
||||
) {
|
||||
return _controller.stream.listen(onData);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:appflowy/core/notification/folder_notification.dart';
|
||||
@ -11,7 +12,6 @@ import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/notification.pb.dart'
|
||||
as user;
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_backend/rust_stream.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
@ -23,6 +23,9 @@ typedef DidUpdateUserWorkspacesCallback = void Function(
|
||||
RepeatedUserWorkspacePB workspaces,
|
||||
);
|
||||
typedef UserProfileNotifyValue = FlowyResult<UserProfilePB, FlowyError>;
|
||||
typedef DidUpdateUserWorkspaceSetting = void Function(
|
||||
UseAISettingPB settings,
|
||||
);
|
||||
|
||||
class UserListener {
|
||||
UserListener({
|
||||
@ -37,28 +40,26 @@ class UserListener {
|
||||
|
||||
/// Update notification about _all_ of the users workspaces
|
||||
///
|
||||
DidUpdateUserWorkspacesCallback? didUpdateUserWorkspaces;
|
||||
DidUpdateUserWorkspacesCallback? onUserWorkspaceListUpdated;
|
||||
|
||||
/// Update notification about _one_ workspace
|
||||
///
|
||||
DidUpdateUserWorkspaceCallback? didUpdateUserWorkspace;
|
||||
DidUpdateUserWorkspaceCallback? onUserWorkspaceUpdated;
|
||||
DidUpdateUserWorkspaceSetting? onUserWorkspaceSettingUpdated;
|
||||
|
||||
void start({
|
||||
void Function(UserProfileNotifyValue)? onProfileUpdated,
|
||||
void Function(RepeatedUserWorkspacePB)? didUpdateUserWorkspaces,
|
||||
void Function(UserWorkspacePB)? didUpdateUserWorkspace,
|
||||
DidUpdateUserWorkspacesCallback? onUserWorkspaceListUpdated,
|
||||
void Function(UserWorkspacePB)? onUserWorkspaceUpdated,
|
||||
DidUpdateUserWorkspaceSetting? onUserWorkspaceSettingUpdated,
|
||||
}) {
|
||||
if (onProfileUpdated != null) {
|
||||
_profileNotifier?.addPublishListener(onProfileUpdated);
|
||||
}
|
||||
|
||||
if (didUpdateUserWorkspaces != null) {
|
||||
this.didUpdateUserWorkspaces = didUpdateUserWorkspaces;
|
||||
}
|
||||
|
||||
if (didUpdateUserWorkspace != null) {
|
||||
this.didUpdateUserWorkspace = didUpdateUserWorkspace;
|
||||
}
|
||||
this.onUserWorkspaceListUpdated = onUserWorkspaceListUpdated;
|
||||
this.onUserWorkspaceUpdated = onUserWorkspaceUpdated;
|
||||
this.onUserWorkspaceSettingUpdated = onUserWorkspaceSettingUpdated;
|
||||
|
||||
_userParser = UserNotificationParser(
|
||||
id: _userProfile.id.toString(),
|
||||
@ -92,13 +93,18 @@ class UserListener {
|
||||
result.map(
|
||||
(r) {
|
||||
final value = RepeatedUserWorkspacePB.fromBuffer(r);
|
||||
didUpdateUserWorkspaces?.call(value);
|
||||
onUserWorkspaceListUpdated?.call(value);
|
||||
},
|
||||
);
|
||||
break;
|
||||
case user.UserNotification.DidUpdateUserWorkspace:
|
||||
result.map(
|
||||
(r) => didUpdateUserWorkspace?.call(UserWorkspacePB.fromBuffer(r)),
|
||||
(r) => onUserWorkspaceUpdated?.call(UserWorkspacePB.fromBuffer(r)),
|
||||
);
|
||||
case user.UserNotification.DidUpdateAISetting:
|
||||
result.map(
|
||||
(r) =>
|
||||
onUserWorkspaceSettingUpdated?.call(UseAISettingPB.fromBuffer(r)),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
@ -110,8 +116,8 @@ class UserListener {
|
||||
typedef WorkspaceSettingNotifyValue
|
||||
= FlowyResult<WorkspaceSettingPB, FlowyError>;
|
||||
|
||||
class UserWorkspaceListener {
|
||||
UserWorkspaceListener();
|
||||
class FolderListener {
|
||||
FolderListener();
|
||||
|
||||
final PublishNotifier<WorkspaceSettingNotifyValue> _settingChangedNotifier =
|
||||
PublishNotifier();
|
||||
|
@ -9,12 +9,12 @@ part 'home_bloc.freezed.dart';
|
||||
|
||||
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
HomeBloc(WorkspaceSettingPB workspaceSetting)
|
||||
: _workspaceListener = UserWorkspaceListener(),
|
||||
: _workspaceListener = FolderListener(),
|
||||
super(HomeState.initial(workspaceSetting)) {
|
||||
_dispatch(workspaceSetting);
|
||||
}
|
||||
|
||||
final UserWorkspaceListener _workspaceListener;
|
||||
final FolderListener _workspaceListener;
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
|
@ -15,7 +15,7 @@ class HomeSettingBloc extends Bloc<HomeSettingEvent, HomeSettingState> {
|
||||
WorkspaceSettingPB workspaceSetting,
|
||||
AppearanceSettingsCubit appearanceSettingsCubit,
|
||||
double screenWidthPx,
|
||||
) : _listener = UserWorkspaceListener(),
|
||||
) : _listener = FolderListener(),
|
||||
_appearanceSettingsCubit = appearanceSettingsCubit,
|
||||
super(
|
||||
HomeSettingState.initial(
|
||||
@ -27,7 +27,7 @@ class HomeSettingBloc extends Bloc<HomeSettingEvent, HomeSettingState> {
|
||||
_dispatch();
|
||||
}
|
||||
|
||||
final UserWorkspaceListener _listener;
|
||||
final FolderListener _listener;
|
||||
final AppearanceSettingsCubit _appearanceSettingsCubit;
|
||||
|
||||
@override
|
||||
|
@ -13,7 +13,7 @@ part 'menu_user_bloc.freezed.dart';
|
||||
class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
|
||||
MenuUserBloc(this.userProfile)
|
||||
: _userListener = UserListener(userProfile: userProfile),
|
||||
_userWorkspaceListener = UserWorkspaceListener(),
|
||||
_userWorkspaceListener = FolderListener(),
|
||||
_userService = UserBackendService(userId: userProfile.id),
|
||||
super(MenuUserState.initial(userProfile)) {
|
||||
_dispatch();
|
||||
@ -21,7 +21,7 @@ class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
|
||||
|
||||
final UserBackendService _userService;
|
||||
final UserListener _userListener;
|
||||
final UserWorkspaceListener _userWorkspaceListener;
|
||||
final FolderListener _userWorkspaceListener;
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
|
@ -0,0 +1,127 @@
|
||||
import 'package:appflowy/user/application/user_listener.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'settings_ai_bloc.freezed.dart';
|
||||
|
||||
class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
SettingsAIBloc(this.userProfile)
|
||||
: _userListener = UserListener(userProfile: userProfile),
|
||||
super(SettingsAIState(userProfile: userProfile)) {
|
||||
_dispatch();
|
||||
}
|
||||
|
||||
final UserListener _userListener;
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _userListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _dispatch() {
|
||||
on<SettingsAIEvent>((event, emit) {
|
||||
event.when(
|
||||
started: () {
|
||||
_userListener.start(
|
||||
onProfileUpdated: _onProfileUpdated,
|
||||
onUserWorkspaceSettingUpdated: (settings) {
|
||||
if (!isClosed) {
|
||||
add(SettingsAIEvent.didLoadAISetting(settings));
|
||||
}
|
||||
},
|
||||
);
|
||||
_loadUserWorkspaceSetting();
|
||||
},
|
||||
didReceiveUserProfile: (userProfile) {
|
||||
emit(state.copyWith(userProfile: userProfile));
|
||||
},
|
||||
toggleAISearch: () {
|
||||
_updateUserWorkspaceSetting(
|
||||
disableSearchIndexing:
|
||||
!(state.aiSettings?.disableSearchIndexing ?? false),
|
||||
);
|
||||
},
|
||||
selectModel: (AIModelPB model) {
|
||||
_updateUserWorkspaceSetting(model: model);
|
||||
},
|
||||
didLoadAISetting: (UseAISettingPB settings) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
aiSettings: settings,
|
||||
enableSearchIndexing: !settings.disableSearchIndexing,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _updateUserWorkspaceSetting({
|
||||
bool? disableSearchIndexing,
|
||||
AIModelPB? model,
|
||||
}) {
|
||||
final payload =
|
||||
UpdateUserWorkspaceSettingPB(workspaceId: userProfile.workspaceId);
|
||||
if (disableSearchIndexing != null) {
|
||||
payload.disableSearchIndexing = disableSearchIndexing;
|
||||
}
|
||||
if (model != null) {
|
||||
payload.aiModel = model;
|
||||
}
|
||||
UserEventUpdateWorkspaceSetting(payload).send();
|
||||
}
|
||||
|
||||
void _onProfileUpdated(
|
||||
FlowyResult<UserProfilePB, FlowyError> userProfileOrFailed,
|
||||
) =>
|
||||
userProfileOrFailed.fold(
|
||||
(newUserProfile) =>
|
||||
add(SettingsAIEvent.didReceiveUserProfile(newUserProfile)),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
|
||||
void _loadUserWorkspaceSetting() {
|
||||
final payload = UserWorkspaceIdPB(workspaceId: userProfile.workspaceId);
|
||||
UserEventGetWorkspaceSetting(payload).send().then((result) {
|
||||
result.fold((settins) {
|
||||
if (!isClosed) {
|
||||
add(SettingsAIEvent.didLoadAISetting(settins));
|
||||
}
|
||||
}, (err) {
|
||||
Log.error(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SettingsAIEvent with _$SettingsAIEvent {
|
||||
const factory SettingsAIEvent.started() = _Started;
|
||||
const factory SettingsAIEvent.didLoadAISetting(
|
||||
UseAISettingPB settings,
|
||||
) = _DidLoadWorkspaceSetting;
|
||||
|
||||
const factory SettingsAIEvent.toggleAISearch() = _toggleAISearch;
|
||||
|
||||
const factory SettingsAIEvent.selectModel(AIModelPB model) = _SelectAIModel;
|
||||
|
||||
const factory SettingsAIEvent.didReceiveUserProfile(
|
||||
UserProfilePB newUserProfile,
|
||||
) = _DidReceiveUserProfile;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SettingsAIState with _$SettingsAIState {
|
||||
const factory SettingsAIState({
|
||||
required UserProfilePB userProfile,
|
||||
UseAISettingPB? aiSettings,
|
||||
@Default(true) bool enableSearchIndexing,
|
||||
}) = _SettingsAIState;
|
||||
}
|
@ -13,12 +13,13 @@ enum SettingsPage {
|
||||
account,
|
||||
workspace,
|
||||
manageData,
|
||||
shortcuts,
|
||||
ai,
|
||||
plan,
|
||||
billing,
|
||||
// OLD
|
||||
notifications,
|
||||
cloud,
|
||||
shortcuts,
|
||||
member,
|
||||
featureFlags,
|
||||
}
|
||||
|
@ -63,24 +63,6 @@ class SettingsUserViewBloc extends Bloc<SettingsUserEvent, SettingsUserState> {
|
||||
);
|
||||
});
|
||||
},
|
||||
updateUserOpenAIKey: (openAIKey) {
|
||||
_userService.updateUserProfile(openAIKey: openAIKey).then((result) {
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
},
|
||||
updateUserStabilityAIKey: (stabilityAIKey) {
|
||||
_userService
|
||||
.updateUserProfile(stabilityAiKey: stabilityAIKey)
|
||||
.then((result) {
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
},
|
||||
updateUserEmail: (String email) {
|
||||
_userService.updateUserProfile(email: email).then((result) {
|
||||
result.fold(
|
||||
@ -127,11 +109,6 @@ class SettingsUserEvent with _$SettingsUserEvent {
|
||||
const factory SettingsUserEvent.updateUserIcon({required String iconUrl}) =
|
||||
_UpdateUserIcon;
|
||||
const factory SettingsUserEvent.removeUserIcon() = _RemoveUserIcon;
|
||||
const factory SettingsUserEvent.updateUserOpenAIKey(String openAIKey) =
|
||||
_UpdateUserOpenaiKey;
|
||||
const factory SettingsUserEvent.updateUserStabilityAIKey(
|
||||
String stabilityAIKey,
|
||||
) = _UpdateUserStabilityAIKey;
|
||||
const factory SettingsUserEvent.didReceiveUserProfile(
|
||||
UserProfilePB newUserProfile,
|
||||
) = _DidReceiveUserProfile;
|
||||
|
@ -29,9 +29,9 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_listener.start(
|
||||
didUpdateUserWorkspaces: (workspaces) =>
|
||||
onUserWorkspaceListUpdated: (workspaces) =>
|
||||
add(UserWorkspaceEvent.updateWorkspaces(workspaces)),
|
||||
didUpdateUserWorkspace: (workspace) {
|
||||
onUserWorkspaceUpdated: (workspace) {
|
||||
// If currentWorkspace is updated, eg. Icon or Name, we should notify
|
||||
// the UI to render the updated information.
|
||||
final currentWorkspace = state.currentWorkspace;
|
||||
|
@ -136,39 +136,7 @@ class _SettingsAccountViewState extends State<SettingsAccountView> {
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
SettingsCategory(
|
||||
title: LocaleKeys.settings_accountPage_keys_title.tr(),
|
||||
children: [
|
||||
SettingsInputField(
|
||||
label:
|
||||
LocaleKeys.settings_accountPage_keys_openAILabel.tr(),
|
||||
tooltip:
|
||||
LocaleKeys.settings_accountPage_keys_openAITooltip.tr(),
|
||||
placeholder:
|
||||
LocaleKeys.settings_accountPage_keys_openAIHint.tr(),
|
||||
value: state.userProfile.openaiKey,
|
||||
obscureText: true,
|
||||
onSave: (key) => context
|
||||
.read<SettingsUserViewBloc>()
|
||||
.add(SettingsUserEvent.updateUserOpenAIKey(key)),
|
||||
),
|
||||
SettingsInputField(
|
||||
label: LocaleKeys.settings_accountPage_keys_stabilityAILabel
|
||||
.tr(),
|
||||
tooltip: LocaleKeys
|
||||
.settings_accountPage_keys_stabilityAITooltip
|
||||
.tr(),
|
||||
placeholder: LocaleKeys
|
||||
.settings_accountPage_keys_stabilityAIHint
|
||||
.tr(),
|
||||
value: state.userProfile.stabilityAiKey,
|
||||
obscureText: true,
|
||||
onSave: (key) => context
|
||||
.read<SettingsUserViewBloc>()
|
||||
.add(SettingsUserEvent.updateUserStabilityAIKey(key)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SettingsCategory(
|
||||
title: LocaleKeys.settings_accountPage_login_title.tr(),
|
||||
children: [
|
||||
|
@ -0,0 +1,168 @@
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_dropdown.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class AIFeatureOnlySupportedWhenUsingAppFlowyCloud extends StatelessWidget {
|
||||
const AIFeatureOnlySupportedWhenUsingAppFlowyCloud({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 30),
|
||||
child: FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_loginToEnableAIFeature.tr(),
|
||||
maxLines: null,
|
||||
fontSize: 16,
|
||||
lineHeight: 1.6,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SettingsAIView extends StatelessWidget {
|
||||
const SettingsAIView({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<SettingsAIBloc>(
|
||||
create: (context) =>
|
||||
SettingsAIBloc(userProfile)..add(const SettingsAIEvent.started()),
|
||||
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
return SettingsBody(
|
||||
title: LocaleKeys.settings_aiPage_title.tr(),
|
||||
description:
|
||||
LocaleKeys.settings_aiPage_keys_aiSettingsDescription.tr(),
|
||||
children: const [
|
||||
AIModelSeclection(),
|
||||
_AISearchToggle(value: false),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AIModelSeclection extends StatelessWidget {
|
||||
const AIModelSeclection({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
FlowyText(
|
||||
LocaleKeys.settings_aiPage_keys_llmModel.tr(),
|
||||
fontSize: 14,
|
||||
),
|
||||
const Spacer(),
|
||||
BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
return Expanded(
|
||||
child: SettingsDropdown<AIModelPB>(
|
||||
key: const Key('AIModelDropdown'),
|
||||
expandWidth: false,
|
||||
onChanged: (format) {
|
||||
context.read<SettingsAIBloc>().add(
|
||||
SettingsAIEvent.selectModel(format),
|
||||
);
|
||||
},
|
||||
selectedOption: state.userProfile.aiModel,
|
||||
options: _availableModels
|
||||
.map(
|
||||
(format) => buildDropdownMenuEntry<AIModelPB>(
|
||||
context,
|
||||
value: format,
|
||||
label: _titleForAIModel(format),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<AIModelPB> _availableModels = [
|
||||
AIModelPB.DefaultModel,
|
||||
AIModelPB.Claude3Opus,
|
||||
AIModelPB.Claude3Sonnet,
|
||||
AIModelPB.GPT35,
|
||||
AIModelPB.GPT4o,
|
||||
];
|
||||
|
||||
String _titleForAIModel(AIModelPB model) {
|
||||
switch (model) {
|
||||
case AIModelPB.DefaultModel:
|
||||
return "Default";
|
||||
case AIModelPB.Claude3Opus:
|
||||
return "Claude 3 Opus";
|
||||
case AIModelPB.Claude3Sonnet:
|
||||
return "Claude 3 Sonnet";
|
||||
case AIModelPB.GPT35:
|
||||
return "GPT-3.5";
|
||||
case AIModelPB.GPT4o:
|
||||
return "GPT-4o";
|
||||
case AIModelPB.LocalAIModel:
|
||||
return "Local";
|
||||
default:
|
||||
Log.error("Unknown AI model: $model, fallback to default");
|
||||
return "Default";
|
||||
}
|
||||
}
|
||||
|
||||
class _AISearchToggle extends StatelessWidget {
|
||||
const _AISearchToggle({required this.value});
|
||||
|
||||
final bool value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.settings_aiPage_keys_enableAISearchTitle.tr(),
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const HSpace(16),
|
||||
BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
if (state.aiSettings == null) {
|
||||
return const CircularProgressIndicator.adaptive();
|
||||
} else {
|
||||
return Toggle(
|
||||
value: state.enableSearchIndexing,
|
||||
onChanged: (_) {
|
||||
context.read<SettingsAIBloc>().add(
|
||||
const SettingsAIEvent.toggleAISearch(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_ai_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_billing_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_manage_data_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_plan_view.dart';
|
||||
@ -13,8 +15,6 @@ import 'package:appflowy/workspace/presentation/settings/widgets/feature_flags/f
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_page.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_notifications_view.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@ -113,6 +113,12 @@ class SettingsDialog extends StatelessWidget {
|
||||
return SettingCloud(restartAppFlowy: () => restartApp());
|
||||
case SettingsPage.shortcuts:
|
||||
return const SettingsShortcutsView();
|
||||
case SettingsPage.ai:
|
||||
if (user.authenticator == AuthenticatorPB.AppFlowyCloud) {
|
||||
return SettingsAIView(userProfile: user);
|
||||
} else {
|
||||
return const AIFeatureOnlySupportedWhenUsingAppFlowyCloud();
|
||||
}
|
||||
case SettingsPage.member:
|
||||
return WorkspaceMembersPage(userProfile: user);
|
||||
case SettingsPage.plan:
|
||||
|
@ -41,8 +41,7 @@ class RestartButton extends StatelessWidget {
|
||||
SizedBox(
|
||||
height: 42,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.settings_manageDataPage_dataStorage_actions_change
|
||||
.tr(),
|
||||
LocaleKeys.settings_menu_restartApp.tr(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
fontWeight: FontWeight.w600,
|
||||
radius: BorderRadius.circular(12),
|
||||
|
@ -102,6 +102,16 @@ class SettingsMenu extends StatelessWidget {
|
||||
icon: const FlowySvg(FlowySvgs.settings_shortcuts_m),
|
||||
changeSelectedPage: changeSelectedPage,
|
||||
),
|
||||
SettingsMenuElement(
|
||||
page: SettingsPage.ai,
|
||||
selectedPage: currentPage,
|
||||
label: LocaleKeys.settings_aiPage_menuLabel.tr(),
|
||||
icon: const FlowySvg(
|
||||
FlowySvgs.ai_summary_generate_s,
|
||||
size: Size.square(24),
|
||||
),
|
||||
changeSelectedPage: changeSelectedPage,
|
||||
),
|
||||
if (FeatureFlag.planBilling.isOn &&
|
||||
userProfile.authenticator ==
|
||||
AuthenticatorPB.AppFlowyCloud &&
|
||||
|
35
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
35
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -172,7 +172,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -192,7 +192,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -772,7 +772,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@ -787,6 +787,7 @@ dependencies = [
|
||||
"collab",
|
||||
"collab-rt-entity",
|
||||
"collab-rt-protocol",
|
||||
"futures",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"getrandom 0.2.10",
|
||||
@ -794,6 +795,8 @@ dependencies = [
|
||||
"infra",
|
||||
"mime",
|
||||
"parking_lot 0.12.1",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"reqwest",
|
||||
"scraper 0.17.1",
|
||||
@ -818,7 +821,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
@ -830,7 +833,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@ -1070,7 +1073,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -1095,7 +1098,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1341,7 +1344,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa 1.0.6",
|
||||
"phf 0.8.0",
|
||||
"phf 0.11.2",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@ -1452,10 +1455,11 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
"appflowy-ai-client",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"collab-entity",
|
||||
@ -2426,6 +2430,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.5",
|
||||
"chrono",
|
||||
"client-api",
|
||||
"collab",
|
||||
"collab-entity",
|
||||
"flowy-error",
|
||||
@ -2894,7 +2899,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@ -2911,7 +2916,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -3343,7 +3348,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -4850,7 +4855,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"heck 0.4.1",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.11.0",
|
||||
"log",
|
||||
"multimap",
|
||||
"once_cell",
|
||||
@ -4871,7 +4876,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.11.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.47",
|
||||
@ -5835,7 +5840,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
@ -52,7 +52,7 @@ collab-user = { version = "0.2" }
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6262816043efeede8823d7a7ea252083adf407e9" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "d61524d63605aa010afa6a734cbbe4fb4cd68ea1" }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
|
29
frontend/appflowy_web/wasm-libs/Cargo.lock
generated
29
frontend/appflowy_web/wasm-libs/Cargo.lock
generated
@ -215,7 +215,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -235,7 +235,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -561,7 +561,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@ -576,6 +576,7 @@ dependencies = [
|
||||
"collab",
|
||||
"collab-rt-entity",
|
||||
"collab-rt-protocol",
|
||||
"futures",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"getrandom 0.2.12",
|
||||
@ -583,6 +584,8 @@ dependencies = [
|
||||
"infra",
|
||||
"mime",
|
||||
"parking_lot 0.12.1",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"reqwest",
|
||||
"scraper 0.17.1",
|
||||
@ -607,7 +610,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
@ -619,7 +622,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@ -797,7 +800,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -822,7 +825,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1036,10 +1039,11 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
"appflowy-ai-client",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"collab-entity",
|
||||
@ -1662,6 +1666,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
"client-api",
|
||||
"collab",
|
||||
"collab-entity",
|
||||
"flowy-error",
|
||||
@ -1919,7 +1924,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@ -1936,7 +1941,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -2237,7 +2242,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -3951,7 +3956,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
@ -20,6 +20,7 @@ flowy-derive = { path = "../../rust-lib/build-tool/flowy-derive" }
|
||||
flowy-codegen = { path = "../../rust-lib/build-tool/flowy-codegen" }
|
||||
flowy-document = { path = "../../rust-lib/flowy-document" }
|
||||
flowy-folder = { path = "../../rust-lib/flowy-folder" }
|
||||
flowy-storage = { path = "../../rust-lib/flowy-storage" }
|
||||
lib-infra = { path = "../../rust-lib/lib-infra" }
|
||||
bytes = { version = "1.5" }
|
||||
protobuf = { version = "2.28.0" }
|
||||
@ -54,7 +55,7 @@ yrs = "0.18.8"
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6262816043efeede8823d7a7ea252083adf407e9" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "d61524d63605aa010afa6a734cbbe4fb4cd68ea1" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
|
@ -33,6 +33,9 @@ pub struct UserProfilePB {
|
||||
|
||||
#[pb(index = 10)]
|
||||
pub stability_ai_key: String,
|
||||
|
||||
#[pb(index = 11)]
|
||||
pub ai_model: String,
|
||||
}
|
||||
|
||||
impl From<UserProfile> for UserProfilePB {
|
||||
@ -52,6 +55,7 @@ impl From<UserProfile> for UserProfilePB {
|
||||
authenticator: user_profile.authenticator.into(),
|
||||
workspace_id: user_profile.workspace_id,
|
||||
stability_ai_key: user_profile.stability_ai_key,
|
||||
ai_model: user_profile.ai_model,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ pub struct UserProfilePB {
|
||||
pub encryption_sign: ::std::string::String,
|
||||
pub workspace_id: ::std::string::String,
|
||||
pub stability_ai_key: ::std::string::String,
|
||||
pub ai_model: ::std::string::String,
|
||||
// special fields
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
@ -289,6 +290,32 @@ impl UserProfilePB {
|
||||
pub fn take_stability_ai_key(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.stability_ai_key, ::std::string::String::new())
|
||||
}
|
||||
|
||||
// string ai_model = 11;
|
||||
|
||||
|
||||
pub fn get_ai_model(&self) -> &str {
|
||||
&self.ai_model
|
||||
}
|
||||
pub fn clear_ai_model(&mut self) {
|
||||
self.ai_model.clear();
|
||||
}
|
||||
|
||||
// Param is passed by value, moved
|
||||
pub fn set_ai_model(&mut self, v: ::std::string::String) {
|
||||
self.ai_model = v;
|
||||
}
|
||||
|
||||
// Mutable pointer to the field.
|
||||
// If field is not initialized, it is initialized with default value first.
|
||||
pub fn mut_ai_model(&mut self) -> &mut ::std::string::String {
|
||||
&mut self.ai_model
|
||||
}
|
||||
|
||||
// Take field
|
||||
pub fn take_ai_model(&mut self) -> ::std::string::String {
|
||||
::std::mem::replace(&mut self.ai_model, ::std::string::String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for UserProfilePB {
|
||||
@ -334,6 +361,9 @@ impl ::protobuf::Message for UserProfilePB {
|
||||
10 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.stability_ai_key)?;
|
||||
},
|
||||
11 => {
|
||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.ai_model)?;
|
||||
},
|
||||
_ => {
|
||||
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
|
||||
},
|
||||
@ -376,6 +406,9 @@ impl ::protobuf::Message for UserProfilePB {
|
||||
if !self.stability_ai_key.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(10, &self.stability_ai_key);
|
||||
}
|
||||
if !self.ai_model.is_empty() {
|
||||
my_size += ::protobuf::rt::string_size(11, &self.ai_model);
|
||||
}
|
||||
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||
self.cached_size.set(my_size);
|
||||
my_size
|
||||
@ -412,6 +445,9 @@ impl ::protobuf::Message for UserProfilePB {
|
||||
if !self.stability_ai_key.is_empty() {
|
||||
os.write_string(10, &self.stability_ai_key)?;
|
||||
}
|
||||
if !self.ai_model.is_empty() {
|
||||
os.write_string(11, &self.ai_model)?;
|
||||
}
|
||||
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||
::std::result::Result::Ok(())
|
||||
}
|
||||
@ -500,6 +536,11 @@ impl ::protobuf::Message for UserProfilePB {
|
||||
|m: &UserProfilePB| { &m.stability_ai_key },
|
||||
|m: &mut UserProfilePB| { &mut m.stability_ai_key },
|
||||
));
|
||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
|
||||
"ai_model",
|
||||
|m: &UserProfilePB| { &m.ai_model },
|
||||
|m: &mut UserProfilePB| { &mut m.ai_model },
|
||||
));
|
||||
::protobuf::reflect::MessageDescriptor::new_pb_name::<UserProfilePB>(
|
||||
"UserProfilePB",
|
||||
fields,
|
||||
@ -526,6 +567,7 @@ impl ::protobuf::Clear for UserProfilePB {
|
||||
self.encryption_sign.clear();
|
||||
self.workspace_id.clear();
|
||||
self.stability_ai_key.clear();
|
||||
self.ai_model.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
@ -593,7 +635,7 @@ impl ::protobuf::reflect::ProtobufValue for EncryptionTypePB {
|
||||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\nuser.proto\x1a\nauth.proto\"\xc7\x02\n\rUserProfilePB\x12\x0e\n\x02i\
|
||||
\n\nuser.proto\x1a\nauth.proto\"\xe2\x02\n\rUserProfilePB\x12\x0e\n\x02i\
|
||||
d\x18\x01\x20\x01(\x03R\x02id\x12\x14\n\x05email\x18\x02\x20\x01(\tR\x05\
|
||||
email\x12\x12\n\x04name\x18\x03\x20\x01(\tR\x04name\x12\x14\n\x05token\
|
||||
\x18\x04\x20\x01(\tR\x05token\x12\x19\n\x08icon_url\x18\x05\x20\x01(\tR\
|
||||
@ -601,8 +643,9 @@ static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\rauthenticator\x18\x07\x20\x01(\x0e2\x10.AuthenticatorPBR\rauthenticato\
|
||||
r\x12'\n\x0fencryption_sign\x18\x08\x20\x01(\tR\x0eencryptionSign\x12!\n\
|
||||
\x0cworkspace_id\x18\t\x20\x01(\tR\x0bworkspaceId\x12(\n\x10stability_ai\
|
||||
_key\x18\n\x20\x01(\tR\x0estabilityAiKey*3\n\x10EncryptionTypePB\x12\x10\
|
||||
\n\x0cNoEncryption\x10\0\x12\r\n\tSymmetric\x10\x01b\x06proto3\
|
||||
_key\x18\n\x20\x01(\tR\x0estabilityAiKey\x12\x19\n\x08ai_model\x18\x0b\
|
||||
\x20\x01(\tR\x07aiModel*3\n\x10EncryptionTypePB\x12\x10\n\x0cNoEncryptio\
|
||||
n\x10\0\x12\r\n\tSymmetric\x10\x01b\x06proto3\
|
||||
";
|
||||
|
||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||
|
@ -41,7 +41,7 @@ impl ServerProviderWASM {
|
||||
self.config.clone(),
|
||||
true,
|
||||
self.device_id.clone(),
|
||||
"0.0.1"
|
||||
"0.0.1",
|
||||
));
|
||||
*self.server.write() = Some(server.clone());
|
||||
server
|
||||
@ -70,6 +70,10 @@ impl UserCloudServiceProvider for ServerProviderWASM {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_ai_model(&self, ai_model: &str) -> Result<(), FlowyError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>> {
|
||||
self.get_server().subscribe_token_state()
|
||||
}
|
||||
|
35
frontend/appflowy_web_app/src-tauri/Cargo.lock
generated
35
frontend/appflowy_web_app/src-tauri/Cargo.lock
generated
@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -183,7 +183,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -746,7 +746,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@ -761,6 +761,7 @@ dependencies = [
|
||||
"collab",
|
||||
"collab-rt-entity",
|
||||
"collab-rt-protocol",
|
||||
"futures",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"getrandom 0.2.12",
|
||||
@ -768,6 +769,8 @@ dependencies = [
|
||||
"infra",
|
||||
"mime",
|
||||
"parking_lot 0.12.1",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"reqwest",
|
||||
"scraper 0.17.1",
|
||||
@ -792,7 +795,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
@ -804,7 +807,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@ -1053,7 +1056,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -1078,7 +1081,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1328,7 +1331,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa 1.0.10",
|
||||
"phf 0.8.0",
|
||||
"phf 0.11.2",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@ -1439,10 +1442,11 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
"appflowy-ai-client",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"collab-entity",
|
||||
@ -2463,6 +2467,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.7",
|
||||
"chrono",
|
||||
"client-api",
|
||||
"collab",
|
||||
"collab-entity",
|
||||
"flowy-error",
|
||||
@ -2968,7 +2973,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@ -2985,7 +2990,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -3422,7 +3427,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -4931,7 +4936,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"heck 0.4.1",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.11.0",
|
||||
"log",
|
||||
"multimap",
|
||||
"once_cell",
|
||||
@ -4952,7 +4957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.11.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.55",
|
||||
@ -5930,7 +5935,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
@ -52,7 +52,7 @@ collab-user = { version = "0.2" }
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6262816043efeede8823d7a7ea252083adf407e9" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "d61524d63605aa010afa6a734cbbe4fb4cd68ea1" }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
|
@ -369,15 +369,6 @@
|
||||
"change": "Change email"
|
||||
}
|
||||
},
|
||||
"keys": {
|
||||
"title": "AI API Keys",
|
||||
"openAILabel": "OpenAI API key",
|
||||
"openAITooltip": "You can find your Secret API key on the API key page",
|
||||
"openAIHint": "Input your OpenAI API Key",
|
||||
"stabilityAILabel": "Stability API key",
|
||||
"stabilityAITooltip": "Your Stability API key, used to authenticate your requests",
|
||||
"stabilityAIHint": "Input your Stability API Key"
|
||||
},
|
||||
"login": {
|
||||
"title": "Account login",
|
||||
"loginLabel": "Log in",
|
||||
@ -611,6 +602,23 @@
|
||||
"couldNotLoadErrorMsg": "Could not load shortcuts, Try again",
|
||||
"couldNotSaveErrorMsg": "Could not save shortcuts, Try again"
|
||||
},
|
||||
"aiPage": {
|
||||
"title": "AI Settings",
|
||||
"menuLabel": "AI Settings",
|
||||
"keys": {
|
||||
"enableAISearchTitle": "AI Search",
|
||||
"aiSettingsDescription": "Select or configure Ai models used on AppFlowy. For best performance we recommend using the default model options",
|
||||
"loginToEnableAIFeature": "AI features are only enabled after logging in with AppFlowy Cloud. If you don't have an AppFlowy account, go to 'My Account' to sign up",
|
||||
"llmModel": "Language Model",
|
||||
"title": "AI API Keys",
|
||||
"openAILabel": "OpenAI API key",
|
||||
"openAITooltip": "You can find your Secret API key on the API key page",
|
||||
"openAIHint": "Input your OpenAI API Key",
|
||||
"stabilityAILabel": "Stability API key",
|
||||
"stabilityAITooltip": "Your Stability API key, used to authenticate your requests",
|
||||
"stabilityAIHint": "Input your Stability API Key"
|
||||
}
|
||||
},
|
||||
"planPage": {
|
||||
"menuLabel": "Plan",
|
||||
"title": "Pricing plan",
|
||||
@ -1295,6 +1303,7 @@
|
||||
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
|
||||
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
||||
"smartEditDisabled": "Connect OpenAI in Settings",
|
||||
"appflowyAIEditDisabled": "Sign in to enable AI features",
|
||||
"discardResponse": "Do you want to discard the AI responses?",
|
||||
"createInlineMathEquation": "Create equation",
|
||||
"fonts": "Fonts",
|
||||
|
51
frontend/rust-lib/Cargo.lock
generated
51
frontend/rust-lib/Cargo.lock
generated
@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -183,7 +183,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -664,7 +664,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@ -679,6 +679,7 @@ dependencies = [
|
||||
"collab",
|
||||
"collab-rt-entity",
|
||||
"collab-rt-protocol",
|
||||
"futures",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"getrandom 0.2.10",
|
||||
@ -686,6 +687,8 @@ dependencies = [
|
||||
"infra",
|
||||
"mime",
|
||||
"parking_lot 0.12.1",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"prost",
|
||||
"reqwest",
|
||||
"scraper 0.17.1",
|
||||
@ -710,7 +713,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
@ -722,7 +725,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@ -931,7 +934,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -956,7 +959,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1176,7 +1179,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"phf 0.8.0",
|
||||
"phf 0.11.2",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@ -1276,10 +1279,11 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
"appflowy-ai-client",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"collab-entity",
|
||||
@ -2265,6 +2269,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.21.5",
|
||||
"chrono",
|
||||
"client-api",
|
||||
"collab",
|
||||
"collab-entity",
|
||||
"flowy-error",
|
||||
@ -2567,7 +2572,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@ -2584,7 +2589,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -2949,7 +2954,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -3827,7 +3832,7 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_macros 0.8.0",
|
||||
"phf_shared 0.8.0",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
@ -3847,6 +3852,7 @@ version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_macros 0.11.2",
|
||||
"phf_shared 0.11.2",
|
||||
]
|
||||
|
||||
@ -3914,6 +3920,19 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.2",
|
||||
"phf_shared 0.11.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.47",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.8.0"
|
||||
@ -4117,7 +4136,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"heck 0.4.1",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.11.0",
|
||||
"log",
|
||||
"multimap",
|
||||
"once_cell",
|
||||
@ -4138,7 +4157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.11.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.47",
|
||||
@ -5035,7 +5054,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6262816043efeede8823d7a7ea252083adf407e9#6262816043efeede8823d7a7ea252083adf407e9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=d61524d63605aa010afa6a734cbbe4fb4cd68ea1#d61524d63605aa010afa6a734cbbe4fb4cd68ea1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
@ -31,7 +31,6 @@ members = [
|
||||
"flowy-search-pub",
|
||||
"flowy-chat",
|
||||
"flowy-chat-pub",
|
||||
"flowy-storage-pub",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@ -97,8 +96,8 @@ validator = { version = "0.16.1", features = ["derive"] }
|
||||
# Run the script:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6262816043efeede8823d7a7ea252083adf407e9" }
|
||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6262816043efeede8823d7a7ea252083adf407e9" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "d61524d63605aa010afa6a734cbbe4fb4cd68ea1" }
|
||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "d61524d63605aa010afa6a734cbbe4fb4cd68ea1" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::event_builder::EventBuilder;
|
||||
use crate::EventIntegrationTest;
|
||||
use flowy_chat::entities::{
|
||||
ChatMessageListPB, ChatMessageTypePB, LoadNextChatMessagePB, LoadPrevChatMessagePB,
|
||||
SendChatPayloadPB,
|
||||
ChatMessageListPB, ChatMessageTypePB, CompleteTextPB, CompleteTextTaskPB, CompletionTypePB,
|
||||
LoadNextChatMessagePB, LoadPrevChatMessagePB, SendChatPayloadPB,
|
||||
};
|
||||
use flowy_chat::event_map::ChatEvent;
|
||||
use flowy_folder::entities::{CreateViewPayloadPB, ViewLayoutPB, ViewPB};
|
||||
@ -86,4 +86,22 @@ impl EventIntegrationTest {
|
||||
.await
|
||||
.parse::<ChatMessageListPB>()
|
||||
}
|
||||
|
||||
pub async fn complete_text(
|
||||
&self,
|
||||
text: &str,
|
||||
completion_type: CompletionTypePB,
|
||||
) -> CompleteTextTaskPB {
|
||||
let payload = CompleteTextPB {
|
||||
text: text.to_string(),
|
||||
completion_type,
|
||||
stream_port: 0,
|
||||
};
|
||||
EventBuilder::new(self.clone())
|
||||
.event(ChatEvent::CompleteText)
|
||||
.payload(payload)
|
||||
.async_send()
|
||||
.await
|
||||
.parse::<CompleteTextTaskPB>()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
|
||||
use event_integration_test::user_event::user_localhost_af_cloud;
|
||||
use event_integration_test::EventIntegrationTest;
|
||||
use flowy_chat::entities::{CompletionTypePB};
|
||||
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
#[tokio::test]
|
||||
async fn af_cloud_complete_text_test() {
|
||||
user_localhost_af_cloud().await;
|
||||
let test = EventIntegrationTest::new().await;
|
||||
test.af_cloud_sign_up().await;
|
||||
|
||||
let _workspace_id = test.get_current_workspace().await.id;
|
||||
let _task = test
|
||||
.complete_text("hello world", CompletionTypePB::MakeLonger)
|
||||
.await;
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(6)).await;
|
||||
}
|
@ -1 +1,2 @@
|
||||
mod ai_tool_test;
|
||||
mod chat_message_test;
|
||||
|
@ -1,5 +1,7 @@
|
||||
use bytes::Bytes;
|
||||
pub use client_api::entity::ai_dto::{RelatedQuestion, RepeatedRelatedQuestion, StringOrMessage};
|
||||
pub use client_api::entity::ai_dto::{
|
||||
CompletionType, RelatedQuestion, RepeatedRelatedQuestion, StringOrMessage,
|
||||
};
|
||||
pub use client_api::entity::{
|
||||
ChatAuthorType, ChatMessage, ChatMessageType, MessageCursor, QAChatMessage, RepeatedChatMessage,
|
||||
};
|
||||
@ -11,6 +13,7 @@ use lib_infra::future::FutureResult;
|
||||
|
||||
pub type ChatMessageStream = BoxStream<'static, Result<ChatMessage, AppResponseError>>;
|
||||
pub type StreamAnswer = BoxStream<'static, Result<Bytes, AppResponseError>>;
|
||||
pub type StreamComplete = BoxStream<'static, Result<Bytes, AppResponseError>>;
|
||||
#[async_trait]
|
||||
pub trait ChatCloudService: Send + Sync + 'static {
|
||||
fn create_chat(
|
||||
@ -72,4 +75,11 @@ pub trait ChatCloudService: Send + Sync + 'static {
|
||||
chat_id: &str,
|
||||
question_message_id: i64,
|
||||
) -> FutureResult<ChatMessage, FlowyError>;
|
||||
|
||||
async fn stream_complete(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
text: &str,
|
||||
complete_type: CompletionType,
|
||||
) -> Result<StreamComplete, FlowyError>;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::chat_manager::ChatUserService;
|
||||
use crate::entities::{
|
||||
ChatMessageErrorPB, ChatMessageListPB, ChatMessagePB, RepeatedRelatedQuestionPB,
|
||||
};
|
||||
use crate::manager::ChatUserService;
|
||||
use crate::notification::{send_notification, ChatNotification};
|
||||
use crate::persistence::{insert_chat_messages, select_chat_messages, ChatMessageTable};
|
||||
use allo_isolate::Isolate;
|
||||
|
@ -17,8 +17,8 @@ pub trait ChatUserService: Send + Sync + 'static {
|
||||
}
|
||||
|
||||
pub struct ChatManager {
|
||||
cloud_service: Arc<dyn ChatCloudService>,
|
||||
user_service: Arc<dyn ChatUserService>,
|
||||
pub(crate) cloud_service: Arc<dyn ChatCloudService>,
|
||||
pub(crate) user_service: Arc<dyn ChatUserService>,
|
||||
chats: Arc<DashMap<String, Arc<Chat>>>,
|
||||
}
|
||||
|
@ -205,3 +205,32 @@ impl From<RepeatedRelatedQuestion> for RepeatedRelatedQuestionPB {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf, Clone, Debug)]
|
||||
pub struct CompleteTextPB {
|
||||
#[pb(index = 1)]
|
||||
pub text: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub completion_type: CompletionTypePB,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub stream_port: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf, Clone, Debug)]
|
||||
pub struct CompleteTextTaskPB {
|
||||
#[pb(index = 1)]
|
||||
pub task_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, ProtoBuf_Enum, Default)]
|
||||
pub enum CompletionTypePB {
|
||||
UnknownCompletionType = 0,
|
||||
#[default]
|
||||
ImproveWriting = 1,
|
||||
SpellingAndGrammar = 2,
|
||||
MakeShorter = 3,
|
||||
MakeLonger = 4,
|
||||
ContinueWriting = 5,
|
||||
}
|
||||
|
@ -2,11 +2,12 @@ use flowy_chat_pub::cloud::ChatMessageType;
|
||||
use std::sync::{Arc, Weak};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::tools::AITools;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||
|
||||
use crate::chat_manager::ChatManager;
|
||||
use crate::entities::*;
|
||||
use crate::manager::ChatManager;
|
||||
|
||||
fn upgrade_chat_manager(
|
||||
chat_manager: AFPluginState<Weak<ChatManager>>,
|
||||
@ -110,3 +111,22 @@ pub(crate) async fn stop_stream_handler(
|
||||
chat_manager.stop_stream(&data.chat_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn start_complete_text_handler(
|
||||
data: AFPluginData<CompleteTextPB>,
|
||||
tools: AFPluginState<Arc<AITools>>,
|
||||
) -> DataResult<CompleteTextTaskPB, FlowyError> {
|
||||
let task = tools.create_complete_task(data.into_inner()).await?;
|
||||
data_result_ok(task)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn stop_complete_text_handler(
|
||||
data: AFPluginData<CompleteTextTaskPB>,
|
||||
tools: AFPluginState<Arc<AITools>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let data = data.into_inner();
|
||||
tools.cancel_complete_task(&data.task_id).await;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,23 +1,30 @@
|
||||
use std::sync::Weak;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use strum_macros::Display;
|
||||
|
||||
use crate::tools::AITools;
|
||||
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
|
||||
use lib_dispatch::prelude::*;
|
||||
|
||||
use crate::chat_manager::ChatManager;
|
||||
use crate::event_handler::*;
|
||||
use crate::manager::ChatManager;
|
||||
|
||||
pub fn init(chat_manager: Weak<ChatManager>) -> AFPlugin {
|
||||
let user_service = Arc::downgrade(&chat_manager.upgrade().unwrap().user_service);
|
||||
let cloud_service = Arc::downgrade(&chat_manager.upgrade().unwrap().cloud_service);
|
||||
let ai_tools = Arc::new(AITools::new(cloud_service, user_service));
|
||||
AFPlugin::new()
|
||||
.name("Flowy-Chat")
|
||||
.state(chat_manager)
|
||||
.state(ai_tools)
|
||||
.event(ChatEvent::StreamMessage, stream_chat_message_handler)
|
||||
.event(ChatEvent::LoadPrevMessage, load_prev_message_handler)
|
||||
.event(ChatEvent::LoadNextMessage, load_next_message_handler)
|
||||
.event(ChatEvent::GetRelatedQuestion, get_related_question_handler)
|
||||
.event(ChatEvent::GetAnswerForQuestion, get_answer_handler)
|
||||
.event(ChatEvent::StopStream, stop_stream_handler)
|
||||
.event(ChatEvent::CompleteText, start_complete_text_handler)
|
||||
.event(ChatEvent::StopCompleteText, stop_complete_text_handler)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||
@ -41,4 +48,10 @@ pub enum ChatEvent {
|
||||
|
||||
#[event(input = "ChatMessageIdPB", output = "ChatMessagePB")]
|
||||
GetAnswerForQuestion = 5,
|
||||
|
||||
#[event(input = "CompleteTextPB", output = "CompleteTextTaskPB")]
|
||||
CompleteText = 6,
|
||||
|
||||
#[event(input = "CompleteTextTaskPB")]
|
||||
StopCompleteText = 7,
|
||||
}
|
||||
|
@ -2,8 +2,9 @@ mod event_handler;
|
||||
pub mod event_map;
|
||||
|
||||
mod chat;
|
||||
pub mod chat_manager;
|
||||
pub mod entities;
|
||||
pub mod manager;
|
||||
pub mod notification;
|
||||
mod persistence;
|
||||
mod protobuf;
|
||||
mod tools;
|
||||
|
136
frontend/rust-lib/flowy-chat/src/tools.rs
Normal file
136
frontend/rust-lib/flowy-chat/src/tools.rs
Normal file
@ -0,0 +1,136 @@
|
||||
use crate::chat_manager::ChatUserService;
|
||||
use crate::entities::{CompleteTextPB, CompleteTextTaskPB, CompletionTypePB};
|
||||
use allo_isolate::Isolate;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use flowy_chat_pub::cloud::{ChatCloudService, CompletionType};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use lib_infra::isolate_stream::IsolateSink;
|
||||
|
||||
use std::sync::{Arc, Weak};
|
||||
use tokio::select;
|
||||
use tracing::{error, trace};
|
||||
|
||||
pub struct AITools {
|
||||
tasks: Arc<DashMap<String, tokio::sync::mpsc::Sender<()>>>,
|
||||
cloud_service: Weak<dyn ChatCloudService>,
|
||||
user_service: Weak<dyn ChatUserService>,
|
||||
}
|
||||
|
||||
impl AITools {
|
||||
pub fn new(
|
||||
cloud_service: Weak<dyn ChatCloudService>,
|
||||
user_service: Weak<dyn ChatUserService>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tasks: Arc::new(DashMap::new()),
|
||||
cloud_service,
|
||||
user_service,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_complete_task(
|
||||
&self,
|
||||
complete: CompleteTextPB,
|
||||
) -> FlowyResult<CompleteTextTaskPB> {
|
||||
let workspace_id = self
|
||||
.user_service
|
||||
.upgrade()
|
||||
.ok_or_else(FlowyError::internal)?
|
||||
.workspace_id()?;
|
||||
let (tx, rx) = tokio::sync::mpsc::channel(1);
|
||||
let task = ToolTask::new(workspace_id, complete, self.cloud_service.clone(), rx);
|
||||
let task_id = task.task_id.clone();
|
||||
self.tasks.insert(task_id.clone(), tx);
|
||||
|
||||
task.start().await;
|
||||
Ok(CompleteTextTaskPB { task_id })
|
||||
}
|
||||
|
||||
pub async fn cancel_complete_task(&self, task_id: &str) {
|
||||
if let Some(entry) = self.tasks.remove(task_id) {
|
||||
let _ = entry.1.send(()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ToolTask {
|
||||
workspace_id: String,
|
||||
task_id: String,
|
||||
stop_rx: tokio::sync::mpsc::Receiver<()>,
|
||||
context: CompleteTextPB,
|
||||
cloud_service: Weak<dyn ChatCloudService>,
|
||||
}
|
||||
|
||||
impl ToolTask {
|
||||
pub fn new(
|
||||
workspace_id: String,
|
||||
context: CompleteTextPB,
|
||||
cloud_service: Weak<dyn ChatCloudService>,
|
||||
stop_rx: tokio::sync::mpsc::Receiver<()>,
|
||||
) -> Self {
|
||||
Self {
|
||||
workspace_id,
|
||||
task_id: uuid::Uuid::new_v4().to_string(),
|
||||
context,
|
||||
cloud_service,
|
||||
stop_rx,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn start(mut self) {
|
||||
tokio::spawn(async move {
|
||||
let mut sink = IsolateSink::new(Isolate::new(self.context.stream_port));
|
||||
match self.cloud_service.upgrade() {
|
||||
None => {},
|
||||
Some(cloud_service) => {
|
||||
let complete_type = match self.context.completion_type {
|
||||
CompletionTypePB::UnknownCompletionType => CompletionType::ImproveWriting,
|
||||
CompletionTypePB::ImproveWriting => CompletionType::ImproveWriting,
|
||||
CompletionTypePB::SpellingAndGrammar => CompletionType::SpellingAndGrammar,
|
||||
CompletionTypePB::MakeShorter => CompletionType::MakeShorter,
|
||||
CompletionTypePB::MakeLonger => CompletionType::MakeLonger,
|
||||
CompletionTypePB::ContinueWriting => CompletionType::ContinueWriting,
|
||||
};
|
||||
let _ = sink.send("start:".to_string()).await;
|
||||
match cloud_service
|
||||
.stream_complete(&self.workspace_id, &self.context.text, complete_type)
|
||||
.await
|
||||
{
|
||||
Ok(mut stream) => loop {
|
||||
select! {
|
||||
_ = self.stop_rx.recv() => {
|
||||
return;
|
||||
},
|
||||
result = stream.next() => {
|
||||
match result {
|
||||
Some(Ok(data)) => {
|
||||
let s = String::from_utf8(data.to_vec()).unwrap_or_default();
|
||||
trace!("stream completion data: {}", s);
|
||||
let _ = sink.send(format!("data:{}", s)).await;
|
||||
},
|
||||
Some(Err(error)) => {
|
||||
error!("stream error: {}", error);
|
||||
let _ = sink.send(format!("error:{}", error)).await;
|
||||
return;
|
||||
},
|
||||
None => {
|
||||
let _ = sink.send(format!("finish:{}", self.task_id)).await;
|
||||
return;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
error!("stream complete error: {}", error);
|
||||
let _ = sink.send(format!("error:{}", error)).await;
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
use flowy_chat::manager::{ChatManager, ChatUserService};
|
||||
use flowy_chat::chat_manager::{ChatManager, ChatUserService};
|
||||
use flowy_chat_pub::cloud::ChatCloudService;
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_sqlite::DBConnection;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use bytes::Bytes;
|
||||
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use collab_integrate::CollabKVDB;
|
||||
use flowy_chat::manager::ChatManager;
|
||||
use flowy_chat::chat_manager::ChatManager;
|
||||
use flowy_database2::entities::DatabaseLayoutPB;
|
||||
use flowy_database2::services::share::csv::CSVFormat;
|
||||
use flowy_database2::template::{make_default_board, make_default_calendar, make_default_grid};
|
||||
|
@ -54,6 +54,7 @@ pub fn create_log_filter(level: String, with_crates: Vec<String>, platform: Plat
|
||||
filters.push(format!("flowy_search={}", level));
|
||||
filters.push(format!("flowy_chat={}", level));
|
||||
filters.push(format!("flowy_storage={}", level));
|
||||
filters.push(format!("flowy_ai={}", level));
|
||||
// Enable the frontend logs. DO NOT DISABLE.
|
||||
// These logs are essential for debugging and verifying frontend behavior.
|
||||
filters.push(format!("dart_ffi={}", level));
|
||||
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||
|
||||
use anyhow::Error;
|
||||
use client_api::collab_sync::{SinkConfig, SyncObject, SyncPlugin};
|
||||
use client_api::entity::ai_dto::RepeatedRelatedQuestion;
|
||||
use client_api::entity::ai_dto::{CompletionType, RepeatedRelatedQuestion};
|
||||
use client_api::entity::ChatMessageType;
|
||||
use collab::core::origin::{CollabClient, CollabOrigin};
|
||||
|
||||
@ -12,14 +12,14 @@ use collab::preclude::CollabPlugin;
|
||||
use collab_entity::CollabType;
|
||||
use collab_plugins::cloud_storage::postgres::SupabaseDBPlugin;
|
||||
use tokio_stream::wrappers::WatchStream;
|
||||
use tracing::debug;
|
||||
use tracing::{debug, info};
|
||||
|
||||
use collab_integrate::collab_builder::{
|
||||
CollabCloudPluginProvider, CollabPluginProviderContext, CollabPluginProviderType,
|
||||
};
|
||||
use flowy_chat_pub::cloud::{
|
||||
ChatCloudService, ChatMessage, ChatMessageStream, MessageCursor, RepeatedChatMessage,
|
||||
StreamAnswer,
|
||||
StreamAnswer, StreamComplete,
|
||||
};
|
||||
use flowy_database_pub::cloud::{
|
||||
CollabDocStateByOid, DatabaseCloudService, DatabaseSnapshot, SummaryRowContent,
|
||||
@ -148,6 +148,13 @@ impl UserCloudServiceProvider for ServerProvider {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_ai_model(&self, ai_model: &str) -> Result<(), FlowyError> {
|
||||
info!("Set AI model: {}", ai_model);
|
||||
let server = self.get_server()?;
|
||||
server.set_ai_model(ai_model)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>> {
|
||||
let server = self.get_server().ok()?;
|
||||
server.subscribe_token_state()
|
||||
@ -667,6 +674,21 @@ impl ChatCloudService for ServerProvider {
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
async fn stream_complete(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
text: &str,
|
||||
complete_type: CompletionType,
|
||||
) -> Result<StreamComplete, FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let text = text.to_string();
|
||||
let server = self.get_server()?;
|
||||
server
|
||||
.chat_service()
|
||||
.stream_complete(&workspace_id, &text, complete_type)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
@ -9,7 +9,7 @@ use tokio::sync::RwLock;
|
||||
use tracing::{debug, error, event, info, instrument};
|
||||
|
||||
use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabPluginProviderType};
|
||||
use flowy_chat::manager::ChatManager;
|
||||
use flowy_chat::chat_manager::ChatManager;
|
||||
use flowy_database2::DatabaseManager;
|
||||
use flowy_document::manager::DocumentManager;
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
|
@ -1,4 +1,4 @@
|
||||
use flowy_chat::manager::ChatManager;
|
||||
use flowy_chat::chat_manager::ChatManager;
|
||||
use std::sync::Weak;
|
||||
|
||||
use flowy_database2::DatabaseManager;
|
||||
|
@ -1,11 +1,11 @@
|
||||
use crate::af_cloud::AFServer;
|
||||
use client_api::entity::ai_dto::RepeatedRelatedQuestion;
|
||||
use client_api::entity::ai_dto::{CompleteTextParams, CompletionType, RepeatedRelatedQuestion};
|
||||
use client_api::entity::{
|
||||
CreateAnswerMessageParams, CreateChatMessageParams, CreateChatParams, MessageCursor,
|
||||
RepeatedChatMessage,
|
||||
};
|
||||
use flowy_chat_pub::cloud::{
|
||||
ChatCloudService, ChatMessage, ChatMessageStream, ChatMessageType, StreamAnswer,
|
||||
ChatCloudService, ChatMessage, ChatMessageStream, ChatMessageType, StreamAnswer, StreamComplete,
|
||||
};
|
||||
use flowy_error::FlowyError;
|
||||
use futures_util::StreamExt;
|
||||
@ -187,4 +187,23 @@ where
|
||||
Ok(resp)
|
||||
})
|
||||
}
|
||||
|
||||
async fn stream_complete(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
text: &str,
|
||||
completion_type: CompletionType,
|
||||
) -> Result<StreamComplete, FlowyError> {
|
||||
let params = CompleteTextParams {
|
||||
text: text.to_string(),
|
||||
completion_type,
|
||||
};
|
||||
let stream = self
|
||||
.inner
|
||||
.try_get_client()?
|
||||
.stream_completion_text(workspace_id, params)
|
||||
.await
|
||||
.map_err(FlowyError::from)?;
|
||||
Ok(stream.boxed())
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ use client_api::entity::workspace_dto::{
|
||||
CreateWorkspaceParam, PatchWorkspaceParam, WorkspaceMemberChangeset, WorkspaceMemberInvitation,
|
||||
};
|
||||
use client_api::entity::{
|
||||
AFRole, AFWorkspace, AFWorkspaceInvitation, AuthProvider, CollabParams, CreateCollabParams,
|
||||
QueryWorkspaceMember,
|
||||
AFRole, AFWorkspace, AFWorkspaceInvitation, AFWorkspaceSettings, AFWorkspaceSettingsChange,
|
||||
AuthProvider, CollabParams, CreateCollabParams, QueryWorkspaceMember,
|
||||
};
|
||||
use client_api::entity::{QueryCollab, QueryCollabParams};
|
||||
use client_api::{Client, ClientConfiguration};
|
||||
@ -570,6 +570,35 @@ where
|
||||
Ok(url)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_workspace_setting(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
) -> FutureResult<AFWorkspaceSettings, FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
let client = try_get_client?;
|
||||
let settings = client.get_workspace_settings(&workspace_id).await?;
|
||||
Ok(settings)
|
||||
})
|
||||
}
|
||||
|
||||
fn update_workspace_setting(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
workspace_settings: AFWorkspaceSettingsChange,
|
||||
) -> FutureResult<AFWorkspaceSettings, FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let try_get_client = self.server.try_get_client();
|
||||
FutureResult::new(async move {
|
||||
let client = try_get_client?;
|
||||
let settings = client
|
||||
.update_workspace_settings(&workspace_id, &workspace_settings)
|
||||
.await?;
|
||||
Ok(settings)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_admin_client(client: &Arc<AFCloudClient>) -> FlowyResult<Client> {
|
||||
|
@ -65,6 +65,7 @@ pub fn user_profile_from_af_profile(
|
||||
encryption_type,
|
||||
uid: profile.uid,
|
||||
updated_at: profile.updated_at,
|
||||
ai_model: "".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Error;
|
||||
use client_api::collab_sync::ServerCollabMessage;
|
||||
use client_api::entity::ai_dto::AIModel;
|
||||
use client_api::entity::UserMessage;
|
||||
use client_api::notify::{TokenState, TokenStateReceiver};
|
||||
use client_api::ws::{
|
||||
@ -126,6 +128,11 @@ impl AppFlowyServer for AppFlowyCloudServer {
|
||||
.map_err(|err| Error::new(FlowyError::unauthorized().with_context(err)))
|
||||
}
|
||||
|
||||
fn set_ai_model(&self, ai_model: &str) -> Result<(), Error> {
|
||||
self.client.set_ai_model(AIModel::from_str(ai_model)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>> {
|
||||
let mut token_state_rx = self.client.subscribe_token_state();
|
||||
let (watch_tx, watch_rx) = watch::channel(UserTokenState::Init);
|
||||
|
@ -1,6 +1,8 @@
|
||||
use client_api::entity::ai_dto::RepeatedRelatedQuestion;
|
||||
use client_api::entity::ai_dto::{CompletionType, RepeatedRelatedQuestion};
|
||||
use client_api::entity::{ChatMessageType, MessageCursor, RepeatedChatMessage};
|
||||
use flowy_chat_pub::cloud::{ChatCloudService, ChatMessage, ChatMessageStream, StreamAnswer};
|
||||
use flowy_chat_pub::cloud::{
|
||||
ChatCloudService, ChatMessage, ChatMessageStream, StreamAnswer, StreamComplete,
|
||||
};
|
||||
use flowy_error::FlowyError;
|
||||
use lib_infra::async_trait::async_trait;
|
||||
use lib_infra::future::FutureResult;
|
||||
@ -96,4 +98,13 @@ impl ChatCloudService for DefaultChatCloudServiceImpl {
|
||||
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
|
||||
})
|
||||
}
|
||||
|
||||
async fn stream_complete(
|
||||
&self,
|
||||
_workspace_id: &str,
|
||||
_text: &str,
|
||||
_complete_type: CompletionType,
|
||||
) -> Result<StreamComplete, FlowyError> {
|
||||
Err(FlowyError::not_support().with_context("complete text is not supported in local server."))
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,10 @@ pub trait AppFlowyServer: Send + Sync + 'static {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_ai_model(&self, _ai_model: &str) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>> {
|
||||
None
|
||||
}
|
||||
|
@ -242,6 +242,7 @@ where
|
||||
authenticator: Authenticator::Supabase,
|
||||
encryption_type: EncryptionType::from_sign(&response.encryption_sign),
|
||||
updated_at: response.updated_at.timestamp(),
|
||||
ai_model: "".to_string(),
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
@ -0,0 +1 @@
|
||||
-- This file should undo anything in `up.sql`
|
@ -0,0 +1,2 @@
|
||||
-- Your SQL goes here
|
||||
ALTER TABLE user_table ADD COLUMN ai_model TEXT NOT NULL DEFAULT '';
|
@ -75,6 +75,7 @@ diesel::table! {
|
||||
encryption_type -> Text,
|
||||
stability_ai_key -> Text,
|
||||
updated_at -> BigInt,
|
||||
ai_model -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,3 +21,4 @@ tokio-stream = "0.1.14"
|
||||
flowy-folder-pub.workspace = true
|
||||
tracing.workspace = true
|
||||
base64 = "0.21"
|
||||
client-api = { workspace = true }
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub use client_api::entity::{AFWorkspaceSettings, AFWorkspaceSettingsChange};
|
||||
use collab_entity::{CollabObject, CollabType};
|
||||
use flowy_error::{internal_error, ErrorCode, FlowyError};
|
||||
use lib_infra::box_any::BoxAny;
|
||||
@ -61,6 +62,7 @@ pub trait UserCloudServiceProvider: Send + Sync {
|
||||
/// # Returns
|
||||
/// A `Result` which is `Ok` if the token is successfully set, or a `FlowyError` otherwise.
|
||||
fn set_token(&self, token: &str) -> Result<(), FlowyError>;
|
||||
fn set_ai_model(&self, ai_model: &str) -> Result<(), FlowyError>;
|
||||
|
||||
/// Subscribes to the state of the authentication token.
|
||||
///
|
||||
@ -294,6 +296,21 @@ pub trait UserCloudService: Send + Sync + 'static {
|
||||
fn get_billing_portal_url(&self) -> FutureResult<String, FlowyError> {
|
||||
FutureResult::new(async { Err(FlowyError::not_support()) })
|
||||
}
|
||||
|
||||
fn get_workspace_setting(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
) -> FutureResult<AFWorkspaceSettings, FlowyError> {
|
||||
FutureResult::new(async { Err(FlowyError::not_support()) })
|
||||
}
|
||||
|
||||
fn update_workspace_setting(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
workspace_settings: AFWorkspaceSettingsChange,
|
||||
) -> FutureResult<AFWorkspaceSettings, FlowyError> {
|
||||
FutureResult::new(async { Err(FlowyError::not_support()) })
|
||||
}
|
||||
}
|
||||
|
||||
pub type UserUpdateReceiver = tokio::sync::mpsc::Receiver<UserUpdate>;
|
||||
|
@ -171,6 +171,7 @@ pub struct UserProfile {
|
||||
// If the encryption_sign is not empty, which means the user has enabled the encryption.
|
||||
pub encryption_type: EncryptionType,
|
||||
pub updated_at: i64,
|
||||
pub ai_model: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
|
||||
@ -249,6 +250,7 @@ where
|
||||
encryption_type: value.encryption_type(),
|
||||
stability_ai_key,
|
||||
updated_at: value.updated_at(),
|
||||
ai_model: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -264,6 +266,7 @@ pub struct UpdateUserProfileParams {
|
||||
pub stability_ai_key: Option<String>,
|
||||
pub encryption_sign: Option<String>,
|
||||
pub token: Option<String>,
|
||||
pub ai_model: Option<String>,
|
||||
}
|
||||
|
||||
impl UpdateUserProfileParams {
|
||||
@ -318,6 +321,11 @@ impl UpdateUserProfileParams {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ai_model(mut self, ai_model: &str) -> Self {
|
||||
self.ai_model = Some(ai_model.to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.name.is_none()
|
||||
&& self.email.is_none()
|
||||
|
@ -1,11 +1,12 @@
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
use validator::Validate;
|
||||
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_user_pub::entities::*;
|
||||
|
||||
use crate::entities::parser::{UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword};
|
||||
use crate::entities::AuthenticatorPB;
|
||||
use crate::entities::{AIModelPB, AuthenticatorPB};
|
||||
use crate::errors::ErrorCode;
|
||||
|
||||
use super::parser::UserStabilityAIKey;
|
||||
@ -56,6 +57,9 @@ pub struct UserProfilePB {
|
||||
|
||||
#[pb(index = 11)]
|
||||
pub stability_ai_key: String,
|
||||
|
||||
#[pb(index = 12)]
|
||||
pub ai_model: AIModelPB,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)]
|
||||
@ -88,6 +92,7 @@ impl From<UserProfile> for UserProfilePB {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -199,6 +204,7 @@ impl TryInto<UpdateUserProfileParams> for UpdateUserProfilePayloadPB {
|
||||
encryption_sign: None,
|
||||
token: None,
|
||||
stability_ai_key,
|
||||
ai_model: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
use std::str::FromStr;
|
||||
use validator::Validate;
|
||||
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_user_pub::cloud::{AFWorkspaceSettings, AFWorkspaceSettingsChange};
|
||||
use flowy_user_pub::entities::{
|
||||
RecurringInterval, Role, SubscriptionPlan, WorkspaceInvitation, WorkspaceMember,
|
||||
WorkspaceSubscription,
|
||||
@ -344,3 +346,86 @@ pub struct BillingPortalPB {
|
||||
#[pb(index = 1)]
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default, Clone, Validate)]
|
||||
pub struct UseAISettingPB {
|
||||
#[pb(index = 1)]
|
||||
pub disable_search_indexing: bool,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub ai_model: AIModelPB,
|
||||
}
|
||||
|
||||
impl From<AFWorkspaceSettings> for UseAISettingPB {
|
||||
fn from(value: AFWorkspaceSettings) -> Self {
|
||||
Self {
|
||||
disable_search_indexing: value.disable_search_indexing,
|
||||
ai_model: AIModelPB::from_str(&value.ai_model).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default, Clone, Validate)]
|
||||
pub struct UpdateUserWorkspaceSettingPB {
|
||||
#[pb(index = 1)]
|
||||
#[validate(custom = "required_not_empty_str")]
|
||||
pub workspace_id: String,
|
||||
|
||||
#[pb(index = 2, one_of)]
|
||||
pub disable_search_indexing: Option<bool>,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub ai_model: Option<AIModelPB>,
|
||||
}
|
||||
|
||||
impl From<UpdateUserWorkspaceSettingPB> for AFWorkspaceSettingsChange {
|
||||
fn from(value: UpdateUserWorkspaceSettingPB) -> Self {
|
||||
let mut change = AFWorkspaceSettingsChange::new();
|
||||
if let Some(disable_search_indexing) = value.disable_search_indexing {
|
||||
change = change.disable_search_indexing(disable_search_indexing);
|
||||
}
|
||||
if let Some(ai_model) = value.ai_model {
|
||||
change = change.ai_model(ai_model.to_str().to_string());
|
||||
}
|
||||
change
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf_Enum, Debug, Clone, Eq, PartialEq, Default)]
|
||||
pub enum AIModelPB {
|
||||
#[default]
|
||||
DefaultModel = 0,
|
||||
GPT35 = 1,
|
||||
GPT4o = 2,
|
||||
Claude3Sonnet = 3,
|
||||
Claude3Opus = 4,
|
||||
LocalAIModel = 5,
|
||||
}
|
||||
|
||||
impl AIModelPB {
|
||||
pub fn to_str(&self) -> &str {
|
||||
match self {
|
||||
AIModelPB::DefaultModel => "default-model",
|
||||
AIModelPB::GPT35 => "gpt-3.5-turbo",
|
||||
AIModelPB::GPT4o => "gpt-4o",
|
||||
AIModelPB::Claude3Sonnet => "claude-3-sonnet",
|
||||
AIModelPB::Claude3Opus => "claude-3-opus",
|
||||
AIModelPB::LocalAIModel => "local",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for AIModelPB {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"gpt-3.5-turbo" => Ok(AIModelPB::GPT35),
|
||||
"gpt-4o" => Ok(AIModelPB::GPT4o),
|
||||
"claude-3-sonnet" => Ok(AIModelPB::Claude3Sonnet),
|
||||
"claude-3-opus" => Ok(AIModelPB::Claude3Opus),
|
||||
"local" => Ok(AIModelPB::LocalAIModel),
|
||||
_ => Ok(AIModelPB::DefaultModel),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -832,3 +832,25 @@ pub async fn get_workspace_member_info(
|
||||
let member = manager.get_workspace_member_info(param.uid).await?;
|
||||
data_result_ok(member.into())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "info", skip_all, err)]
|
||||
pub async fn update_workspace_setting(
|
||||
params: AFPluginData<UpdateUserWorkspaceSettingPB>,
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
) -> Result<(), FlowyError> {
|
||||
let params = params.try_into_inner()?;
|
||||
let manager = upgrade_manager(manager)?;
|
||||
manager.update_workspace_setting(params).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "info", skip_all, err)]
|
||||
pub async fn get_workspace_setting(
|
||||
params: AFPluginData<UserWorkspaceIdPB>,
|
||||
manager: AFPluginState<Weak<UserManager>>,
|
||||
) -> DataResult<UseAISettingPB, FlowyError> {
|
||||
let params = params.try_into_inner()?;
|
||||
let manager = upgrade_manager(manager)?;
|
||||
let pb = manager.get_workspace_settings(¶ms.workspace_id).await?;
|
||||
data_result_ok(pb)
|
||||
}
|
||||
|
@ -74,6 +74,9 @@ pub fn init(user_manager: Weak<UserManager>) -> AFPlugin {
|
||||
.event(UserEvent::CancelWorkspaceSubscription, cancel_workspace_subscription_handler)
|
||||
.event(UserEvent::GetWorkspaceUsage, get_workspace_usage_handler)
|
||||
.event(UserEvent::GetBillingPortal, get_billing_portal_handler)
|
||||
// Workspace Setting
|
||||
.event(UserEvent::UpdateWorkspaceSetting, update_workspace_setting)
|
||||
.event(UserEvent::GetWorkspaceSetting, get_workspace_setting)
|
||||
|
||||
}
|
||||
|
||||
@ -253,6 +256,12 @@ pub enum UserEvent {
|
||||
|
||||
#[event(input = "WorkspaceMemberIdPB", output = "WorkspaceMemberPB")]
|
||||
GetMemberInfo = 56,
|
||||
|
||||
#[event(input = "UpdateUserWorkspaceSettingPB")]
|
||||
UpdateWorkspaceSetting = 57,
|
||||
|
||||
#[event(input = "UserWorkspaceIdPB", output = "UseAISettingPB")]
|
||||
GetWorkspaceSetting = 58,
|
||||
}
|
||||
|
||||
pub trait UserStatusCallback: Send + Sync + 'static {
|
||||
|
@ -14,6 +14,7 @@ pub(crate) enum UserNotification {
|
||||
DidUpdateUserWorkspaces = 3,
|
||||
DidUpdateCloudConfig = 4,
|
||||
DidUpdateUserWorkspace = 5,
|
||||
DidUpdateAISetting = 6,
|
||||
}
|
||||
|
||||
impl std::convert::From<UserNotification> for i32 {
|
||||
|
@ -24,6 +24,7 @@ pub struct UserTable {
|
||||
pub(crate) encryption_type: String,
|
||||
pub(crate) stability_ai_key: String,
|
||||
pub(crate) updated_at: i64,
|
||||
pub(crate) ai_model: String,
|
||||
}
|
||||
|
||||
impl UserTable {
|
||||
@ -49,6 +50,7 @@ impl From<(UserProfile, Authenticator)> for UserTable {
|
||||
encryption_type,
|
||||
stability_ai_key: user_profile.stability_ai_key,
|
||||
updated_at: user_profile.updated_at,
|
||||
ai_model: user_profile.ai_model,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -67,6 +69,7 @@ impl From<UserTable> for UserProfile {
|
||||
encryption_type: EncryptionType::from_str(&table.encryption_type).unwrap_or_default(),
|
||||
stability_ai_key: table.stability_ai_key,
|
||||
updated_at: table.updated_at,
|
||||
ai_model: table.ai_model,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,6 +86,7 @@ pub struct UserTableChangeset {
|
||||
pub encryption_type: Option<String>,
|
||||
pub token: Option<String>,
|
||||
pub stability_ai_key: Option<String>,
|
||||
pub ai_model: Option<String>,
|
||||
}
|
||||
|
||||
impl UserTableChangeset {
|
||||
@ -101,6 +105,7 @@ impl UserTableChangeset {
|
||||
encryption_type,
|
||||
token: params.token,
|
||||
stability_ai_key: params.stability_ai_key,
|
||||
ai_model: params.ai_model,
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,6 +121,7 @@ impl UserTableChangeset {
|
||||
encryption_type: Some(encryption_type),
|
||||
token: Some(user_profile.token),
|
||||
stability_ai_key: Some(user_profile.stability_ai_key),
|
||||
ai_model: Some(user_profile.ai_model),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +169,10 @@ impl UserManager {
|
||||
error!("Set token failed: {}", err);
|
||||
}
|
||||
|
||||
if let Err(err) = self.cloud_services.set_ai_model(&user.ai_model) {
|
||||
error!("Set ai model failed: {}", err);
|
||||
}
|
||||
|
||||
// Subscribe the token state
|
||||
let weak_cloud_services = Arc::downgrade(&self.cloud_services);
|
||||
let weak_authenticate_user = Arc::downgrade(&self.authenticate_user);
|
||||
@ -804,7 +808,7 @@ fn current_authenticator() -> Authenticator {
|
||||
}
|
||||
}
|
||||
|
||||
fn upsert_user_profile_change(
|
||||
pub fn upsert_user_profile_change(
|
||||
uid: i64,
|
||||
mut conn: DBConnection,
|
||||
changeset: UserTableChangeset,
|
||||
|
@ -11,13 +11,14 @@ use flowy_folder_pub::entities::{AppFlowyData, ImportData};
|
||||
use flowy_sqlite::schema::user_workspace_table;
|
||||
use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods};
|
||||
use flowy_user_pub::entities::{
|
||||
Role, UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus, WorkspaceMember,
|
||||
WorkspaceSubscription, WorkspaceUsage,
|
||||
Role, UpdateUserProfileParams, UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus,
|
||||
WorkspaceMember, WorkspaceSubscription, WorkspaceUsage,
|
||||
};
|
||||
use lib_dispatch::prelude::af_spawn;
|
||||
|
||||
use crate::entities::{
|
||||
RepeatedUserWorkspacePB, ResetWorkspacePB, SubscribeWorkspacePB, UserWorkspacePB,
|
||||
RepeatedUserWorkspacePB, ResetWorkspacePB, SubscribeWorkspacePB, UpdateUserWorkspaceSettingPB,
|
||||
UseAISettingPB, UserWorkspacePB,
|
||||
};
|
||||
use crate::migrations::AnonUser;
|
||||
use crate::notification::{send_notification, UserNotification};
|
||||
@ -27,10 +28,11 @@ use crate::services::data_import::{
|
||||
use crate::services::sqlite_sql::member_sql::{
|
||||
select_workspace_member, upsert_workspace_member, WorkspaceMemberTable,
|
||||
};
|
||||
use crate::services::sqlite_sql::user_sql::UserTableChangeset;
|
||||
use crate::services::sqlite_sql::workspace_sql::{
|
||||
get_all_user_workspace_op, get_user_workspace_op, insert_new_workspaces_op, UserWorkspaceTable,
|
||||
};
|
||||
use crate::user_manager::UserManager;
|
||||
use crate::user_manager::{upsert_user_profile_change, UserManager};
|
||||
use flowy_user_pub::session::Session;
|
||||
|
||||
impl UserManager {
|
||||
@ -483,6 +485,49 @@ impl UserManager {
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
pub async fn update_workspace_setting(
|
||||
&self,
|
||||
updated_settings: UpdateUserWorkspaceSettingPB,
|
||||
) -> FlowyResult<()> {
|
||||
let ai_model = updated_settings
|
||||
.ai_model
|
||||
.as_ref()
|
||||
.map(|model| model.to_str().to_string());
|
||||
let workspace_id = updated_settings.workspace_id.clone();
|
||||
let cloud_service = self.cloud_services.get_user_service()?;
|
||||
let settings = cloud_service
|
||||
.update_workspace_setting(&workspace_id, updated_settings.into())
|
||||
.await?;
|
||||
|
||||
let pb = UseAISettingPB::from(settings);
|
||||
let uid = self.user_id()?;
|
||||
send_notification(&uid.to_string(), UserNotification::DidUpdateAISetting)
|
||||
.payload(pb)
|
||||
.send();
|
||||
|
||||
if let Some(ai_model) = ai_model {
|
||||
if let Err(err) = self.cloud_services.set_ai_model(&ai_model) {
|
||||
error!("Set ai model failed: {}", err);
|
||||
}
|
||||
|
||||
let conn = self.db_connection(uid)?;
|
||||
let params = UpdateUserProfileParams::new(uid).with_ai_model(&ai_model);
|
||||
upsert_user_profile_change(uid, conn, UserTableChangeset::new(params))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_workspace_settings(&self, workspace_id: &str) -> FlowyResult<UseAISettingPB> {
|
||||
let cloud_service = self.cloud_services.get_user_service()?;
|
||||
let settings = cloud_service.get_workspace_setting(workspace_id).await?;
|
||||
|
||||
let uid = self.user_id()?;
|
||||
let conn = self.db_connection(uid)?;
|
||||
let params = UpdateUserProfileParams::new(uid).with_ai_model(&settings.ai_model);
|
||||
upsert_user_profile_change(uid, conn, UserTableChangeset::new(params))?;
|
||||
Ok(UseAISettingPB::from(settings))
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self), err)]
|
||||
pub async fn get_workspace_member_info(&self, uid: i64) -> FlowyResult<WorkspaceMember> {
|
||||
let workspace_id = self.get_session()?.user_workspace.id.clone();
|
||||
|
Loading…
Reference in New Issue
Block a user