mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-09-11 19:17:43 +03: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);
|
await tester.ime.insertText(inputContent);
|
||||||
expect(find.text(inputContent, findRichText: true), findsOneWidget);
|
expect(find.text(inputContent, findRichText: true), findsOneWidget);
|
||||||
|
|
||||||
// TODO(nathan): remove the await
|
|
||||||
// 6 seconds for data sync
|
// 6 seconds for data sync
|
||||||
await tester.waitForSeconds(6);
|
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/startup/startup.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.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 {
|
Future<void> mockOpenAIRepository() async {
|
||||||
await getIt.unregister<OpenAIRepository>();
|
await getIt.unregister<AIRepository>();
|
||||||
getIt.registerFactoryAsync<OpenAIRepository>(
|
getIt.registerFactoryAsync<AIRepository>(
|
||||||
() => Future.value(
|
() => Future.value(
|
||||||
MockOpenAIRepository(),
|
MockOpenAIRepository(),
|
||||||
),
|
),
|
||||||
|
@ -44,7 +44,7 @@ class MockOpenAIRepository extends HttpOpenAIRepository {
|
|||||||
required Future<void> Function() onStart,
|
required Future<void> Function() onStart,
|
||||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||||
required Future<void> Function() onEnd,
|
required Future<void> Function() onEnd,
|
||||||
required void Function(OpenAIError error) onError,
|
required void Function(AIError error) onError,
|
||||||
String? suffix,
|
String? suffix,
|
||||||
int maxTokens = 2048,
|
int maxTokens = 2048,
|
||||||
double temperature = 0.3,
|
double temperature = 0.3,
|
||||||
|
@ -90,7 +90,7 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
|
|||||||
UploadImageType.local,
|
UploadImageType.local,
|
||||||
UploadImageType.url,
|
UploadImageType.url,
|
||||||
UploadImageType.unsplash,
|
UploadImageType.unsplash,
|
||||||
UploadImageType.openAI,
|
// UploadImageType.openAI,
|
||||||
UploadImageType.stabilityAI,
|
UploadImageType.stabilityAI,
|
||||||
],
|
],
|
||||||
onSelectedLocalImage: (path) {
|
onSelectedLocalImage: (path) {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.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/service/error.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/startup/startup.dart';
|
||||||
import 'package:appflowy_result/appflowy_result.dart';
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -22,7 +22,7 @@ class OpenAIImageWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _OpenAIImageWidgetState extends State<OpenAIImageWidget> {
|
class _OpenAIImageWidgetState extends State<OpenAIImageWidget> {
|
||||||
Future<FlowyResult<List<String>, OpenAIError>>? future;
|
Future<FlowyResult<List<String>, AIError>>? future;
|
||||||
String query = '';
|
String query = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -93,7 +93,7 @@ class _OpenAIImageWidgetState extends State<OpenAIImageWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _search() async {
|
void _search() async {
|
||||||
final openAI = await getIt.getAsync<OpenAIRepository>();
|
final openAI = await getIt.getAsync<AIRepository>();
|
||||||
setState(() {
|
setState(() {
|
||||||
future = openAI.generateImage(
|
future = openAI.generateImage(
|
||||||
prompt: query,
|
prompt: query,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
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/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/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/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/unsplash_image_widget.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_file_widget.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_file_widget.dart';
|
||||||
@ -19,7 +18,7 @@ enum UploadImageType {
|
|||||||
url,
|
url,
|
||||||
unsplash,
|
unsplash,
|
||||||
stabilityAI,
|
stabilityAI,
|
||||||
openAI,
|
// openAI,
|
||||||
color;
|
color;
|
||||||
|
|
||||||
String get description {
|
String get description {
|
||||||
@ -30,8 +29,8 @@ enum UploadImageType {
|
|||||||
return LocaleKeys.document_imageBlock_embedLink_label.tr();
|
return LocaleKeys.document_imageBlock_embedLink_label.tr();
|
||||||
case UploadImageType.unsplash:
|
case UploadImageType.unsplash:
|
||||||
return LocaleKeys.document_imageBlock_unsplash_label.tr();
|
return LocaleKeys.document_imageBlock_unsplash_label.tr();
|
||||||
case UploadImageType.openAI:
|
// case UploadImageType.openAI:
|
||||||
return LocaleKeys.document_imageBlock_ai_label.tr();
|
// return LocaleKeys.document_imageBlock_ai_label.tr();
|
||||||
case UploadImageType.stabilityAI:
|
case UploadImageType.stabilityAI:
|
||||||
return LocaleKeys.document_imageBlock_stability_ai_label.tr();
|
return LocaleKeys.document_imageBlock_stability_ai_label.tr();
|
||||||
case UploadImageType.color:
|
case UploadImageType.color:
|
||||||
@ -186,23 +185,23 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
case UploadImageType.openAI:
|
// case UploadImageType.openAI:
|
||||||
return supportOpenAI
|
// return supportOpenAI
|
||||||
? Expanded(
|
// ? Expanded(
|
||||||
child: Container(
|
// child: Container(
|
||||||
padding: const EdgeInsets.all(8.0),
|
// padding: const EdgeInsets.all(8.0),
|
||||||
constraints: constraints,
|
// constraints: constraints,
|
||||||
child: OpenAIImageWidget(
|
// child: OpenAIImageWidget(
|
||||||
onSelectNetworkImage: widget.onSelectedAIImage,
|
// onSelectNetworkImage: widget.onSelectedAIImage,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
)
|
// )
|
||||||
: Padding(
|
// : Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
// padding: const EdgeInsets.all(8.0),
|
||||||
child: FlowyText(
|
// child: FlowyText(
|
||||||
LocaleKeys.document_imageBlock_pleaseInputYourOpenAIKey.tr(),
|
// LocaleKeys.document_imageBlock_pleaseInputYourOpenAIKey.tr(),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
case UploadImageType.stabilityAI:
|
case UploadImageType.stabilityAI:
|
||||||
return supportStabilityAI
|
return supportStabilityAI
|
||||||
? Expanded(
|
? 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';
|
part 'error.g.dart';
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class OpenAIError with _$OpenAIError {
|
class AIError with _$AIError {
|
||||||
const factory OpenAIError({
|
const factory AIError({
|
||||||
String? code,
|
String? code,
|
||||||
required String message,
|
required String message,
|
||||||
}) = _OpenAIError;
|
}) = _AIError;
|
||||||
|
|
||||||
factory OpenAIError.fromJson(Map<String, Object?> json) =>
|
factory AIError.fromJson(Map<String, Object?> json) =>
|
||||||
_$OpenAIErrorFromJson(json);
|
_$AIErrorFromJson(json);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
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:appflowy_result/appflowy_result.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
@ -25,58 +26,7 @@ enum OpenAIRequestType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class OpenAIRepository {
|
class HttpOpenAIRepository implements AIRepository {
|
||||||
/// 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 {
|
|
||||||
const HttpOpenAIRepository({
|
const HttpOpenAIRepository({
|
||||||
required this.client,
|
required this.client,
|
||||||
required this.apiKey,
|
required this.apiKey,
|
||||||
@ -90,50 +40,13 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
|||||||
'Content-Type': 'application/json',
|
'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
|
@override
|
||||||
Future<void> getStreamedCompletions({
|
Future<void> getStreamedCompletions({
|
||||||
required String prompt,
|
required String prompt,
|
||||||
required Future<void> Function() onStart,
|
required Future<void> Function() onStart,
|
||||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||||
required Future<void> Function() onEnd,
|
required Future<void> Function() onEnd,
|
||||||
required void Function(OpenAIError error) onError,
|
required void Function(AIError error) onError,
|
||||||
String? suffix,
|
String? suffix,
|
||||||
int maxTokens = 2048,
|
int maxTokens = 2048,
|
||||||
double temperature = 0.3,
|
double temperature = 0.3,
|
||||||
@ -201,50 +114,14 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
|||||||
} else {
|
} else {
|
||||||
final body = await response.stream.bytesToString();
|
final body = await response.stream.bytesToString();
|
||||||
onError(
|
onError(
|
||||||
OpenAIError.fromJson(json.decode(body)['error']),
|
AIError.fromJson(json.decode(body)['error']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<FlowyResult<TextEditResponse, OpenAIError>> getEdits({
|
Future<FlowyResult<List<String>, AIError>> generateImage({
|
||||||
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({
|
|
||||||
required String prompt,
|
required String prompt,
|
||||||
int n = 1,
|
int n = 1,
|
||||||
}) async {
|
}) async {
|
||||||
@ -273,11 +150,23 @@ class HttpOpenAIRepository implements OpenAIRepository {
|
|||||||
return FlowyResult.success(urls);
|
return FlowyResult.success(urls);
|
||||||
} else {
|
} else {
|
||||||
return FlowyResult.failure(
|
return FlowyResult.failure(
|
||||||
OpenAIError.fromJson(json.decode(response.body)['error']),
|
AIError.fromJson(json.decode(response.body)['error']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (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/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/build_context_extension.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/text_robot.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/discard_dialog.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.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/user/application/user_service.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/toast.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:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:easy_localization/easy_localization.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.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text_field.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/primary_button.dart';
|
||||||
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
|
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class AutoCompletionBlockKeys {
|
class AutoCompletionBlockKeys {
|
||||||
@ -187,15 +185,11 @@ class _AutoCompletionBlockComponentState
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onGenerate() async {
|
Future<void> _onGenerate() async {
|
||||||
final loading = Loading(context);
|
|
||||||
loading.start();
|
|
||||||
|
|
||||||
await _updateEditingText();
|
await _updateEditingText();
|
||||||
|
|
||||||
final userProfile = await UserBackendService.getCurrentUserProfile()
|
final userProfile = await UserBackendService.getCurrentUserProfile()
|
||||||
.then((value) => value.toNullable());
|
.then((value) => value.toNullable());
|
||||||
if (userProfile == null) {
|
if (userProfile == null) {
|
||||||
await loading.stop();
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
showSnackBarMessage(
|
showSnackBarMessage(
|
||||||
context,
|
context,
|
||||||
@ -208,34 +202,28 @@ class _AutoCompletionBlockComponentState
|
|||||||
|
|
||||||
final textRobot = TextRobot(editorState: editorState);
|
final textRobot = TextRobot(editorState: editorState);
|
||||||
BarrierDialog? barrierDialog;
|
BarrierDialog? barrierDialog;
|
||||||
final openAIRepository = HttpOpenAIRepository(
|
final aiRepository = AppFlowyAIService();
|
||||||
client: http.Client(),
|
await aiRepository.streamCompletion(
|
||||||
apiKey: userProfile.openaiKey,
|
text: controller.text,
|
||||||
);
|
completionType: CompletionTypePB.ContinueWriting,
|
||||||
await openAIRepository.getStreamedCompletions(
|
|
||||||
prompt: controller.text,
|
|
||||||
onStart: () async {
|
onStart: () async {
|
||||||
await loading.stop();
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
barrierDialog = BarrierDialog(context);
|
barrierDialog = BarrierDialog(context);
|
||||||
barrierDialog?.show();
|
barrierDialog?.show();
|
||||||
await _makeSurePreviousNodeIsEmptyParagraphNode();
|
await _makeSurePreviousNodeIsEmptyParagraphNode();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onProcess: (response) async {
|
onProcess: (text) async {
|
||||||
if (response.choices.isNotEmpty) {
|
|
||||||
final text = response.choices.first.text;
|
|
||||||
await textRobot.autoInsertText(
|
await textRobot.autoInsertText(
|
||||||
text,
|
text,
|
||||||
delay: Duration.zero,
|
delay: Duration.zero,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onEnd: () async {
|
onEnd: () async {
|
||||||
await barrierDialog?.dismiss();
|
barrierDialog?.dismiss();
|
||||||
},
|
},
|
||||||
onError: (error) async {
|
onError: (error) async {
|
||||||
await loading.stop();
|
barrierDialog?.dismiss();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
showSnackBarMessage(
|
showSnackBarMessage(
|
||||||
context,
|
context,
|
||||||
@ -272,8 +260,6 @@ class _AutoCompletionBlockComponentState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final loading = Loading(context);
|
|
||||||
loading.start();
|
|
||||||
// clear previous response
|
// clear previous response
|
||||||
final selection = startSelection;
|
final selection = startSelection;
|
||||||
if (selection != null) {
|
if (selection != null) {
|
||||||
@ -292,7 +278,6 @@ class _AutoCompletionBlockComponentState
|
|||||||
final userProfile = await UserBackendService.getCurrentUserProfile()
|
final userProfile = await UserBackendService.getCurrentUserProfile()
|
||||||
.then((value) => value.toNullable());
|
.then((value) => value.toNullable());
|
||||||
if (userProfile == null) {
|
if (userProfile == null) {
|
||||||
await loading.stop();
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
showSnackBarMessage(
|
showSnackBarMessage(
|
||||||
context,
|
context,
|
||||||
@ -303,28 +288,21 @@ class _AutoCompletionBlockComponentState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final textRobot = TextRobot(editorState: editorState);
|
final textRobot = TextRobot(editorState: editorState);
|
||||||
final openAIRepository = HttpOpenAIRepository(
|
final aiResposity = AppFlowyAIService();
|
||||||
client: http.Client(),
|
await aiResposity.streamCompletion(
|
||||||
apiKey: userProfile.openaiKey,
|
text: _rewritePrompt(previousOutput),
|
||||||
);
|
completionType: CompletionTypePB.ContinueWriting,
|
||||||
await openAIRepository.getStreamedCompletions(
|
|
||||||
prompt: _rewritePrompt(previousOutput),
|
|
||||||
onStart: () async {
|
onStart: () async {
|
||||||
await loading.stop();
|
|
||||||
await _makeSurePreviousNodeIsEmptyParagraphNode();
|
await _makeSurePreviousNodeIsEmptyParagraphNode();
|
||||||
},
|
},
|
||||||
onProcess: (response) async {
|
onProcess: (text) async {
|
||||||
if (response.choices.isNotEmpty) {
|
|
||||||
final text = response.choices.first.text;
|
|
||||||
await textRobot.autoInsertText(
|
await textRobot.autoInsertText(
|
||||||
text,
|
text,
|
||||||
delay: Duration.zero,
|
delay: Duration.zero,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onEnd: () async {},
|
onEnd: () async {},
|
||||||
onError: (error) async {
|
onError: (error) async {
|
||||||
await loading.stop();
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
showSnackBarMessage(
|
showSnackBarMessage(
|
||||||
context,
|
context,
|
||||||
@ -462,23 +440,9 @@ class AutoCompletionHeader extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return FlowyText.medium(
|
||||||
children: [
|
|
||||||
FlowyText.medium(
|
|
||||||
LocaleKeys.document_plugins_autoGeneratorTitleName.tr(),
|
LocaleKeys.document_plugins_autoGeneratorTitleName.tr(),
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
FlowyButton(
|
|
||||||
useIntrinsicWidth: true,
|
|
||||||
text: FlowyText.regular(
|
|
||||||
LocaleKeys.document_plugins_autoGeneratorLearnMore.tr(),
|
|
||||||
),
|
|
||||||
onTap: () async {
|
|
||||||
await openLearnMorePage();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class Loading {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> stop() async {
|
void stop() {
|
||||||
if (loadingContext != null) {
|
if (loadingContext != null) {
|
||||||
Navigator.of(loadingContext!).pop();
|
Navigator.of(loadingContext!).pop();
|
||||||
loadingContext = null;
|
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 'dart:async';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/util/learn_more_action.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/discard_dialog.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_action.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_action.dart';
|
||||||
import 'package:appflowy/startup/startup.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/workspace/presentation/home/toast.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.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/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/decoration.dart';
|
import 'package:flowy_infra_ui/style_widget/decoration.dart';
|
||||||
import 'package:flutter/material.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:http/http.dart' as http;
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@ -229,7 +229,11 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildHeaderWidget(context),
|
FlowyText.medium(
|
||||||
|
action.name,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
// _buildHeaderWidget(context),
|
||||||
const Space(0, 10),
|
const Space(0, 10),
|
||||||
_buildResultWidget(context),
|
_buildResultWidget(context),
|
||||||
const Space(0, 10),
|
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) {
|
Widget _buildResultWidget(BuildContext context) {
|
||||||
final loadingWidget = Padding(
|
final loadingWidget = Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
@ -423,34 +406,23 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
|
|||||||
result = "";
|
result = "";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
final openAIRepository = await getIt.getAsync<OpenAIRepository>();
|
final aiResitory = await getIt.getAsync<AIRepository>();
|
||||||
|
await aiResitory.streamCompletion(
|
||||||
var lines = content.split('\n\n');
|
text: content,
|
||||||
if (action == SmartEditAction.summarize) {
|
completionType: completionTypeFromInt(action),
|
||||||
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 {
|
onStart: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
loading = false;
|
loading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onProcess: (response) async {
|
onProcess: (text) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (response.choices.first.text != '\n') {
|
result += text;
|
||||||
result += response.choices.first.text;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onEnd: () async {
|
onEnd: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (i != lines.length - 1) {
|
|
||||||
result += '\n';
|
result += '\n';
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) async {
|
onError: (error) async {
|
||||||
@ -463,5 +435,4 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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/user/application/user_service.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.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_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -32,7 +33,7 @@ class SmartEditActionList extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SmartEditActionListState extends State<SmartEditActionList> {
|
class _SmartEditActionListState extends State<SmartEditActionList> {
|
||||||
bool isOpenAIEnabled = false;
|
bool isAIEnabled = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -40,8 +41,9 @@ class _SmartEditActionListState extends State<SmartEditActionList> {
|
|||||||
|
|
||||||
UserBackendService.getCurrentUserProfile().then((value) {
|
UserBackendService.getCurrentUserProfile().then((value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
isOpenAIEnabled = value.fold(
|
isAIEnabled = value.fold(
|
||||||
(s) => s.openaiKey.isNotEmpty,
|
(userProfile) =>
|
||||||
|
userProfile.authenticator == AuthenticatorPB.AppFlowyCloud,
|
||||||
(_) => false,
|
(_) => false,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -60,9 +62,9 @@ class _SmartEditActionListState extends State<SmartEditActionList> {
|
|||||||
keepEditorFocusNotifier.increase();
|
keepEditorFocusNotifier.increase();
|
||||||
return FlowyIconButton(
|
return FlowyIconButton(
|
||||||
hoverColor: Colors.transparent,
|
hoverColor: Colors.transparent,
|
||||||
tooltipText: isOpenAIEnabled
|
tooltipText: isAIEnabled
|
||||||
? LocaleKeys.document_plugins_smartEdit.tr()
|
? LocaleKeys.document_plugins_smartEdit.tr()
|
||||||
: LocaleKeys.document_plugins_smartEditDisabled.tr(),
|
: LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(),
|
||||||
preferBelow: false,
|
preferBelow: false,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.lightbulb_outline,
|
Icons.lightbulb_outline,
|
||||||
@ -70,12 +72,12 @@ class _SmartEditActionListState extends State<SmartEditActionList> {
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (isOpenAIEnabled) {
|
if (isAIEnabled) {
|
||||||
controller.show();
|
controller.show();
|
||||||
} else {
|
} else {
|
||||||
showSnackBarMessage(
|
showSnackBarMessage(
|
||||||
context,
|
context,
|
||||||
LocaleKeys.document_plugins_smartEditDisabled.tr(),
|
LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(),
|
||||||
showCancel: true,
|
showCancel: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,14 @@ import 'package:appflowy/core/network_monitor.dart';
|
|||||||
import 'package:appflowy/env/cloud_env.dart';
|
import 'package:appflowy/env/cloud_env.dart';
|
||||||
import 'package:appflowy/plugins/document/application/prelude.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/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/document/presentation/editor_plugins/stability_ai/stability_ai_client.dart';
|
||||||
import 'package:appflowy/plugins/trash/application/prelude.dart';
|
import 'package:appflowy/plugins/trash/application/prelude.dart';
|
||||||
import 'package:appflowy/shared/appflowy_cache_manager.dart';
|
import 'package:appflowy/shared/appflowy_cache_manager.dart';
|
||||||
import 'package:appflowy/shared/custom_image_cache_manager.dart';
|
import 'package:appflowy/shared/custom_image_cache_manager.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/startup/tasks/appflowy_cloud_task.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/af_cloud_auth_service.dart';
|
||||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||||
import 'package:appflowy/user/application/auth/supabase_auth_service.dart';
|
import 'package:appflowy/user/application/auth/supabase_auth_service.dart';
|
||||||
@ -85,15 +86,12 @@ void _resolveCommonService(
|
|||||||
() => mode.isTest ? MockApplicationDataStorage() : ApplicationDataStorage(),
|
() => mode.isTest ? MockApplicationDataStorage() : ApplicationDataStorage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
getIt.registerFactoryAsync<OpenAIRepository>(
|
getIt.registerFactoryAsync<AIRepository>(
|
||||||
() async {
|
() async {
|
||||||
final result = await UserBackendService.getCurrentUserProfile();
|
final result = await UserBackendService.getCurrentUserProfile();
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(s) {
|
(s) {
|
||||||
return HttpOpenAIRepository(
|
return AppFlowyAIService();
|
||||||
client: http.Client(),
|
|
||||||
apiKey: s.openaiKey,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
(e) {
|
(e) {
|
||||||
throw Exception('Failed to get user profile: ${e.msg}');
|
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:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'package:appflowy/core/notification/folder_notification.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-notification/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/notification.pb.dart'
|
import 'package:appflowy_backend/protobuf/flowy-user/notification.pb.dart'
|
||||||
as user;
|
as user;
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
|
||||||
import 'package:appflowy_backend/rust_stream.dart';
|
import 'package:appflowy_backend/rust_stream.dart';
|
||||||
import 'package:appflowy_result/appflowy_result.dart';
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
import 'package:flowy_infra/notifier.dart';
|
import 'package:flowy_infra/notifier.dart';
|
||||||
@ -23,6 +23,9 @@ typedef DidUpdateUserWorkspacesCallback = void Function(
|
|||||||
RepeatedUserWorkspacePB workspaces,
|
RepeatedUserWorkspacePB workspaces,
|
||||||
);
|
);
|
||||||
typedef UserProfileNotifyValue = FlowyResult<UserProfilePB, FlowyError>;
|
typedef UserProfileNotifyValue = FlowyResult<UserProfilePB, FlowyError>;
|
||||||
|
typedef DidUpdateUserWorkspaceSetting = void Function(
|
||||||
|
UseAISettingPB settings,
|
||||||
|
);
|
||||||
|
|
||||||
class UserListener {
|
class UserListener {
|
||||||
UserListener({
|
UserListener({
|
||||||
@ -37,28 +40,26 @@ class UserListener {
|
|||||||
|
|
||||||
/// Update notification about _all_ of the users workspaces
|
/// Update notification about _all_ of the users workspaces
|
||||||
///
|
///
|
||||||
DidUpdateUserWorkspacesCallback? didUpdateUserWorkspaces;
|
DidUpdateUserWorkspacesCallback? onUserWorkspaceListUpdated;
|
||||||
|
|
||||||
/// Update notification about _one_ workspace
|
/// Update notification about _one_ workspace
|
||||||
///
|
///
|
||||||
DidUpdateUserWorkspaceCallback? didUpdateUserWorkspace;
|
DidUpdateUserWorkspaceCallback? onUserWorkspaceUpdated;
|
||||||
|
DidUpdateUserWorkspaceSetting? onUserWorkspaceSettingUpdated;
|
||||||
|
|
||||||
void start({
|
void start({
|
||||||
void Function(UserProfileNotifyValue)? onProfileUpdated,
|
void Function(UserProfileNotifyValue)? onProfileUpdated,
|
||||||
void Function(RepeatedUserWorkspacePB)? didUpdateUserWorkspaces,
|
DidUpdateUserWorkspacesCallback? onUserWorkspaceListUpdated,
|
||||||
void Function(UserWorkspacePB)? didUpdateUserWorkspace,
|
void Function(UserWorkspacePB)? onUserWorkspaceUpdated,
|
||||||
|
DidUpdateUserWorkspaceSetting? onUserWorkspaceSettingUpdated,
|
||||||
}) {
|
}) {
|
||||||
if (onProfileUpdated != null) {
|
if (onProfileUpdated != null) {
|
||||||
_profileNotifier?.addPublishListener(onProfileUpdated);
|
_profileNotifier?.addPublishListener(onProfileUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (didUpdateUserWorkspaces != null) {
|
this.onUserWorkspaceListUpdated = onUserWorkspaceListUpdated;
|
||||||
this.didUpdateUserWorkspaces = didUpdateUserWorkspaces;
|
this.onUserWorkspaceUpdated = onUserWorkspaceUpdated;
|
||||||
}
|
this.onUserWorkspaceSettingUpdated = onUserWorkspaceSettingUpdated;
|
||||||
|
|
||||||
if (didUpdateUserWorkspace != null) {
|
|
||||||
this.didUpdateUserWorkspace = didUpdateUserWorkspace;
|
|
||||||
}
|
|
||||||
|
|
||||||
_userParser = UserNotificationParser(
|
_userParser = UserNotificationParser(
|
||||||
id: _userProfile.id.toString(),
|
id: _userProfile.id.toString(),
|
||||||
@ -92,13 +93,18 @@ class UserListener {
|
|||||||
result.map(
|
result.map(
|
||||||
(r) {
|
(r) {
|
||||||
final value = RepeatedUserWorkspacePB.fromBuffer(r);
|
final value = RepeatedUserWorkspacePB.fromBuffer(r);
|
||||||
didUpdateUserWorkspaces?.call(value);
|
onUserWorkspaceListUpdated?.call(value);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case user.UserNotification.DidUpdateUserWorkspace:
|
case user.UserNotification.DidUpdateUserWorkspace:
|
||||||
result.map(
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -110,8 +116,8 @@ class UserListener {
|
|||||||
typedef WorkspaceSettingNotifyValue
|
typedef WorkspaceSettingNotifyValue
|
||||||
= FlowyResult<WorkspaceSettingPB, FlowyError>;
|
= FlowyResult<WorkspaceSettingPB, FlowyError>;
|
||||||
|
|
||||||
class UserWorkspaceListener {
|
class FolderListener {
|
||||||
UserWorkspaceListener();
|
FolderListener();
|
||||||
|
|
||||||
final PublishNotifier<WorkspaceSettingNotifyValue> _settingChangedNotifier =
|
final PublishNotifier<WorkspaceSettingNotifyValue> _settingChangedNotifier =
|
||||||
PublishNotifier();
|
PublishNotifier();
|
||||||
|
@ -9,12 +9,12 @@ part 'home_bloc.freezed.dart';
|
|||||||
|
|
||||||
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||||
HomeBloc(WorkspaceSettingPB workspaceSetting)
|
HomeBloc(WorkspaceSettingPB workspaceSetting)
|
||||||
: _workspaceListener = UserWorkspaceListener(),
|
: _workspaceListener = FolderListener(),
|
||||||
super(HomeState.initial(workspaceSetting)) {
|
super(HomeState.initial(workspaceSetting)) {
|
||||||
_dispatch(workspaceSetting);
|
_dispatch(workspaceSetting);
|
||||||
}
|
}
|
||||||
|
|
||||||
final UserWorkspaceListener _workspaceListener;
|
final FolderListener _workspaceListener;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
|
@ -15,7 +15,7 @@ class HomeSettingBloc extends Bloc<HomeSettingEvent, HomeSettingState> {
|
|||||||
WorkspaceSettingPB workspaceSetting,
|
WorkspaceSettingPB workspaceSetting,
|
||||||
AppearanceSettingsCubit appearanceSettingsCubit,
|
AppearanceSettingsCubit appearanceSettingsCubit,
|
||||||
double screenWidthPx,
|
double screenWidthPx,
|
||||||
) : _listener = UserWorkspaceListener(),
|
) : _listener = FolderListener(),
|
||||||
_appearanceSettingsCubit = appearanceSettingsCubit,
|
_appearanceSettingsCubit = appearanceSettingsCubit,
|
||||||
super(
|
super(
|
||||||
HomeSettingState.initial(
|
HomeSettingState.initial(
|
||||||
@ -27,7 +27,7 @@ class HomeSettingBloc extends Bloc<HomeSettingEvent, HomeSettingState> {
|
|||||||
_dispatch();
|
_dispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
final UserWorkspaceListener _listener;
|
final FolderListener _listener;
|
||||||
final AppearanceSettingsCubit _appearanceSettingsCubit;
|
final AppearanceSettingsCubit _appearanceSettingsCubit;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -13,7 +13,7 @@ part 'menu_user_bloc.freezed.dart';
|
|||||||
class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
|
class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
|
||||||
MenuUserBloc(this.userProfile)
|
MenuUserBloc(this.userProfile)
|
||||||
: _userListener = UserListener(userProfile: userProfile),
|
: _userListener = UserListener(userProfile: userProfile),
|
||||||
_userWorkspaceListener = UserWorkspaceListener(),
|
_userWorkspaceListener = FolderListener(),
|
||||||
_userService = UserBackendService(userId: userProfile.id),
|
_userService = UserBackendService(userId: userProfile.id),
|
||||||
super(MenuUserState.initial(userProfile)) {
|
super(MenuUserState.initial(userProfile)) {
|
||||||
_dispatch();
|
_dispatch();
|
||||||
@ -21,7 +21,7 @@ class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
|
|||||||
|
|
||||||
final UserBackendService _userService;
|
final UserBackendService _userService;
|
||||||
final UserListener _userListener;
|
final UserListener _userListener;
|
||||||
final UserWorkspaceListener _userWorkspaceListener;
|
final FolderListener _userWorkspaceListener;
|
||||||
final UserProfilePB userProfile;
|
final UserProfilePB userProfile;
|
||||||
|
|
||||||
@override
|
@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,
|
account,
|
||||||
workspace,
|
workspace,
|
||||||
manageData,
|
manageData,
|
||||||
|
shortcuts,
|
||||||
|
ai,
|
||||||
plan,
|
plan,
|
||||||
billing,
|
billing,
|
||||||
// OLD
|
// OLD
|
||||||
notifications,
|
notifications,
|
||||||
cloud,
|
cloud,
|
||||||
shortcuts,
|
|
||||||
member,
|
member,
|
||||||
featureFlags,
|
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) {
|
updateUserEmail: (String email) {
|
||||||
_userService.updateUserProfile(email: email).then((result) {
|
_userService.updateUserProfile(email: email).then((result) {
|
||||||
result.fold(
|
result.fold(
|
||||||
@ -127,11 +109,6 @@ class SettingsUserEvent with _$SettingsUserEvent {
|
|||||||
const factory SettingsUserEvent.updateUserIcon({required String iconUrl}) =
|
const factory SettingsUserEvent.updateUserIcon({required String iconUrl}) =
|
||||||
_UpdateUserIcon;
|
_UpdateUserIcon;
|
||||||
const factory SettingsUserEvent.removeUserIcon() = _RemoveUserIcon;
|
const factory SettingsUserEvent.removeUserIcon() = _RemoveUserIcon;
|
||||||
const factory SettingsUserEvent.updateUserOpenAIKey(String openAIKey) =
|
|
||||||
_UpdateUserOpenaiKey;
|
|
||||||
const factory SettingsUserEvent.updateUserStabilityAIKey(
|
|
||||||
String stabilityAIKey,
|
|
||||||
) = _UpdateUserStabilityAIKey;
|
|
||||||
const factory SettingsUserEvent.didReceiveUserProfile(
|
const factory SettingsUserEvent.didReceiveUserProfile(
|
||||||
UserProfilePB newUserProfile,
|
UserProfilePB newUserProfile,
|
||||||
) = _DidReceiveUserProfile;
|
) = _DidReceiveUserProfile;
|
||||||
|
@ -29,9 +29,9 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
|||||||
await event.when(
|
await event.when(
|
||||||
initial: () async {
|
initial: () async {
|
||||||
_listener.start(
|
_listener.start(
|
||||||
didUpdateUserWorkspaces: (workspaces) =>
|
onUserWorkspaceListUpdated: (workspaces) =>
|
||||||
add(UserWorkspaceEvent.updateWorkspaces(workspaces)),
|
add(UserWorkspaceEvent.updateWorkspaces(workspaces)),
|
||||||
didUpdateUserWorkspace: (workspace) {
|
onUserWorkspaceUpdated: (workspace) {
|
||||||
// If currentWorkspace is updated, eg. Icon or Name, we should notify
|
// If currentWorkspace is updated, eg. Icon or Name, we should notify
|
||||||
// the UI to render the updated information.
|
// the UI to render the updated information.
|
||||||
final currentWorkspace = state.currentWorkspace;
|
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(
|
SettingsCategory(
|
||||||
title: LocaleKeys.settings_accountPage_login_title.tr(),
|
title: LocaleKeys.settings_accountPage_login_title.tr(),
|
||||||
children: [
|
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:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.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/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_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_billing_view.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_manage_data_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';
|
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/members/workspace_member_page.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu.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/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:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
@ -113,6 +113,12 @@ class SettingsDialog extends StatelessWidget {
|
|||||||
return SettingCloud(restartAppFlowy: () => restartApp());
|
return SettingCloud(restartAppFlowy: () => restartApp());
|
||||||
case SettingsPage.shortcuts:
|
case SettingsPage.shortcuts:
|
||||||
return const SettingsShortcutsView();
|
return const SettingsShortcutsView();
|
||||||
|
case SettingsPage.ai:
|
||||||
|
if (user.authenticator == AuthenticatorPB.AppFlowyCloud) {
|
||||||
|
return SettingsAIView(userProfile: user);
|
||||||
|
} else {
|
||||||
|
return const AIFeatureOnlySupportedWhenUsingAppFlowyCloud();
|
||||||
|
}
|
||||||
case SettingsPage.member:
|
case SettingsPage.member:
|
||||||
return WorkspaceMembersPage(userProfile: user);
|
return WorkspaceMembersPage(userProfile: user);
|
||||||
case SettingsPage.plan:
|
case SettingsPage.plan:
|
||||||
|
@ -41,8 +41,7 @@ class RestartButton extends StatelessWidget {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 42,
|
height: 42,
|
||||||
child: FlowyTextButton(
|
child: FlowyTextButton(
|
||||||
LocaleKeys.settings_manageDataPage_dataStorage_actions_change
|
LocaleKeys.settings_menu_restartApp.tr(),
|
||||||
.tr(),
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
radius: BorderRadius.circular(12),
|
radius: BorderRadius.circular(12),
|
||||||
|
@ -102,6 +102,16 @@ class SettingsMenu extends StatelessWidget {
|
|||||||
icon: const FlowySvg(FlowySvgs.settings_shortcuts_m),
|
icon: const FlowySvg(FlowySvgs.settings_shortcuts_m),
|
||||||
changeSelectedPage: changeSelectedPage,
|
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 &&
|
if (FeatureFlag.planBilling.isOn &&
|
||||||
userProfile.authenticator ==
|
userProfile.authenticator ==
|
||||||
AuthenticatorPB.AppFlowyCloud &&
|
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]]
|
[[package]]
|
||||||
name = "app-error"
|
name = "app-error"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -192,7 +192,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "appflowy-ai-client"
|
name = "appflowy-ai-client"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -772,7 +772,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api"
|
name = "client-api"
|
||||||
version = "0.2.0"
|
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 = [
|
dependencies = [
|
||||||
"again",
|
"again",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -787,6 +787,7 @@ dependencies = [
|
|||||||
"collab",
|
"collab",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
"collab-rt-protocol",
|
"collab-rt-protocol",
|
||||||
|
"futures",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"getrandom 0.2.10",
|
"getrandom 0.2.10",
|
||||||
@ -794,6 +795,8 @@ dependencies = [
|
|||||||
"infra",
|
"infra",
|
||||||
"mime",
|
"mime",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project",
|
||||||
"prost",
|
"prost",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"scraper 0.17.1",
|
"scraper 0.17.1",
|
||||||
@ -818,7 +821,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api-entity"
|
name = "client-api-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
@ -830,7 +833,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-websocket"
|
name = "client-websocket"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -1070,7 +1073,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-rt-entity"
|
name = "collab-rt-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -1095,7 +1098,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-rt-protocol"
|
name = "collab-rt-protocol"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1341,7 +1344,7 @@ dependencies = [
|
|||||||
"cssparser-macros",
|
"cssparser-macros",
|
||||||
"dtoa-short",
|
"dtoa-short",
|
||||||
"itoa 1.0.6",
|
"itoa 1.0.6",
|
||||||
"phf 0.8.0",
|
"phf 0.11.2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1452,10 +1455,11 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "database-entity"
|
name = "database-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
|
"appflowy-ai-client",
|
||||||
"bincode",
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
@ -2426,6 +2430,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
@ -2894,7 +2899,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue"
|
name = "gotrue"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -2911,7 +2916,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue-entity"
|
name = "gotrue-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -3343,7 +3348,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "infra"
|
name = "infra"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -4850,7 +4855,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"log",
|
"log",
|
||||||
"multimap",
|
"multimap",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -4871,7 +4876,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.47",
|
"syn 2.0.47",
|
||||||
@ -5835,7 +5840,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "shared-entity"
|
name = "shared-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
|
@ -52,7 +52,7 @@ collab-user = { version = "0.2" }
|
|||||||
# Run the script:
|
# Run the script:
|
||||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
# 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]
|
[dependencies]
|
||||||
serde_json.workspace = true
|
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]]
|
[[package]]
|
||||||
name = "app-error"
|
name = "app-error"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -235,7 +235,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "appflowy-ai-client"
|
name = "appflowy-ai-client"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -561,7 +561,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api"
|
name = "client-api"
|
||||||
version = "0.2.0"
|
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 = [
|
dependencies = [
|
||||||
"again",
|
"again",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -576,6 +576,7 @@ dependencies = [
|
|||||||
"collab",
|
"collab",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
"collab-rt-protocol",
|
"collab-rt-protocol",
|
||||||
|
"futures",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"getrandom 0.2.12",
|
"getrandom 0.2.12",
|
||||||
@ -583,6 +584,8 @@ dependencies = [
|
|||||||
"infra",
|
"infra",
|
||||||
"mime",
|
"mime",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project",
|
||||||
"prost",
|
"prost",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"scraper 0.17.1",
|
"scraper 0.17.1",
|
||||||
@ -607,7 +610,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api-entity"
|
name = "client-api-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
@ -619,7 +622,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-websocket"
|
name = "client-websocket"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -797,7 +800,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-rt-entity"
|
name = "collab-rt-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -822,7 +825,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-rt-protocol"
|
name = "collab-rt-protocol"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1036,10 +1039,11 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "database-entity"
|
name = "database-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
|
"appflowy-ai-client",
|
||||||
"bincode",
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
@ -1662,6 +1666,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
@ -1919,7 +1924,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue"
|
name = "gotrue"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -1936,7 +1941,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue-entity"
|
name = "gotrue-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -2237,7 +2242,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "infra"
|
name = "infra"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -3951,7 +3956,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "shared-entity"
|
name = "shared-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"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-codegen = { path = "../../rust-lib/build-tool/flowy-codegen" }
|
||||||
flowy-document = { path = "../../rust-lib/flowy-document" }
|
flowy-document = { path = "../../rust-lib/flowy-document" }
|
||||||
flowy-folder = { path = "../../rust-lib/flowy-folder" }
|
flowy-folder = { path = "../../rust-lib/flowy-folder" }
|
||||||
|
flowy-storage = { path = "../../rust-lib/flowy-storage" }
|
||||||
lib-infra = { path = "../../rust-lib/lib-infra" }
|
lib-infra = { path = "../../rust-lib/lib-infra" }
|
||||||
bytes = { version = "1.5" }
|
bytes = { version = "1.5" }
|
||||||
protobuf = { version = "2.28.0" }
|
protobuf = { version = "2.28.0" }
|
||||||
@ -54,7 +55,7 @@ yrs = "0.18.8"
|
|||||||
# Run the script:
|
# Run the script:
|
||||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
# 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]
|
[profile.dev]
|
||||||
opt-level = 0
|
opt-level = 0
|
||||||
|
@ -33,6 +33,9 @@ pub struct UserProfilePB {
|
|||||||
|
|
||||||
#[pb(index = 10)]
|
#[pb(index = 10)]
|
||||||
pub stability_ai_key: String,
|
pub stability_ai_key: String,
|
||||||
|
|
||||||
|
#[pb(index = 11)]
|
||||||
|
pub ai_model: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<UserProfile> for UserProfilePB {
|
impl From<UserProfile> for UserProfilePB {
|
||||||
@ -52,6 +55,7 @@ impl From<UserProfile> for UserProfilePB {
|
|||||||
authenticator: user_profile.authenticator.into(),
|
authenticator: user_profile.authenticator.into(),
|
||||||
workspace_id: user_profile.workspace_id,
|
workspace_id: user_profile.workspace_id,
|
||||||
stability_ai_key: user_profile.stability_ai_key,
|
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 encryption_sign: ::std::string::String,
|
||||||
pub workspace_id: ::std::string::String,
|
pub workspace_id: ::std::string::String,
|
||||||
pub stability_ai_key: ::std::string::String,
|
pub stability_ai_key: ::std::string::String,
|
||||||
|
pub ai_model: ::std::string::String,
|
||||||
// special fields
|
// special fields
|
||||||
pub unknown_fields: ::protobuf::UnknownFields,
|
pub unknown_fields: ::protobuf::UnknownFields,
|
||||||
pub cached_size: ::protobuf::CachedSize,
|
pub cached_size: ::protobuf::CachedSize,
|
||||||
@ -289,6 +290,32 @@ impl UserProfilePB {
|
|||||||
pub fn take_stability_ai_key(&mut self) -> ::std::string::String {
|
pub fn take_stability_ai_key(&mut self) -> ::std::string::String {
|
||||||
::std::mem::replace(&mut self.stability_ai_key, ::std::string::String::new())
|
::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 {
|
impl ::protobuf::Message for UserProfilePB {
|
||||||
@ -334,6 +361,9 @@ impl ::protobuf::Message for UserProfilePB {
|
|||||||
10 => {
|
10 => {
|
||||||
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.stability_ai_key)?;
|
::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())?;
|
::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() {
|
if !self.stability_ai_key.is_empty() {
|
||||||
my_size += ::protobuf::rt::string_size(10, &self.stability_ai_key);
|
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());
|
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
|
||||||
self.cached_size.set(my_size);
|
self.cached_size.set(my_size);
|
||||||
my_size
|
my_size
|
||||||
@ -412,6 +445,9 @@ impl ::protobuf::Message for UserProfilePB {
|
|||||||
if !self.stability_ai_key.is_empty() {
|
if !self.stability_ai_key.is_empty() {
|
||||||
os.write_string(10, &self.stability_ai_key)?;
|
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())?;
|
os.write_unknown_fields(self.get_unknown_fields())?;
|
||||||
::std::result::Result::Ok(())
|
::std::result::Result::Ok(())
|
||||||
}
|
}
|
||||||
@ -500,6 +536,11 @@ impl ::protobuf::Message for UserProfilePB {
|
|||||||
|m: &UserProfilePB| { &m.stability_ai_key },
|
|m: &UserProfilePB| { &m.stability_ai_key },
|
||||||
|m: &mut UserProfilePB| { &mut 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>(
|
::protobuf::reflect::MessageDescriptor::new_pb_name::<UserProfilePB>(
|
||||||
"UserProfilePB",
|
"UserProfilePB",
|
||||||
fields,
|
fields,
|
||||||
@ -526,6 +567,7 @@ impl ::protobuf::Clear for UserProfilePB {
|
|||||||
self.encryption_sign.clear();
|
self.encryption_sign.clear();
|
||||||
self.workspace_id.clear();
|
self.workspace_id.clear();
|
||||||
self.stability_ai_key.clear();
|
self.stability_ai_key.clear();
|
||||||
|
self.ai_model.clear();
|
||||||
self.unknown_fields.clear();
|
self.unknown_fields.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -593,7 +635,7 @@ impl ::protobuf::reflect::ProtobufValue for EncryptionTypePB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
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\
|
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\
|
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\
|
\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\
|
\rauthenticator\x18\x07\x20\x01(\x0e2\x10.AuthenticatorPBR\rauthenticato\
|
||||||
r\x12'\n\x0fencryption_sign\x18\x08\x20\x01(\tR\x0eencryptionSign\x12!\n\
|
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\
|
\x0cworkspace_id\x18\t\x20\x01(\tR\x0bworkspaceId\x12(\n\x10stability_ai\
|
||||||
_key\x18\n\x20\x01(\tR\x0estabilityAiKey*3\n\x10EncryptionTypePB\x12\x10\
|
_key\x18\n\x20\x01(\tR\x0estabilityAiKey\x12\x19\n\x08ai_model\x18\x0b\
|
||||||
\n\x0cNoEncryption\x10\0\x12\r\n\tSymmetric\x10\x01b\x06proto3\
|
\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;
|
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||||
|
@ -41,7 +41,7 @@ impl ServerProviderWASM {
|
|||||||
self.config.clone(),
|
self.config.clone(),
|
||||||
true,
|
true,
|
||||||
self.device_id.clone(),
|
self.device_id.clone(),
|
||||||
"0.0.1"
|
"0.0.1",
|
||||||
));
|
));
|
||||||
*self.server.write() = Some(server.clone());
|
*self.server.write() = Some(server.clone());
|
||||||
server
|
server
|
||||||
@ -70,6 +70,10 @@ impl UserCloudServiceProvider for ServerProviderWASM {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_ai_model(&self, ai_model: &str) -> Result<(), FlowyError> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>> {
|
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>> {
|
||||||
self.get_server().subscribe_token_state()
|
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]]
|
[[package]]
|
||||||
name = "app-error"
|
name = "app-error"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -183,7 +183,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "appflowy-ai-client"
|
name = "appflowy-ai-client"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -746,7 +746,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api"
|
name = "client-api"
|
||||||
version = "0.2.0"
|
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 = [
|
dependencies = [
|
||||||
"again",
|
"again",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -761,6 +761,7 @@ dependencies = [
|
|||||||
"collab",
|
"collab",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
"collab-rt-protocol",
|
"collab-rt-protocol",
|
||||||
|
"futures",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"getrandom 0.2.12",
|
"getrandom 0.2.12",
|
||||||
@ -768,6 +769,8 @@ dependencies = [
|
|||||||
"infra",
|
"infra",
|
||||||
"mime",
|
"mime",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project",
|
||||||
"prost",
|
"prost",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"scraper 0.17.1",
|
"scraper 0.17.1",
|
||||||
@ -792,7 +795,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api-entity"
|
name = "client-api-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
@ -804,7 +807,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-websocket"
|
name = "client-websocket"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -1053,7 +1056,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-rt-entity"
|
name = "collab-rt-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -1078,7 +1081,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-rt-protocol"
|
name = "collab-rt-protocol"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1328,7 +1331,7 @@ dependencies = [
|
|||||||
"cssparser-macros",
|
"cssparser-macros",
|
||||||
"dtoa-short",
|
"dtoa-short",
|
||||||
"itoa 1.0.10",
|
"itoa 1.0.10",
|
||||||
"phf 0.8.0",
|
"phf 0.11.2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1439,10 +1442,11 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "database-entity"
|
name = "database-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
|
"appflowy-ai-client",
|
||||||
"bincode",
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
@ -2463,6 +2467,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
@ -2968,7 +2973,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue"
|
name = "gotrue"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -2985,7 +2990,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue-entity"
|
name = "gotrue-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -3422,7 +3427,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "infra"
|
name = "infra"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -4931,7 +4936,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"log",
|
"log",
|
||||||
"multimap",
|
"multimap",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -4952,7 +4957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.55",
|
"syn 2.0.55",
|
||||||
@ -5930,7 +5935,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "shared-entity"
|
name = "shared-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
|
@ -52,7 +52,7 @@ collab-user = { version = "0.2" }
|
|||||||
# Run the script:
|
# Run the script:
|
||||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
# 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]
|
[dependencies]
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
@ -369,15 +369,6 @@
|
|||||||
"change": "Change email"
|
"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": {
|
"login": {
|
||||||
"title": "Account login",
|
"title": "Account login",
|
||||||
"loginLabel": "Log in",
|
"loginLabel": "Log in",
|
||||||
@ -611,6 +602,23 @@
|
|||||||
"couldNotLoadErrorMsg": "Could not load shortcuts, Try again",
|
"couldNotLoadErrorMsg": "Could not load shortcuts, Try again",
|
||||||
"couldNotSaveErrorMsg": "Could not save 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": {
|
"planPage": {
|
||||||
"menuLabel": "Plan",
|
"menuLabel": "Plan",
|
||||||
"title": "Pricing plan",
|
"title": "Pricing plan",
|
||||||
@ -1295,6 +1303,7 @@
|
|||||||
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
|
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
|
||||||
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
||||||
"smartEditDisabled": "Connect OpenAI in Settings",
|
"smartEditDisabled": "Connect OpenAI in Settings",
|
||||||
|
"appflowyAIEditDisabled": "Sign in to enable AI features",
|
||||||
"discardResponse": "Do you want to discard the AI responses?",
|
"discardResponse": "Do you want to discard the AI responses?",
|
||||||
"createInlineMathEquation": "Create equation",
|
"createInlineMathEquation": "Create equation",
|
||||||
"fonts": "Fonts",
|
"fonts": "Fonts",
|
||||||
|
51
frontend/rust-lib/Cargo.lock
generated
51
frontend/rust-lib/Cargo.lock
generated
@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "app-error"
|
name = "app-error"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -183,7 +183,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "appflowy-ai-client"
|
name = "appflowy-ai-client"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -664,7 +664,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api"
|
name = "client-api"
|
||||||
version = "0.2.0"
|
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 = [
|
dependencies = [
|
||||||
"again",
|
"again",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -679,6 +679,7 @@ dependencies = [
|
|||||||
"collab",
|
"collab",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
"collab-rt-protocol",
|
"collab-rt-protocol",
|
||||||
|
"futures",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"getrandom 0.2.10",
|
"getrandom 0.2.10",
|
||||||
@ -686,6 +687,8 @@ dependencies = [
|
|||||||
"infra",
|
"infra",
|
||||||
"mime",
|
"mime",
|
||||||
"parking_lot 0.12.1",
|
"parking_lot 0.12.1",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project",
|
||||||
"prost",
|
"prost",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"scraper 0.17.1",
|
"scraper 0.17.1",
|
||||||
@ -710,7 +713,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api-entity"
|
name = "client-api-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
@ -722,7 +725,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-websocket"
|
name = "client-websocket"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -931,7 +934,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-rt-entity"
|
name = "collab-rt-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -956,7 +959,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-rt-protocol"
|
name = "collab-rt-protocol"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1176,7 +1179,7 @@ dependencies = [
|
|||||||
"cssparser-macros",
|
"cssparser-macros",
|
||||||
"dtoa-short",
|
"dtoa-short",
|
||||||
"itoa",
|
"itoa",
|
||||||
"phf 0.8.0",
|
"phf 0.11.2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1276,10 +1279,11 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "database-entity"
|
name = "database-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
|
"appflowy-ai-client",
|
||||||
"bincode",
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
@ -2265,6 +2269,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
@ -2567,7 +2572,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue"
|
name = "gotrue"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -2584,7 +2589,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue-entity"
|
name = "gotrue-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -2949,7 +2954,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "infra"
|
name = "infra"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -3827,7 +3832,7 @@ version = "0.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"phf_macros",
|
"phf_macros 0.8.0",
|
||||||
"phf_shared 0.8.0",
|
"phf_shared 0.8.0",
|
||||||
"proc-macro-hack",
|
"proc-macro-hack",
|
||||||
]
|
]
|
||||||
@ -3847,6 +3852,7 @@ version = "0.11.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"phf_macros 0.11.2",
|
||||||
"phf_shared 0.11.2",
|
"phf_shared 0.11.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3914,6 +3920,19 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"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]]
|
[[package]]
|
||||||
name = "phf_shared"
|
name = "phf_shared"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@ -4117,7 +4136,7 @@ checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"log",
|
"log",
|
||||||
"multimap",
|
"multimap",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -4138,7 +4157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.47",
|
"syn 2.0.47",
|
||||||
@ -5035,7 +5054,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "shared-entity"
|
name = "shared-entity"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
|
@ -31,7 +31,6 @@ members = [
|
|||||||
"flowy-search-pub",
|
"flowy-search-pub",
|
||||||
"flowy-chat",
|
"flowy-chat",
|
||||||
"flowy-chat-pub",
|
"flowy-chat-pub",
|
||||||
"flowy-storage-pub",
|
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
@ -97,8 +96,8 @@ validator = { version = "0.16.1", features = ["derive"] }
|
|||||||
# Run the script:
|
# Run the script:
|
||||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
# 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" }
|
||||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6262816043efeede8823d7a7ea252083adf407e9" }
|
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "d61524d63605aa010afa6a734cbbe4fb4cd68ea1" }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::event_builder::EventBuilder;
|
use crate::event_builder::EventBuilder;
|
||||||
use crate::EventIntegrationTest;
|
use crate::EventIntegrationTest;
|
||||||
use flowy_chat::entities::{
|
use flowy_chat::entities::{
|
||||||
ChatMessageListPB, ChatMessageTypePB, LoadNextChatMessagePB, LoadPrevChatMessagePB,
|
ChatMessageListPB, ChatMessageTypePB, CompleteTextPB, CompleteTextTaskPB, CompletionTypePB,
|
||||||
SendChatPayloadPB,
|
LoadNextChatMessagePB, LoadPrevChatMessagePB, SendChatPayloadPB,
|
||||||
};
|
};
|
||||||
use flowy_chat::event_map::ChatEvent;
|
use flowy_chat::event_map::ChatEvent;
|
||||||
use flowy_folder::entities::{CreateViewPayloadPB, ViewLayoutPB, ViewPB};
|
use flowy_folder::entities::{CreateViewPayloadPB, ViewLayoutPB, ViewPB};
|
||||||
@ -86,4 +86,22 @@ impl EventIntegrationTest {
|
|||||||
.await
|
.await
|
||||||
.parse::<ChatMessageListPB>()
|
.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;
|
mod chat_message_test;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use bytes::Bytes;
|
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::{
|
pub use client_api::entity::{
|
||||||
ChatAuthorType, ChatMessage, ChatMessageType, MessageCursor, QAChatMessage, RepeatedChatMessage,
|
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 ChatMessageStream = BoxStream<'static, Result<ChatMessage, AppResponseError>>;
|
||||||
pub type StreamAnswer = BoxStream<'static, Result<Bytes, AppResponseError>>;
|
pub type StreamAnswer = BoxStream<'static, Result<Bytes, AppResponseError>>;
|
||||||
|
pub type StreamComplete = BoxStream<'static, Result<Bytes, AppResponseError>>;
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ChatCloudService: Send + Sync + 'static {
|
pub trait ChatCloudService: Send + Sync + 'static {
|
||||||
fn create_chat(
|
fn create_chat(
|
||||||
@ -72,4 +75,11 @@ pub trait ChatCloudService: Send + Sync + 'static {
|
|||||||
chat_id: &str,
|
chat_id: &str,
|
||||||
question_message_id: i64,
|
question_message_id: i64,
|
||||||
) -> FutureResult<ChatMessage, FlowyError>;
|
) -> 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::{
|
use crate::entities::{
|
||||||
ChatMessageErrorPB, ChatMessageListPB, ChatMessagePB, RepeatedRelatedQuestionPB,
|
ChatMessageErrorPB, ChatMessageListPB, ChatMessagePB, RepeatedRelatedQuestionPB,
|
||||||
};
|
};
|
||||||
use crate::manager::ChatUserService;
|
|
||||||
use crate::notification::{send_notification, ChatNotification};
|
use crate::notification::{send_notification, ChatNotification};
|
||||||
use crate::persistence::{insert_chat_messages, select_chat_messages, ChatMessageTable};
|
use crate::persistence::{insert_chat_messages, select_chat_messages, ChatMessageTable};
|
||||||
use allo_isolate::Isolate;
|
use allo_isolate::Isolate;
|
||||||
|
@ -17,8 +17,8 @@ pub trait ChatUserService: Send + Sync + 'static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChatManager {
|
pub struct ChatManager {
|
||||||
cloud_service: Arc<dyn ChatCloudService>,
|
pub(crate) cloud_service: Arc<dyn ChatCloudService>,
|
||||||
user_service: Arc<dyn ChatUserService>,
|
pub(crate) user_service: Arc<dyn ChatUserService>,
|
||||||
chats: Arc<DashMap<String, Arc<Chat>>>,
|
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 std::sync::{Arc, Weak};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
|
use crate::tools::AITools;
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||||
|
|
||||||
|
use crate::chat_manager::ChatManager;
|
||||||
use crate::entities::*;
|
use crate::entities::*;
|
||||||
use crate::manager::ChatManager;
|
|
||||||
|
|
||||||
fn upgrade_chat_manager(
|
fn upgrade_chat_manager(
|
||||||
chat_manager: AFPluginState<Weak<ChatManager>>,
|
chat_manager: AFPluginState<Weak<ChatManager>>,
|
||||||
@ -110,3 +111,22 @@ pub(crate) async fn stop_stream_handler(
|
|||||||
chat_manager.stop_stream(&data.chat_id).await?;
|
chat_manager.stop_stream(&data.chat_id).await?;
|
||||||
Ok(())
|
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 strum_macros::Display;
|
||||||
|
|
||||||
|
use crate::tools::AITools;
|
||||||
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
|
use flowy_derive::{Flowy_Event, ProtoBuf_Enum};
|
||||||
use lib_dispatch::prelude::*;
|
use lib_dispatch::prelude::*;
|
||||||
|
|
||||||
|
use crate::chat_manager::ChatManager;
|
||||||
use crate::event_handler::*;
|
use crate::event_handler::*;
|
||||||
use crate::manager::ChatManager;
|
|
||||||
|
|
||||||
pub fn init(chat_manager: Weak<ChatManager>) -> AFPlugin {
|
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()
|
AFPlugin::new()
|
||||||
.name("Flowy-Chat")
|
.name("Flowy-Chat")
|
||||||
.state(chat_manager)
|
.state(chat_manager)
|
||||||
|
.state(ai_tools)
|
||||||
.event(ChatEvent::StreamMessage, stream_chat_message_handler)
|
.event(ChatEvent::StreamMessage, stream_chat_message_handler)
|
||||||
.event(ChatEvent::LoadPrevMessage, load_prev_message_handler)
|
.event(ChatEvent::LoadPrevMessage, load_prev_message_handler)
|
||||||
.event(ChatEvent::LoadNextMessage, load_next_message_handler)
|
.event(ChatEvent::LoadNextMessage, load_next_message_handler)
|
||||||
.event(ChatEvent::GetRelatedQuestion, get_related_question_handler)
|
.event(ChatEvent::GetRelatedQuestion, get_related_question_handler)
|
||||||
.event(ChatEvent::GetAnswerForQuestion, get_answer_handler)
|
.event(ChatEvent::GetAnswerForQuestion, get_answer_handler)
|
||||||
.event(ChatEvent::StopStream, stop_stream_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)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||||
@ -41,4 +48,10 @@ pub enum ChatEvent {
|
|||||||
|
|
||||||
#[event(input = "ChatMessageIdPB", output = "ChatMessagePB")]
|
#[event(input = "ChatMessageIdPB", output = "ChatMessagePB")]
|
||||||
GetAnswerForQuestion = 5,
|
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;
|
pub mod event_map;
|
||||||
|
|
||||||
mod chat;
|
mod chat;
|
||||||
|
pub mod chat_manager;
|
||||||
pub mod entities;
|
pub mod entities;
|
||||||
pub mod manager;
|
|
||||||
pub mod notification;
|
pub mod notification;
|
||||||
mod persistence;
|
mod persistence;
|
||||||
mod protobuf;
|
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_chat_pub::cloud::ChatCloudService;
|
||||||
use flowy_error::FlowyError;
|
use flowy_error::FlowyError;
|
||||||
use flowy_sqlite::DBConnection;
|
use flowy_sqlite::DBConnection;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||||
use collab_integrate::CollabKVDB;
|
use collab_integrate::CollabKVDB;
|
||||||
use flowy_chat::manager::ChatManager;
|
use flowy_chat::chat_manager::ChatManager;
|
||||||
use flowy_database2::entities::DatabaseLayoutPB;
|
use flowy_database2::entities::DatabaseLayoutPB;
|
||||||
use flowy_database2::services::share::csv::CSVFormat;
|
use flowy_database2::services::share::csv::CSVFormat;
|
||||||
use flowy_database2::template::{make_default_board, make_default_calendar, make_default_grid};
|
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_search={}", level));
|
||||||
filters.push(format!("flowy_chat={}", level));
|
filters.push(format!("flowy_chat={}", level));
|
||||||
filters.push(format!("flowy_storage={}", level));
|
filters.push(format!("flowy_storage={}", level));
|
||||||
|
filters.push(format!("flowy_ai={}", level));
|
||||||
// Enable the frontend logs. DO NOT DISABLE.
|
// Enable the frontend logs. DO NOT DISABLE.
|
||||||
// These logs are essential for debugging and verifying frontend behavior.
|
// These logs are essential for debugging and verifying frontend behavior.
|
||||||
filters.push(format!("dart_ffi={}", level));
|
filters.push(format!("dart_ffi={}", level));
|
||||||
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use client_api::collab_sync::{SinkConfig, SyncObject, SyncPlugin};
|
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 client_api::entity::ChatMessageType;
|
||||||
use collab::core::origin::{CollabClient, CollabOrigin};
|
use collab::core::origin::{CollabClient, CollabOrigin};
|
||||||
|
|
||||||
@ -12,14 +12,14 @@ use collab::preclude::CollabPlugin;
|
|||||||
use collab_entity::CollabType;
|
use collab_entity::CollabType;
|
||||||
use collab_plugins::cloud_storage::postgres::SupabaseDBPlugin;
|
use collab_plugins::cloud_storage::postgres::SupabaseDBPlugin;
|
||||||
use tokio_stream::wrappers::WatchStream;
|
use tokio_stream::wrappers::WatchStream;
|
||||||
use tracing::debug;
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use collab_integrate::collab_builder::{
|
use collab_integrate::collab_builder::{
|
||||||
CollabCloudPluginProvider, CollabPluginProviderContext, CollabPluginProviderType,
|
CollabCloudPluginProvider, CollabPluginProviderContext, CollabPluginProviderType,
|
||||||
};
|
};
|
||||||
use flowy_chat_pub::cloud::{
|
use flowy_chat_pub::cloud::{
|
||||||
ChatCloudService, ChatMessage, ChatMessageStream, MessageCursor, RepeatedChatMessage,
|
ChatCloudService, ChatMessage, ChatMessageStream, MessageCursor, RepeatedChatMessage,
|
||||||
StreamAnswer,
|
StreamAnswer, StreamComplete,
|
||||||
};
|
};
|
||||||
use flowy_database_pub::cloud::{
|
use flowy_database_pub::cloud::{
|
||||||
CollabDocStateByOid, DatabaseCloudService, DatabaseSnapshot, SummaryRowContent,
|
CollabDocStateByOid, DatabaseCloudService, DatabaseSnapshot, SummaryRowContent,
|
||||||
@ -148,6 +148,13 @@ impl UserCloudServiceProvider for ServerProvider {
|
|||||||
Ok(())
|
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>> {
|
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>> {
|
||||||
let server = self.get_server().ok()?;
|
let server = self.get_server().ok()?;
|
||||||
server.subscribe_token_state()
|
server.subscribe_token_state()
|
||||||
@ -667,6 +674,21 @@ impl ChatCloudService for ServerProvider {
|
|||||||
.await
|
.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]
|
#[async_trait]
|
||||||
|
@ -9,7 +9,7 @@ use tokio::sync::RwLock;
|
|||||||
use tracing::{debug, error, event, info, instrument};
|
use tracing::{debug, error, event, info, instrument};
|
||||||
|
|
||||||
use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabPluginProviderType};
|
use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabPluginProviderType};
|
||||||
use flowy_chat::manager::ChatManager;
|
use flowy_chat::chat_manager::ChatManager;
|
||||||
use flowy_database2::DatabaseManager;
|
use flowy_database2::DatabaseManager;
|
||||||
use flowy_document::manager::DocumentManager;
|
use flowy_document::manager::DocumentManager;
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
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 std::sync::Weak;
|
||||||
|
|
||||||
use flowy_database2::DatabaseManager;
|
use flowy_database2::DatabaseManager;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use crate::af_cloud::AFServer;
|
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::{
|
use client_api::entity::{
|
||||||
CreateAnswerMessageParams, CreateChatMessageParams, CreateChatParams, MessageCursor,
|
CreateAnswerMessageParams, CreateChatMessageParams, CreateChatParams, MessageCursor,
|
||||||
RepeatedChatMessage,
|
RepeatedChatMessage,
|
||||||
};
|
};
|
||||||
use flowy_chat_pub::cloud::{
|
use flowy_chat_pub::cloud::{
|
||||||
ChatCloudService, ChatMessage, ChatMessageStream, ChatMessageType, StreamAnswer,
|
ChatCloudService, ChatMessage, ChatMessageStream, ChatMessageType, StreamAnswer, StreamComplete,
|
||||||
};
|
};
|
||||||
use flowy_error::FlowyError;
|
use flowy_error::FlowyError;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
@ -187,4 +187,23 @@ where
|
|||||||
Ok(resp)
|
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,
|
CreateWorkspaceParam, PatchWorkspaceParam, WorkspaceMemberChangeset, WorkspaceMemberInvitation,
|
||||||
};
|
};
|
||||||
use client_api::entity::{
|
use client_api::entity::{
|
||||||
AFRole, AFWorkspace, AFWorkspaceInvitation, AuthProvider, CollabParams, CreateCollabParams,
|
AFRole, AFWorkspace, AFWorkspaceInvitation, AFWorkspaceSettings, AFWorkspaceSettingsChange,
|
||||||
QueryWorkspaceMember,
|
AuthProvider, CollabParams, CreateCollabParams, QueryWorkspaceMember,
|
||||||
};
|
};
|
||||||
use client_api::entity::{QueryCollab, QueryCollabParams};
|
use client_api::entity::{QueryCollab, QueryCollabParams};
|
||||||
use client_api::{Client, ClientConfiguration};
|
use client_api::{Client, ClientConfiguration};
|
||||||
@ -570,6 +570,35 @@ where
|
|||||||
Ok(url)
|
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> {
|
async fn get_admin_client(client: &Arc<AFCloudClient>) -> FlowyResult<Client> {
|
||||||
|
@ -65,6 +65,7 @@ pub fn user_profile_from_af_profile(
|
|||||||
encryption_type,
|
encryption_type,
|
||||||
uid: profile.uid,
|
uid: profile.uid,
|
||||||
updated_at: profile.updated_at,
|
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::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use client_api::collab_sync::ServerCollabMessage;
|
use client_api::collab_sync::ServerCollabMessage;
|
||||||
|
use client_api::entity::ai_dto::AIModel;
|
||||||
use client_api::entity::UserMessage;
|
use client_api::entity::UserMessage;
|
||||||
use client_api::notify::{TokenState, TokenStateReceiver};
|
use client_api::notify::{TokenState, TokenStateReceiver};
|
||||||
use client_api::ws::{
|
use client_api::ws::{
|
||||||
@ -126,6 +128,11 @@ impl AppFlowyServer for AppFlowyCloudServer {
|
|||||||
.map_err(|err| Error::new(FlowyError::unauthorized().with_context(err)))
|
.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>> {
|
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>> {
|
||||||
let mut token_state_rx = self.client.subscribe_token_state();
|
let mut token_state_rx = self.client.subscribe_token_state();
|
||||||
let (watch_tx, watch_rx) = watch::channel(UserTokenState::Init);
|
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 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 flowy_error::FlowyError;
|
||||||
use lib_infra::async_trait::async_trait;
|
use lib_infra::async_trait::async_trait;
|
||||||
use lib_infra::future::FutureResult;
|
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."))
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_ai_model(&self, _ai_model: &str) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>> {
|
fn subscribe_token_state(&self) -> Option<WatchStream<UserTokenState>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -242,6 +242,7 @@ where
|
|||||||
authenticator: Authenticator::Supabase,
|
authenticator: Authenticator::Supabase,
|
||||||
encryption_type: EncryptionType::from_sign(&response.encryption_sign),
|
encryption_type: EncryptionType::from_sign(&response.encryption_sign),
|
||||||
updated_at: response.updated_at.timestamp(),
|
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,
|
encryption_type -> Text,
|
||||||
stability_ai_key -> Text,
|
stability_ai_key -> Text,
|
||||||
updated_at -> BigInt,
|
updated_at -> BigInt,
|
||||||
|
ai_model -> Text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,3 +21,4 @@ tokio-stream = "0.1.14"
|
|||||||
flowy-folder-pub.workspace = true
|
flowy-folder-pub.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
base64 = "0.21"
|
base64 = "0.21"
|
||||||
|
client-api = { workspace = true }
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
pub use client_api::entity::{AFWorkspaceSettings, AFWorkspaceSettingsChange};
|
||||||
use collab_entity::{CollabObject, CollabType};
|
use collab_entity::{CollabObject, CollabType};
|
||||||
use flowy_error::{internal_error, ErrorCode, FlowyError};
|
use flowy_error::{internal_error, ErrorCode, FlowyError};
|
||||||
use lib_infra::box_any::BoxAny;
|
use lib_infra::box_any::BoxAny;
|
||||||
@ -61,6 +62,7 @@ pub trait UserCloudServiceProvider: Send + Sync {
|
|||||||
/// # Returns
|
/// # Returns
|
||||||
/// A `Result` which is `Ok` if the token is successfully set, or a `FlowyError` otherwise.
|
/// 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_token(&self, token: &str) -> Result<(), FlowyError>;
|
||||||
|
fn set_ai_model(&self, ai_model: &str) -> Result<(), FlowyError>;
|
||||||
|
|
||||||
/// Subscribes to the state of the authentication token.
|
/// 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> {
|
fn get_billing_portal_url(&self) -> FutureResult<String, FlowyError> {
|
||||||
FutureResult::new(async { Err(FlowyError::not_support()) })
|
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>;
|
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.
|
// If the encryption_sign is not empty, which means the user has enabled the encryption.
|
||||||
pub encryption_type: EncryptionType,
|
pub encryption_type: EncryptionType,
|
||||||
pub updated_at: i64,
|
pub updated_at: i64,
|
||||||
|
pub ai_model: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
|
||||||
@ -249,6 +250,7 @@ where
|
|||||||
encryption_type: value.encryption_type(),
|
encryption_type: value.encryption_type(),
|
||||||
stability_ai_key,
|
stability_ai_key,
|
||||||
updated_at: value.updated_at(),
|
updated_at: value.updated_at(),
|
||||||
|
ai_model: "".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,6 +266,7 @@ pub struct UpdateUserProfileParams {
|
|||||||
pub stability_ai_key: Option<String>,
|
pub stability_ai_key: Option<String>,
|
||||||
pub encryption_sign: Option<String>,
|
pub encryption_sign: Option<String>,
|
||||||
pub token: Option<String>,
|
pub token: Option<String>,
|
||||||
|
pub ai_model: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateUserProfileParams {
|
impl UpdateUserProfileParams {
|
||||||
@ -318,6 +321,11 @@ impl UpdateUserProfileParams {
|
|||||||
self
|
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 {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.name.is_none()
|
self.name.is_none()
|
||||||
&& self.email.is_none()
|
&& self.email.is_none()
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
use std::str::FromStr;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||||
use flowy_user_pub::entities::*;
|
use flowy_user_pub::entities::*;
|
||||||
|
|
||||||
use crate::entities::parser::{UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword};
|
use crate::entities::parser::{UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword};
|
||||||
use crate::entities::AuthenticatorPB;
|
use crate::entities::{AIModelPB, AuthenticatorPB};
|
||||||
use crate::errors::ErrorCode;
|
use crate::errors::ErrorCode;
|
||||||
|
|
||||||
use super::parser::UserStabilityAIKey;
|
use super::parser::UserStabilityAIKey;
|
||||||
@ -56,6 +57,9 @@ pub struct UserProfilePB {
|
|||||||
|
|
||||||
#[pb(index = 11)]
|
#[pb(index = 11)]
|
||||||
pub stability_ai_key: String,
|
pub stability_ai_key: String,
|
||||||
|
|
||||||
|
#[pb(index = 12)]
|
||||||
|
pub ai_model: AIModelPB,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)]
|
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)]
|
||||||
@ -88,6 +92,7 @@ impl From<UserProfile> for UserProfilePB {
|
|||||||
encryption_type: encryption_ty,
|
encryption_type: encryption_ty,
|
||||||
workspace_id: user_profile.workspace_id,
|
workspace_id: user_profile.workspace_id,
|
||||||
stability_ai_key: user_profile.stability_ai_key,
|
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,
|
encryption_sign: None,
|
||||||
token: None,
|
token: None,
|
||||||
stability_ai_key,
|
stability_ai_key,
|
||||||
|
ai_model: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||||
|
use flowy_user_pub::cloud::{AFWorkspaceSettings, AFWorkspaceSettingsChange};
|
||||||
use flowy_user_pub::entities::{
|
use flowy_user_pub::entities::{
|
||||||
RecurringInterval, Role, SubscriptionPlan, WorkspaceInvitation, WorkspaceMember,
|
RecurringInterval, Role, SubscriptionPlan, WorkspaceInvitation, WorkspaceMember,
|
||||||
WorkspaceSubscription,
|
WorkspaceSubscription,
|
||||||
@ -344,3 +346,86 @@ pub struct BillingPortalPB {
|
|||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
pub url: String,
|
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?;
|
let member = manager.get_workspace_member_info(param.uid).await?;
|
||||||
data_result_ok(member.into())
|
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::CancelWorkspaceSubscription, cancel_workspace_subscription_handler)
|
||||||
.event(UserEvent::GetWorkspaceUsage, get_workspace_usage_handler)
|
.event(UserEvent::GetWorkspaceUsage, get_workspace_usage_handler)
|
||||||
.event(UserEvent::GetBillingPortal, get_billing_portal_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")]
|
#[event(input = "WorkspaceMemberIdPB", output = "WorkspaceMemberPB")]
|
||||||
GetMemberInfo = 56,
|
GetMemberInfo = 56,
|
||||||
|
|
||||||
|
#[event(input = "UpdateUserWorkspaceSettingPB")]
|
||||||
|
UpdateWorkspaceSetting = 57,
|
||||||
|
|
||||||
|
#[event(input = "UserWorkspaceIdPB", output = "UseAISettingPB")]
|
||||||
|
GetWorkspaceSetting = 58,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UserStatusCallback: Send + Sync + 'static {
|
pub trait UserStatusCallback: Send + Sync + 'static {
|
||||||
|
@ -14,6 +14,7 @@ pub(crate) enum UserNotification {
|
|||||||
DidUpdateUserWorkspaces = 3,
|
DidUpdateUserWorkspaces = 3,
|
||||||
DidUpdateCloudConfig = 4,
|
DidUpdateCloudConfig = 4,
|
||||||
DidUpdateUserWorkspace = 5,
|
DidUpdateUserWorkspace = 5,
|
||||||
|
DidUpdateAISetting = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<UserNotification> for i32 {
|
impl std::convert::From<UserNotification> for i32 {
|
||||||
|
@ -24,6 +24,7 @@ pub struct UserTable {
|
|||||||
pub(crate) encryption_type: String,
|
pub(crate) encryption_type: String,
|
||||||
pub(crate) stability_ai_key: String,
|
pub(crate) stability_ai_key: String,
|
||||||
pub(crate) updated_at: i64,
|
pub(crate) updated_at: i64,
|
||||||
|
pub(crate) ai_model: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserTable {
|
impl UserTable {
|
||||||
@ -49,6 +50,7 @@ impl From<(UserProfile, Authenticator)> for UserTable {
|
|||||||
encryption_type,
|
encryption_type,
|
||||||
stability_ai_key: user_profile.stability_ai_key,
|
stability_ai_key: user_profile.stability_ai_key,
|
||||||
updated_at: user_profile.updated_at,
|
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(),
|
encryption_type: EncryptionType::from_str(&table.encryption_type).unwrap_or_default(),
|
||||||
stability_ai_key: table.stability_ai_key,
|
stability_ai_key: table.stability_ai_key,
|
||||||
updated_at: table.updated_at,
|
updated_at: table.updated_at,
|
||||||
|
ai_model: table.ai_model,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,6 +86,7 @@ pub struct UserTableChangeset {
|
|||||||
pub encryption_type: Option<String>,
|
pub encryption_type: Option<String>,
|
||||||
pub token: Option<String>,
|
pub token: Option<String>,
|
||||||
pub stability_ai_key: Option<String>,
|
pub stability_ai_key: Option<String>,
|
||||||
|
pub ai_model: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserTableChangeset {
|
impl UserTableChangeset {
|
||||||
@ -101,6 +105,7 @@ impl UserTableChangeset {
|
|||||||
encryption_type,
|
encryption_type,
|
||||||
token: params.token,
|
token: params.token,
|
||||||
stability_ai_key: params.stability_ai_key,
|
stability_ai_key: params.stability_ai_key,
|
||||||
|
ai_model: params.ai_model,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +121,7 @@ impl UserTableChangeset {
|
|||||||
encryption_type: Some(encryption_type),
|
encryption_type: Some(encryption_type),
|
||||||
token: Some(user_profile.token),
|
token: Some(user_profile.token),
|
||||||
stability_ai_key: Some(user_profile.stability_ai_key),
|
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);
|
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
|
// Subscribe the token state
|
||||||
let weak_cloud_services = Arc::downgrade(&self.cloud_services);
|
let weak_cloud_services = Arc::downgrade(&self.cloud_services);
|
||||||
let weak_authenticate_user = Arc::downgrade(&self.authenticate_user);
|
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,
|
uid: i64,
|
||||||
mut conn: DBConnection,
|
mut conn: DBConnection,
|
||||||
changeset: UserTableChangeset,
|
changeset: UserTableChangeset,
|
||||||
|
@ -11,13 +11,14 @@ use flowy_folder_pub::entities::{AppFlowyData, ImportData};
|
|||||||
use flowy_sqlite::schema::user_workspace_table;
|
use flowy_sqlite::schema::user_workspace_table;
|
||||||
use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods};
|
use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods};
|
||||||
use flowy_user_pub::entities::{
|
use flowy_user_pub::entities::{
|
||||||
Role, UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus, WorkspaceMember,
|
Role, UpdateUserProfileParams, UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus,
|
||||||
WorkspaceSubscription, WorkspaceUsage,
|
WorkspaceMember, WorkspaceSubscription, WorkspaceUsage,
|
||||||
};
|
};
|
||||||
use lib_dispatch::prelude::af_spawn;
|
use lib_dispatch::prelude::af_spawn;
|
||||||
|
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
RepeatedUserWorkspacePB, ResetWorkspacePB, SubscribeWorkspacePB, UserWorkspacePB,
|
RepeatedUserWorkspacePB, ResetWorkspacePB, SubscribeWorkspacePB, UpdateUserWorkspaceSettingPB,
|
||||||
|
UseAISettingPB, UserWorkspacePB,
|
||||||
};
|
};
|
||||||
use crate::migrations::AnonUser;
|
use crate::migrations::AnonUser;
|
||||||
use crate::notification::{send_notification, UserNotification};
|
use crate::notification::{send_notification, UserNotification};
|
||||||
@ -27,10 +28,11 @@ use crate::services::data_import::{
|
|||||||
use crate::services::sqlite_sql::member_sql::{
|
use crate::services::sqlite_sql::member_sql::{
|
||||||
select_workspace_member, upsert_workspace_member, WorkspaceMemberTable,
|
select_workspace_member, upsert_workspace_member, WorkspaceMemberTable,
|
||||||
};
|
};
|
||||||
|
use crate::services::sqlite_sql::user_sql::UserTableChangeset;
|
||||||
use crate::services::sqlite_sql::workspace_sql::{
|
use crate::services::sqlite_sql::workspace_sql::{
|
||||||
get_all_user_workspace_op, get_user_workspace_op, insert_new_workspaces_op, UserWorkspaceTable,
|
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;
|
use flowy_user_pub::session::Session;
|
||||||
|
|
||||||
impl UserManager {
|
impl UserManager {
|
||||||
@ -483,6 +485,49 @@ impl UserManager {
|
|||||||
Ok(url)
|
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)]
|
#[instrument(level = "debug", skip(self), err)]
|
||||||
pub async fn get_workspace_member_info(&self, uid: i64) -> FlowyResult<WorkspaceMember> {
|
pub async fn get_workspace_member_info(&self, uid: i64) -> FlowyResult<WorkspaceMember> {
|
||||||
let workspace_id = self.get_session()?.user_workspace.id.clone();
|
let workspace_id = self.get_session()?.user_workspace.id.clone();
|
||||||
|
Loading…
Reference in New Issue
Block a user