Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71cb0f023b | ||
|
|
669c17064b | ||
|
|
b3cb6090f2 | ||
|
|
0d89e22ed2 | ||
|
|
ff2aae213c | ||
|
|
43e64d8219 | ||
|
|
6823fe5d24 | ||
|
|
f683085618 | ||
|
|
71a22dc466 | ||
|
|
eb508a3ec9 | ||
|
|
aacd09d8e2 | ||
|
|
25a27dfa81 | ||
|
|
36349778e3 | ||
|
|
9271d42db5 | ||
|
|
90d6e98b51 | ||
|
|
c39a91a014 |
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import 'data_migration/data_migration_test_runner.dart'
|
||||
as data_migration_test_runner;
|
||||
import 'database/database_test_runner.dart' as database_test_runner;
|
||||
import 'document/document_test_runner.dart' as document_test_runner;
|
||||
import 'set_env.dart' as preset_af_cloud_env_test;
|
||||
import 'sidebar/sidebar_icon_test.dart' as sidebar_icon_test;
|
||||
@@ -28,4 +29,7 @@ Future<void> main() async {
|
||||
sidebar_move_page_test.main();
|
||||
sidebar_rename_untitled_test.main();
|
||||
sidebar_icon_test.main();
|
||||
|
||||
// database
|
||||
database_test_runner.main();
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/resizeable_image.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
hide UploadImageMenu, ResizableImage;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/database_test_op.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// copy link to block
|
||||
group('database image:', () {
|
||||
testWidgets('insert image', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// open the first row detail page and upload an image
|
||||
await tester.createNewPageInSpace(
|
||||
spaceName: Constants.generalSpaceName,
|
||||
layout: ViewLayoutPB.Grid,
|
||||
pageName: 'database image',
|
||||
);
|
||||
await tester.openFirstRowDetailPage();
|
||||
|
||||
// insert an image block
|
||||
{
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
await tester.editor.showSlashMenu();
|
||||
await tester.editor.tapSlashMenuItemWithName(
|
||||
LocaleKeys.document_slashMenu_name_image.tr(),
|
||||
);
|
||||
}
|
||||
|
||||
// upload an image
|
||||
{
|
||||
final image = await rootBundle.load('assets/test/images/sample.jpeg');
|
||||
final tempDirectory = await getTemporaryDirectory();
|
||||
final imagePath = p.join(tempDirectory.path, 'sample.jpeg');
|
||||
final file = File(imagePath)
|
||||
..writeAsBytesSync(image.buffer.asUint8List());
|
||||
|
||||
mockPickFilePaths(
|
||||
paths: [imagePath],
|
||||
);
|
||||
|
||||
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
|
||||
await tester.tapButtonWithName(
|
||||
LocaleKeys.document_imageBlock_upload_placeholder.tr(),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ResizableImage), findsOneWidget);
|
||||
final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
|
||||
expect(node.type, ImageBlockKeys.type);
|
||||
expect(node.attributes[ImageBlockKeys.url], isNotEmpty);
|
||||
|
||||
// remove the temp file
|
||||
file.deleteSync();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'database_image_test.dart' as database_image_test;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
database_image_test.main();
|
||||
}
|
||||
+1
-1
@@ -458,7 +458,7 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('paste the image url', (tester) async {
|
||||
const plainText = 'https://appflowy.io/1.jpg';
|
||||
const plainText = 'http://example.com/1.jpg';
|
||||
final image = await rootBundle.load('assets/test/images/sample.jpeg');
|
||||
final bytes = image.buffer.asUint8List();
|
||||
await tester.pasteContent(plainText: plainText, image: ('jpeg', bytes),
|
||||
|
||||
@@ -175,36 +175,36 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: c5161ac5ab5383ad046884568b4b91cb52df5d91
|
||||
appflowy_backend: 78f6a053f756e6bc29bcc5a2106cbe77b756e97a
|
||||
connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf
|
||||
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
|
||||
app_links: e70ca16b4b0f88253b3b3660200d4a10b4ea9795
|
||||
appflowy_backend: 144c20d8bfb298c4e10fa3fa6701a9f41bf98b88
|
||||
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517
|
||||
flowy_infra_ui: 931b73a18b54a392ab6152eebe29a63a30751f53
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
fluttertoast: 76fea30fcf04176325f6864c87306927bd7d2038
|
||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||
integration_test: d5929033778cc4991a187e4e1a85396fa4f59b3a
|
||||
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
||||
keyboard_height_plugin: ef70a8181b29f27670e9e2450814ca6b6dc05b05
|
||||
open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||
integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
|
||||
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
|
||||
keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86
|
||||
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
|
||||
Sentry: 1fe34e9c2cbba1e347623610d26db121dcb569f1
|
||||
sentry_flutter: e24b397f9a61fa5bbefd8279c3b2242ca86faa90
|
||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3
|
||||
super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4
|
||||
sentry_flutter: a39c2a2d67d5e5b9cb0b94a4985c76dd5b3fc737
|
||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
super_native_extensions: 4916b3c627a9c7fffdc48a23a9eca0b1ac228fa7
|
||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
webview_flutter_wkwebview: 45a041c7831641076618876de3ba75c712860c6b
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1
|
||||
|
||||
PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca
|
||||
|
||||
|
||||
@@ -148,6 +148,15 @@ class CompletionStream {
|
||||
);
|
||||
}
|
||||
|
||||
if (event.startsWith("AI_MAX_REQUIRED:")) {
|
||||
final msg = event.substring(16);
|
||||
onError(
|
||||
AIError(
|
||||
message: msg,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (event.startsWith("start:")) {
|
||||
await onStart();
|
||||
}
|
||||
|
||||
@@ -247,6 +247,7 @@ Future<AppFlowyCloudConfiguration> configurationFromUri(
|
||||
// In development mode, the app is configured to access the AppFlowy cloud server directly through specific ports.
|
||||
// This setup bypasses the need for Nginx, meaning that the AppFlowy cloud should be running without an Nginx server
|
||||
// in the development environment.
|
||||
// If you modify following code, please update the corresponding documentation in the appflowy billing.
|
||||
if (authenticatorType == AuthenticatorType.appflowyCloudDevelop) {
|
||||
return AppFlowyCloudConfiguration(
|
||||
base_url: "$baseUrl:8000",
|
||||
|
||||
@@ -105,6 +105,13 @@ class ChatAIMessageBloc extends Bloc<ChatAIMessageEvent, ChatAIMessageState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
onAIMaxRequired: (message) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
messageState: MessageState.onAIMaxRequired(message),
|
||||
),
|
||||
);
|
||||
},
|
||||
receiveMetadata: (metadata) {
|
||||
Log.debug("AI Steps: ${metadata.progress?.step}");
|
||||
emit(
|
||||
@@ -146,6 +153,12 @@ class ChatAIMessageBloc extends Bloc<ChatAIMessageEvent, ChatAIMessageState> {
|
||||
add(ChatAIMessageEvent.receiveMetadata(metadata));
|
||||
}
|
||||
},
|
||||
onAIMaxRequired: (message) {
|
||||
if (!isClosed) {
|
||||
Log.info(message);
|
||||
add(ChatAIMessageEvent.onAIMaxRequired(message));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -159,6 +172,8 @@ class ChatAIMessageEvent with _$ChatAIMessageEvent {
|
||||
const factory ChatAIMessageEvent.onAIResponseLimit() = _OnAIResponseLimit;
|
||||
const factory ChatAIMessageEvent.onAIImageResponseLimit() =
|
||||
_OnAIImageResponseLimit;
|
||||
const factory ChatAIMessageEvent.onAIMaxRequired(String message) =
|
||||
_OnAIMaxRquired;
|
||||
const factory ChatAIMessageEvent.receiveMetadata(
|
||||
MetadataCollection metadata,
|
||||
) = _ReceiveMetadata;
|
||||
@@ -193,6 +208,7 @@ class MessageState with _$MessageState {
|
||||
const factory MessageState.onError(String error) = _Error;
|
||||
const factory MessageState.onAIResponseLimit() = _AIResponseLimit;
|
||||
const factory MessageState.onAIImageResponseLimit() = _AIImageResponseLimit;
|
||||
const factory MessageState.onAIMaxRequired(String message) = _AIMaxRequired;
|
||||
const factory MessageState.ready() = _Ready;
|
||||
const factory MessageState.loading() = _Loading;
|
||||
}
|
||||
@@ -28,6 +28,14 @@ class AnswerStream {
|
||||
} else if (event == "AI_IMAGE_RESPONSE_LIMIT") {
|
||||
_aiImageLimitReached = true;
|
||||
_onAIImageResponseLimit?.call();
|
||||
} else if (event.startsWith("AI_MAX_REQUIRED:")) {
|
||||
final msg = event.substring(16);
|
||||
// If the callback is not registered yet, add the event to the buffer.
|
||||
if (_onAIMaxRequired != null) {
|
||||
_onAIMaxRequired!(msg);
|
||||
} else {
|
||||
_pendingAIMaxRequiredEvents.add(msg);
|
||||
}
|
||||
}
|
||||
},
|
||||
onDone: () {
|
||||
@@ -56,8 +64,12 @@ class AnswerStream {
|
||||
void Function(String error)? _onError;
|
||||
void Function()? _onAIResponseLimit;
|
||||
void Function()? _onAIImageResponseLimit;
|
||||
void Function(String message)? _onAIMaxRequired;
|
||||
void Function(MetadataCollection metadataCollection)? _onMetadata;
|
||||
|
||||
// Buffer for events that occur before listen() is called.
|
||||
final List<String> _pendingAIMaxRequiredEvents = [];
|
||||
|
||||
int get nativePort => _port.sendPort.nativePort;
|
||||
bool get hasStarted => _hasStarted;
|
||||
bool get aiLimitReached => _aiLimitReached;
|
||||
@@ -78,6 +90,7 @@ class AnswerStream {
|
||||
void Function(String error)? onError,
|
||||
void Function()? onAIResponseLimit,
|
||||
void Function()? onAIImageResponseLimit,
|
||||
void Function(String message)? onAIMaxRequired,
|
||||
void Function(MetadataCollection metadata)? onMetadata,
|
||||
}) {
|
||||
_onData = onData;
|
||||
@@ -87,6 +100,15 @@ class AnswerStream {
|
||||
_onAIResponseLimit = onAIResponseLimit;
|
||||
_onAIImageResponseLimit = onAIImageResponseLimit;
|
||||
_onMetadata = onMetadata;
|
||||
_onAIMaxRequired = onAIMaxRequired;
|
||||
|
||||
// Flush any buffered AI_MAX_REQUIRED events.
|
||||
if (_onAIMaxRequired != null && _pendingAIMaxRequiredEvents.isNotEmpty) {
|
||||
for (final msg in _pendingAIMaxRequiredEvents) {
|
||||
_onAIMaxRequired!(msg);
|
||||
}
|
||||
_pendingAIMaxRequiredEvents.clear();
|
||||
}
|
||||
|
||||
_onStart?.call();
|
||||
}
|
||||
|
||||
+13
-10
@@ -138,16 +138,18 @@ class _MobileChatInputState extends State<MobileChatInput> {
|
||||
else
|
||||
const VSpace(8.0),
|
||||
inputTextField(context),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(8.0),
|
||||
leadingButtons(context),
|
||||
const Spacer(),
|
||||
sendButton(),
|
||||
const HSpace(12.0),
|
||||
],
|
||||
TextFieldTapRegion(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(8.0),
|
||||
leadingButtons(context),
|
||||
const Spacer(),
|
||||
sendButton(),
|
||||
const HSpace(12.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -281,6 +283,7 @@ class _MobileChatInputState extends State<MobileChatInput> {
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
onTapOutside: (_) => focusNode.unfocus(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
+5
@@ -137,6 +137,11 @@ class ChatAIMessageWidget extends StatelessWidget {
|
||||
errorMessage: LocaleKeys.sideBar_purchaseAIMax.tr(),
|
||||
);
|
||||
},
|
||||
onAIMaxRequired: (message) {
|
||||
return ChatErrorMessageWidget(
|
||||
errorMessage: message,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/util/field_type_extension.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:flutter/material.dart' hide Card;
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
@@ -856,10 +857,13 @@ void _openCard({
|
||||
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (_) => RowDetailPage(
|
||||
databaseController: databaseController,
|
||||
rowController: rowController,
|
||||
userProfile: context.read<BoardBloc>().userProfile,
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: context.read<UserWorkspaceBloc>(),
|
||||
child: RowDetailPage(
|
||||
databaseController: databaseController,
|
||||
rowController: rowController,
|
||||
userProfile: context.read<BoardBloc>().userProfile,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
+8
-4
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
@@ -426,10 +427,13 @@ class HiddenGroupPopupItemList extends StatelessWidget {
|
||||
onPressed: () {
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (_) => RowDetailPage(
|
||||
databaseController: databaseController,
|
||||
rowController: rowController,
|
||||
userProfile: context.read<BoardBloc>().userProfile,
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: context.read<UserWorkspaceBloc>(),
|
||||
child: RowDetailPage(
|
||||
databaseController: databaseController,
|
||||
rowController: rowController,
|
||||
userProfile: context.read<BoardBloc>().userProfile,
|
||||
),
|
||||
),
|
||||
);
|
||||
PopoverContainer.of(context).close();
|
||||
|
||||
+8
-4
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/plugins/database/application/row/row_controller.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart';
|
||||
@@ -178,10 +179,13 @@ class _EventCardState extends State<EventCard> {
|
||||
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (_) => RowDetailPage(
|
||||
databaseController: widget.databaseController,
|
||||
rowController: rowController,
|
||||
userProfile: context.read<CalendarBloc>().userProfile,
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: context.read<UserWorkspaceBloc>(),
|
||||
child: RowDetailPage(
|
||||
databaseController: widget.databaseController,
|
||||
rowController: rowController,
|
||||
userProfile: context.read<CalendarBloc>().userProfile,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
+13
-9
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database/tab_bar/desktop/setting_menu.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -13,7 +14,6 @@ import 'package:appflowy/plugins/database/calendar/application/calendar_bloc.dar
|
||||
import 'package:appflowy/plugins/database/calendar/application/unschedule_event_bloc.dart';
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:calendar_view/calendar_view.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -390,7 +390,7 @@ void showEventDetails({
|
||||
context: context,
|
||||
builder: (BuildContext overlayContext) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<ViewBloc>(),
|
||||
value: context.read<UserWorkspaceBloc>(),
|
||||
child: RowDetailPage(
|
||||
rowController: rowController,
|
||||
databaseController: databaseController,
|
||||
@@ -457,14 +457,18 @@ class _UnscheduledEventsButtonState extends State<UnscheduledEventsButton> {
|
||||
),
|
||||
),
|
||||
),
|
||||
popupBuilder: (_) => BlocProvider.value(
|
||||
value: context.read<CalendarBloc>(),
|
||||
child: BlocProvider.value(
|
||||
value: context.read<ViewBloc>(),
|
||||
child: UnscheduleEventsList(
|
||||
databaseController: widget.databaseController,
|
||||
unscheduleEvents: state.unscheduleEvents,
|
||||
popupBuilder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(
|
||||
value: context.read<CalendarBloc>(),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: context.read<UserWorkspaceBloc>(),
|
||||
),
|
||||
],
|
||||
child: UnscheduleEventsList(
|
||||
databaseController: widget.databaseController,
|
||||
unscheduleEvents: state.unscheduleEvents,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/grid_setting_bar.dart';
|
||||
import 'package:appflowy/plugins/database/tab_bar/desktop/setting_menu.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@@ -13,7 +14,6 @@ import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dar
|
||||
import 'package:appflowy/shared/flowy_error_page.dart';
|
||||
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
@@ -196,7 +196,7 @@ class _GridPageState extends State<GridPage> {
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: context.read<ViewBloc>(),
|
||||
value: context.read<UserWorkspaceBloc>(),
|
||||
child: RowDetailPage(
|
||||
databaseController: context.read<GridBloc>().databaseController,
|
||||
rowController: rowController,
|
||||
@@ -233,7 +233,7 @@ class _GridPageState extends State<GridPage> {
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (_) => BlocProvider.value(
|
||||
value: context.read<ViewBloc>(),
|
||||
value: context.read<UserWorkspaceBloc>(),
|
||||
child: RowDetailPage(
|
||||
databaseController:
|
||||
context.read<GridBloc>().databaseController,
|
||||
@@ -559,7 +559,7 @@ class _GridRowsState extends State<_GridRows> {
|
||||
}
|
||||
|
||||
return BlocProvider.value(
|
||||
value: context.read<ViewBloc>(),
|
||||
value: context.read<UserWorkspaceBloc>(),
|
||||
child: RowDetailPage(
|
||||
rowController: RowController(
|
||||
viewId: viewId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/plugins/database/application/row/related_row_detail_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@@ -26,10 +27,13 @@ class RelatedRowDetailPage extends StatelessWidget {
|
||||
return state.when(
|
||||
loading: () => const SizedBox.shrink(),
|
||||
ready: (databaseController, rowController) {
|
||||
return RowDetailPage(
|
||||
databaseController: databaseController,
|
||||
rowController: rowController,
|
||||
allowOpenAsFullPage: false,
|
||||
return BlocProvider.value(
|
||||
value: context.read<UserWorkspaceBloc>(),
|
||||
child: RowDetailPage(
|
||||
databaseController: databaseController,
|
||||
rowController: rowController,
|
||||
allowOpenAsFullPage: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
+3
-1
@@ -2,6 +2,7 @@ import 'dart:ui';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/block_menu/block_menu_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
|
||||
@@ -145,7 +146,8 @@ class _ImageMenuState extends State<ImageMenu> {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (_) => InteractiveImageViewer(
|
||||
userProfile: context.read<UserWorkspaceBloc>().userProfile,
|
||||
userProfile: context.read<UserWorkspaceBloc?>()?.userProfile ??
|
||||
context.read<DocumentBloc>().state.userProfilePB,
|
||||
imageProvider: AFBlockImageProvider(
|
||||
images: [
|
||||
ImageBlockData(
|
||||
|
||||
+2
-1
@@ -54,7 +54,8 @@ class _ImageBrowserLayoutState extends State<ImageBrowserLayout> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_userProfile = context.read<UserWorkspaceBloc?>()?.userProfile;
|
||||
_userProfile = context.read<UserWorkspaceBloc?>()?.userProfile ??
|
||||
context.read<DocumentBloc>().state.userProfilePB;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
+4
-6
@@ -70,8 +70,11 @@ class _ResizableImageState extends State<ResizableImage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
imageWidth = widget.width;
|
||||
_userProfilePB = context.read<UserWorkspaceBloc?>()?.userProfile;
|
||||
|
||||
_userProfilePB = context.read<UserWorkspaceBloc?>()?.userProfile ??
|
||||
context.read<DocumentBloc>().state.userProfilePB;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -97,11 +100,6 @@ class _ResizableImageState extends State<ResizableImage> {
|
||||
Widget child;
|
||||
final src = widget.src;
|
||||
if (isURL(src)) {
|
||||
// load network image
|
||||
if (widget.type == CustomImageType.internal && _userProfilePB == null) {
|
||||
return _buildLoading(context);
|
||||
}
|
||||
|
||||
_cacheImage = FlowyNetworkImage(
|
||||
url: widget.src,
|
||||
width: imageWidth - moveDistance,
|
||||
|
||||
+68
-3
@@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/user/application/user_listener.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
@@ -9,6 +11,7 @@ import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'settings_ai_bloc.freezed.dart';
|
||||
part 'settings_ai_bloc.g.dart';
|
||||
|
||||
class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
SettingsAIBloc(
|
||||
@@ -19,6 +22,7 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
_userService = UserBackendService(userId: userProfile.id),
|
||||
super(
|
||||
SettingsAIState(
|
||||
selectedAIModel: userProfile.aiModel,
|
||||
userProfile: userProfile,
|
||||
currentWorkspaceMemberRole: currentWorkspaceMemberRole,
|
||||
),
|
||||
@@ -65,6 +69,7 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
},
|
||||
);
|
||||
_loadUserWorkspaceSetting();
|
||||
_loadModelList();
|
||||
},
|
||||
didReceiveUserProfile: (userProfile) {
|
||||
emit(state.copyWith(userProfile: userProfile));
|
||||
@@ -78,7 +83,7 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
!(state.aiSettings?.disableSearchIndexing ?? false),
|
||||
);
|
||||
},
|
||||
selectModel: (AIModelPB model) {
|
||||
selectModel: (String model) {
|
||||
_updateUserWorkspaceSetting(model: model);
|
||||
},
|
||||
didLoadAISetting: (UseAISettingPB settings) {
|
||||
@@ -89,6 +94,34 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
didLoadAvailableModels: (String models) {
|
||||
final dynamic decodedJson = jsonDecode(models);
|
||||
Log.info("Available models: $decodedJson");
|
||||
if (decodedJson is Map<String, dynamic>) {
|
||||
final models = ModelList.fromJson(decodedJson).models;
|
||||
if (models.isEmpty) {
|
||||
// If available models is empty, then we just show the
|
||||
// Default
|
||||
emit(state.copyWith(availableModels: ["Default"]));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!models.contains(state.selectedAIModel)) {
|
||||
// Use first model as default model if current selected model
|
||||
// is not available
|
||||
final selectedModel = models[0];
|
||||
_updateUserWorkspaceSetting(model: selectedModel);
|
||||
emit(
|
||||
state.copyWith(
|
||||
availableModels: models,
|
||||
selectedAIModel: selectedModel,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(state.copyWith(availableModels: models));
|
||||
}
|
||||
}
|
||||
},
|
||||
refreshMember: (member) {
|
||||
emit(state.copyWith(currentWorkspaceMemberRole: member.role));
|
||||
},
|
||||
@@ -98,7 +131,7 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
|
||||
void _updateUserWorkspaceSetting({
|
||||
bool? disableSearchIndexing,
|
||||
AIModelPB? model,
|
||||
String? model,
|
||||
}) {
|
||||
final payload = UpdateUserWorkspaceSettingPB(
|
||||
workspaceId: workspaceId,
|
||||
@@ -132,6 +165,18 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _loadModelList() {
|
||||
AIEventGetAvailableModels().send().then((result) {
|
||||
result.fold((config) {
|
||||
if (!isClosed) {
|
||||
add(SettingsAIEvent.didLoadAvailableModels(config.models));
|
||||
}
|
||||
}, (err) {
|
||||
Log.error(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -145,11 +190,15 @@ class SettingsAIEvent with _$SettingsAIEvent {
|
||||
const factory SettingsAIEvent.refreshMember(WorkspaceMemberPB member) =
|
||||
_RefreshMember;
|
||||
|
||||
const factory SettingsAIEvent.selectModel(AIModelPB model) = _SelectAIModel;
|
||||
const factory SettingsAIEvent.selectModel(String model) = _SelectAIModel;
|
||||
|
||||
const factory SettingsAIEvent.didReceiveUserProfile(
|
||||
UserProfilePB newUserProfile,
|
||||
) = _DidReceiveUserProfile;
|
||||
|
||||
const factory SettingsAIEvent.didLoadAvailableModels(
|
||||
String models,
|
||||
) = _DidLoadAvailableModels;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@@ -157,7 +206,23 @@ class SettingsAIState with _$SettingsAIState {
|
||||
const factory SettingsAIState({
|
||||
required UserProfilePB userProfile,
|
||||
UseAISettingPB? aiSettings,
|
||||
@Default("Default") String selectedAIModel,
|
||||
AFRolePB? currentWorkspaceMemberRole,
|
||||
@Default(["Default"]) List<String> availableModels,
|
||||
@Default(true) bool enableSearchIndexing,
|
||||
}) = _SettingsAIState;
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ModelList {
|
||||
ModelList({
|
||||
required this.models,
|
||||
});
|
||||
|
||||
factory ModelList.fromJson(Map<String, dynamic> json) =>
|
||||
_$ModelListFromJson(json);
|
||||
|
||||
final List<String> models;
|
||||
|
||||
Map<String, dynamic> toJson() => _$ModelListToJson(this);
|
||||
}
|
||||
+6
-34
@@ -4,8 +4,6 @@ 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/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:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -30,18 +28,18 @@ class AIModelSelection extends StatelessWidget {
|
||||
),
|
||||
const Spacer(),
|
||||
Flexible(
|
||||
child: SettingsDropdown<AIModelPB>(
|
||||
child: SettingsDropdown<String>(
|
||||
key: const Key('_AIModelSelection'),
|
||||
onChanged: (model) => context
|
||||
.read<SettingsAIBloc>()
|
||||
.add(SettingsAIEvent.selectModel(model)),
|
||||
selectedOption: state.userProfile.aiModel,
|
||||
options: _availableModels
|
||||
selectedOption: state.selectedAIModel,
|
||||
options: state.availableModels
|
||||
.map(
|
||||
(format) => buildDropdownMenuEntry<AIModelPB>(
|
||||
(model) => buildDropdownMenuEntry<String>(
|
||||
context,
|
||||
value: format,
|
||||
label: _titleForAIModel(format),
|
||||
value: model,
|
||||
label: model,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@@ -54,29 +52,3 @@ class AIModelSelection extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<AIModelPB> _availableModels = [
|
||||
AIModelPB.DefaultModel,
|
||||
AIModelPB.Claude3Opus,
|
||||
AIModelPB.Claude3Sonnet,
|
||||
AIModelPB.GPT4oMini,
|
||||
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.GPT4oMini:
|
||||
return "GPT-4o-mini";
|
||||
case AIModelPB.GPT4o:
|
||||
return "GPT-4o";
|
||||
default:
|
||||
Log.error("Unknown AI model: $model, fallback to default");
|
||||
return "Default";
|
||||
}
|
||||
}
|
||||
@@ -141,6 +141,7 @@ class SettingsDialog extends StatelessWidget {
|
||||
case SettingsPage.ai:
|
||||
if (user.authenticator == AuthenticatorPB.AppFlowyCloud) {
|
||||
return SettingsAIView(
|
||||
key: ValueKey(user.hashCode),
|
||||
userProfile: user,
|
||||
currentWorkspaceMemberRole: currentWorkspaceMemberRole,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
@@ -603,13 +603,13 @@ packages:
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
file_picker:
|
||||
dependency: transitive
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: c904b4ab56d53385563c7c39d8e9fa9af086f91495dfc48717ad84a42c3cf204
|
||||
sha256: "16dc141db5a2ccc6520ebb6a2eb5945b1b09e95085c021d9f914f8ded7f1465c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.7"
|
||||
version: "8.1.4"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -197,6 +197,10 @@ dependency_overrides:
|
||||
commit: fbab857b1b1d209240a146d32f496379b9f62276
|
||||
path: flutter_cache_manager
|
||||
|
||||
# Don't upgrade file_picker until the issue is fixed
|
||||
# https://github.com/miguelpruivo/flutter_file_picker/issues/1652
|
||||
file_picker: 8.1.4
|
||||
|
||||
flutter:
|
||||
generate: true
|
||||
uses-material-design: true
|
||||
|
||||
@@ -384,6 +384,8 @@
|
||||
"askOwnerToUpgradeToProIOS": "مساحة العمل الخاصة بك على وشك النفاد من مساحة التخزين المجانية.",
|
||||
"askOwnerToUpgradeToAIMax": "لقد نفدت الاستجابات المجانية للذكاء الاصطناعي من مساحة العمل الخاصة بك. يرجى مطالبة مالك مساحة العمل الخاصة بك بترقية الخطة أو شراء إضافات الذكاء الاصطناعي",
|
||||
"askOwnerToUpgradeToAIMaxIOS": "مساحة العمل الخاصة بك تفتقر إلى الاستجابات المجانية للذكاء الاصطناعي.",
|
||||
"purchaseAIMax": "لقد نفدت استجابات الصور بالذكاء الاصطناعي من مساحة العمل الخاصة بك. يرجى مطالبة مالك مساحة العمل الخاصة بك بشراء AI Max",
|
||||
"aiImageResponseLimit": "لقد نفدت استجابات الصور الخاصة بالذكاء الاصطناعي.<inlang-LineFeed>\nانتقل إلى الإعدادات -> الخطة -> انقر فوق AI Max للحصول على المزيد من استجابات صور AI",
|
||||
"purchaseStorageSpace": "شراء مساحة تخزين",
|
||||
"singleFileProPlanLimitationDescription": "لقد تجاوزت الحد الأقصى لحجم تحميل الملف المسموح به في الخطة المجانية. يرجى الترقية إلى الخطة الاحترافية لتحميل ملفات أكبر حجمًا",
|
||||
"purchaseAIResponse": "شراء",
|
||||
@@ -1194,6 +1196,7 @@
|
||||
"system": "التكيف مع النظام"
|
||||
},
|
||||
"fontScaleFactor": "عامل مقياس الخط",
|
||||
"displaySize": "حجم العرض",
|
||||
"documentSettings": {
|
||||
"cursorColor": "لون مؤشر المستند",
|
||||
"selectionColor": "لون اختيار المستند",
|
||||
|
||||
@@ -54,8 +54,8 @@
|
||||
"LogInWithGoogle": "چوونە ژوورەوە لە ڕێگەی گووگڵەوە",
|
||||
"LogInWithGithub": "چوونە ژوورەوە لە ڕێگەی گیتهاب",
|
||||
"LogInWithDiscord": "چوونە ژوورەوە لە ڕێگەی دیسکۆرد",
|
||||
"loginAsGuestButtonText": "دەست پێ بکە",
|
||||
"logInWithMagicLink": "بە مەجیک لینک بچۆرە ژوورەوە"
|
||||
"logInWithMagicLink": "بە مەجیک لینک بچۆرە ژوورەوە",
|
||||
"loginAsGuestButtonText": "دەست پێ بکە"
|
||||
},
|
||||
"workspace": {
|
||||
"chooseWorkspace": "هەڵبژاردنی شوێنی کارەکەت",
|
||||
|
||||
@@ -901,7 +901,6 @@
|
||||
"description": "Unlimited AI responses powered by advanced AI models, and 50 AI images per month",
|
||||
"price": "{}",
|
||||
"priceInfo": "Per user per month billed annually"
|
||||
|
||||
},
|
||||
"aiOnDevice": {
|
||||
"title": "AI On-device for Mac",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"loginButtonText": "Ingresar",
|
||||
"loginStartWithAnonymous": "Comience una sesión anónima",
|
||||
"continueAnonymousUser": "Continuar con una sesión anónima",
|
||||
"anonymous": "Anónimo",
|
||||
"buttonText": "Ingresar",
|
||||
"signingInText": "Iniciando sesión...",
|
||||
"forgotPassword": "¿Olvidó su contraseña?",
|
||||
@@ -50,6 +51,8 @@
|
||||
"signInWithGoogle": "Iniciar sesión con Google",
|
||||
"signInWithGithub": "Iniciar sesión con Github",
|
||||
"signInWithDiscord": "Iniciar sesión con Discord",
|
||||
"signInWithApple": "Continuar con Apple",
|
||||
"continueAnotherWay": "Continuar por otro camino",
|
||||
"signUpWithGoogle": "Registrarse con Google",
|
||||
"signUpWithGithub": "Registrarse con Github",
|
||||
"signUpWithDiscord": "Registrarse con Discord",
|
||||
@@ -68,8 +71,12 @@
|
||||
},
|
||||
"workspace": {
|
||||
"chooseWorkspace": "Elige tu espacio de trabajo",
|
||||
"defaultName": "Mi espacio de trabajo",
|
||||
"create": "Crear espacio de trabajo",
|
||||
"new": "Nuevo espacio de trabajo",
|
||||
"learnMore": "Más información",
|
||||
"reset": "Restablecer espacio de trabajo",
|
||||
"renameWorkspace": "Cambiar el nombre del espacio de trabajo",
|
||||
"resetWorkspacePrompt": "Al restablecer el espacio de trabajo se eliminarán todas las páginas y datos que contiene. ¿Está seguro de que desea restablecer el espacio de trabajo? Alternativamente, puede comunicarse con el equipo de soporte para restaurar el espacio de trabajo.",
|
||||
"hint": "Espacio de trabajo",
|
||||
"notFoundError": "Espacio de trabajo no encontrado",
|
||||
@@ -105,7 +112,9 @@
|
||||
"html": "HTML",
|
||||
"clipboard": "Copiar al portapapeles",
|
||||
"csv": "CSV",
|
||||
"copyLink": "Copiar enlace"
|
||||
"copyLink": "Copiar enlace",
|
||||
"publish": "Publicar",
|
||||
"publishTab": "Publicar"
|
||||
},
|
||||
"moreAction": {
|
||||
"small": "pequeño",
|
||||
@@ -118,7 +127,9 @@
|
||||
"charCount": "Número de caracteres : {}",
|
||||
"createdAt": "Creado: {}",
|
||||
"deleteView": "Borrar",
|
||||
"duplicateView": "Duplicar"
|
||||
"duplicateView": "Duplicar",
|
||||
"createdAtLabel": "Creado: ",
|
||||
"syncedAtLabel": "Sincronizado: "
|
||||
},
|
||||
"importPanel": {
|
||||
"textAndMarkdown": "Texto y Markdown",
|
||||
@@ -136,7 +147,8 @@
|
||||
"openNewTab": "Abrir en una pestaña nueva",
|
||||
"moveTo": "Mover a",
|
||||
"addToFavorites": "Añadira los favoritos",
|
||||
"copyLink": "Copiar Enlace"
|
||||
"copyLink": "Copiar Enlace",
|
||||
"move": "Mover"
|
||||
},
|
||||
"blankPageTitle": "Página en blanco",
|
||||
"newPageText": "Nueva página",
|
||||
@@ -149,16 +161,31 @@
|
||||
"relatedQuestion": "Relacionado",
|
||||
"serverUnavailable": "Servicio temporalmente no disponible. Por favor, inténtelo de nuevo más tarde.",
|
||||
"aiServerUnavailable": "🌈 ¡Uh-oh! 🌈. Un unicornio se comió nuestra respuesta. ¡Por favor, intenta de nuevo!",
|
||||
"retry": "Rever",
|
||||
"clickToRetry": "Haga clic para volver a intentarlo",
|
||||
"regenerateAnswer": "Regenerar",
|
||||
"question1": "Cómo utilizar Kanban para gestionar tareas",
|
||||
"question2": "Explica el método GTD",
|
||||
"question3": "¿Por qué usar Rust?",
|
||||
"aiMistakePrompt": "La IA puede cometer errores. Consulta información importante."
|
||||
"aiMistakePrompt": "La IA puede cometer errores. Consulta información importante.",
|
||||
"referenceSource": {
|
||||
"one": "Se encontró {count} fuente",
|
||||
"other": "Se encontraron {count} fuentes"
|
||||
},
|
||||
"regenerate": "Intentar otra vez",
|
||||
"addToNewPage": "Crear nueva página",
|
||||
"changeFormat": {
|
||||
"textOnly": "Texto",
|
||||
"text": "Párrafo"
|
||||
},
|
||||
"selectBanner": {
|
||||
"saveButton": "Añadir …"
|
||||
}
|
||||
},
|
||||
"trash": {
|
||||
"text": "Papelera",
|
||||
"restoreAll": "Recuperar todo",
|
||||
"restore": "Restaurar",
|
||||
"deleteAll": "Eliminar todo",
|
||||
"pageHeader": {
|
||||
"fileName": "Nombre de archivo",
|
||||
@@ -204,7 +231,8 @@
|
||||
"moreButtonToolTip": "Eliminar, renombrar y más...",
|
||||
"addPageTooltip": "Inserta una página",
|
||||
"defaultNewPageName": "Sin Título",
|
||||
"renameDialog": "Renombrar"
|
||||
"renameDialog": "Renombrar",
|
||||
"pageNameSuffix": "Copiar"
|
||||
},
|
||||
"noPagesInside": "No hay páginas dentro",
|
||||
"toolbar": {
|
||||
@@ -260,7 +288,10 @@
|
||||
"emptyRecent": "Sin documentos recientes",
|
||||
"favoriteSpace": "Favoritos",
|
||||
"RecentSpace": "Reciente",
|
||||
"Spaces": "Espacios"
|
||||
"Spaces": "Espacios",
|
||||
"aiImageResponseLimit": "Se ha quedado sin respuestas de imágenes de IA.\n\nVaya a Configuración -> Plan -> Haga clic en AI Max para obtener más respuestas de imágenes de IA",
|
||||
"purchaseStorageSpace": "Comprar espacio de almacenamiento",
|
||||
"purchaseAIResponse": "Compra "
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
@@ -294,6 +325,7 @@
|
||||
"upload": "Subir",
|
||||
"edit": "Editar",
|
||||
"delete": "Borrar",
|
||||
"copy": "Copiar",
|
||||
"duplicate": "Duplicar",
|
||||
"putback": "Volver",
|
||||
"update": "Actualizar",
|
||||
@@ -305,6 +337,7 @@
|
||||
"helpCenter": "Centro de ayuda",
|
||||
"add": "Añadir",
|
||||
"yes": "Si",
|
||||
"no": "No",
|
||||
"clear": "Limpiar",
|
||||
"remove": "Eliminar",
|
||||
"dontRemove": "no quitar",
|
||||
@@ -319,7 +352,12 @@
|
||||
"signInDiscord": "Iniciar sesión con discordia",
|
||||
"more": "Más",
|
||||
"create": "Crear",
|
||||
"close": "Cerca"
|
||||
"close": "Cerca",
|
||||
"next": "Próximo",
|
||||
"previous": "Anterior",
|
||||
"submit": "Entregar",
|
||||
"download": "Descargar",
|
||||
"backToHome": "Volver a Inicio"
|
||||
},
|
||||
"label": {
|
||||
"welcome": "¡Bienvenido!",
|
||||
@@ -343,6 +381,9 @@
|
||||
},
|
||||
"settings": {
|
||||
"title": "Ajustes",
|
||||
"popupMenuItem": {
|
||||
"settings": "Ajustes"
|
||||
},
|
||||
"accountPage": {
|
||||
"menuLabel": "Mi cuenta",
|
||||
"title": "Mi cuenta",
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
"appName": "AppFlowy",
|
||||
"defaultUsername": "من",
|
||||
"welcomeText": "به @:appName خوش آمدید",
|
||||
"welcomeTo": "خوش آمدید به",
|
||||
"githubStarText": "به گیتهاب ما ستاره دهید",
|
||||
"subscribeNewsletterText": "اشتراک در خبرنامه",
|
||||
"letsGoButtonText": "شروع کنید",
|
||||
"title": "عنوان",
|
||||
"youCanAlso": "همچنین میتوانید",
|
||||
"and": "و",
|
||||
"failedToOpenUrl": "خطا در بازکردن نشانی وب: {}",
|
||||
"blockActions": {
|
||||
"addBelowTooltip": "برای افزودن در زیر کلیک کنید",
|
||||
"addAboveCmd": "Alt+click",
|
||||
@@ -32,19 +34,47 @@
|
||||
"signIn": {
|
||||
"loginTitle": "ورود به @:appName",
|
||||
"loginButtonText": "ورود",
|
||||
"loginStartWithAnonymous": "ادامه دادن با یک جلسه ناشناس",
|
||||
"continueAnonymousUser": "ادامه دادن به صورت کاربر مهمان",
|
||||
"anonymous": "ناشناس",
|
||||
"buttonText": "ورود",
|
||||
"signingInText": "در حال ورود...",
|
||||
"forgotPassword": "رمز عبور را فراموش کرده اید؟",
|
||||
"emailHint": "ایمیل",
|
||||
"passwordHint": "رمز عبور",
|
||||
"dontHaveAnAccount": "آیا حساب کاربری ندارید؟",
|
||||
"createAccount": "ساخت حساب کاربری",
|
||||
"repeatPasswordEmptyError": "تکرار رمز عبور نمیتواند خالی باشد",
|
||||
"unmatchedPasswordError": "تکرار رمز عبور مشابه رمز عبور نیست",
|
||||
"syncPromptMessage": "همگام سازی داده ها ممکن است کمی طول بکشد. لطفا این صفحه را نبندید",
|
||||
"or": "یا",
|
||||
"signInWithGoogle": "ادامه دادن با گوگل",
|
||||
"signInWithGithub": "ادامه دادن با گیتهاب",
|
||||
"signInWithDiscord": "ادامه دادن با دیسکورد",
|
||||
"signInWithApple": "ادامه دادن با اپل",
|
||||
"continueAnotherWay": "ادامه دادن از طریق دیگر",
|
||||
"signUpWithGoogle": "ثبت نام با گوگل",
|
||||
"signUpWithGithub": "ثبت نام با گیتهاب",
|
||||
"signUpWithDiscord": "ثبت نام با دیسکورد",
|
||||
"signInWith": "ثبت نام با:",
|
||||
"signInWithEmail": "ادامه دادن با ایمیل",
|
||||
"signInWithMagicLink": "ادامه",
|
||||
"pleaseInputYourEmail": "لطفا آدرس ایمیل خود را وارد کنید",
|
||||
"settings": "تنظیمات",
|
||||
"invalidEmail": "لطفا یک آدرس ایمیل معتبر وارد کنید",
|
||||
"alreadyHaveAnAccount": "حساب کاربری دارید؟",
|
||||
"logIn": "ورود",
|
||||
"generalError": "مشکلی پیش آمد. لطفاً بعداً دوباره امتحان کنید",
|
||||
"loginAsGuestButtonText": "شروع کنید"
|
||||
},
|
||||
"workspace": {
|
||||
"chooseWorkspace": "فضای کار خود را انتخاب کنید",
|
||||
"defaultName": "فضای کار من",
|
||||
"create": "ایجاد فضای کار",
|
||||
"new": "فضای کار جدید",
|
||||
"learnMore": "بیشتر بدانید",
|
||||
"renameWorkspace": "حذف فضای کار",
|
||||
"workspaceNameCannotBeEmpty": "اسم فضای کار نمیتواند خالی باشد",
|
||||
"hint": "فضای کار",
|
||||
"notFoundError": "فضای کاری پیدا نشد"
|
||||
},
|
||||
|
||||
@@ -72,8 +72,8 @@
|
||||
"LogInWithGoogle": "Se connecter avec Google",
|
||||
"LogInWithGithub": "Se connecter avec Github",
|
||||
"LogInWithDiscord": "Se connecter avec Discord",
|
||||
"loginAsGuestButtonText": "Commencer",
|
||||
"logInWithMagicLink": "Connectez-vous avec Magic Link"
|
||||
"logInWithMagicLink": "Connectez-vous avec Magic Link",
|
||||
"loginAsGuestButtonText": "Commencer"
|
||||
},
|
||||
"workspace": {
|
||||
"chooseWorkspace": "Choisissez votre espace de travail",
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"appName": "Appflowy",
|
||||
"defaultUsername": "Liom",
|
||||
"welcomeText": "Fáilte go @:appName",
|
||||
"welcomeTo": "Fáilte chuig",
|
||||
"githubStarText": "Réalta ar GitHub",
|
||||
"subscribeNewsletterText": "Liostáil le Nuachtlitir",
|
||||
"letsGoButtonText": "Tús Tapa",
|
||||
"title": "Teideal",
|
||||
"youCanAlso": "Is féidir leat freisin",
|
||||
"and": "agus",
|
||||
"failedToOpenUrl": "Theip ar oscailt an url: {}",
|
||||
"blockActions": {
|
||||
"addBelowTooltip": "Cliceáil chun cur leis thíos",
|
||||
"addAboveCmd": "Alt+cliceáil",
|
||||
"addAboveMacCmd": "Option+cliceáil",
|
||||
"addAboveTooltip": "a chur thuas",
|
||||
"dragTooltip": "Tarraing chun bogadh",
|
||||
"openMenuTooltip": "Cliceáil chun an roghchlár a oscailt"
|
||||
},
|
||||
"signUp": {
|
||||
"buttonText": "Cláraigh",
|
||||
"title": "Cláraigh le @:appName",
|
||||
"getStartedText": "Faigh Tosaigh",
|
||||
"emptyPasswordError": "Ní féidir le pasfhocal a bheith folamh",
|
||||
"repeatPasswordEmptyError": "Ní féidir an pasfhocal athdhéanta a bheith folamh",
|
||||
"unmatchedPasswordError": "Ní ionann pasfhocal athdhéanta agus pasfhocal",
|
||||
"alreadyHaveAnAccount": "An bhfuil cuntas agat cheana féin?",
|
||||
"emailHint": "Ríomhphost",
|
||||
"passwordHint": "Pasfhocal",
|
||||
"repeatPasswordHint": "Déan pasfhocal arís",
|
||||
"signUpWith": "Cláraigh le:"
|
||||
},
|
||||
"signIn": {
|
||||
"loginTitle": "Logáil isteach ar @:appName",
|
||||
"loginButtonText": "Logáil isteach",
|
||||
"loginStartWithAnonymous": "Lean ar aghaidh le seisiún gan ainm",
|
||||
"continueAnonymousUser": "Lean ar aghaidh le seisiún gan ainm",
|
||||
"anonymous": "Gan ainm",
|
||||
"buttonText": "Sínigh Isteach",
|
||||
"signingInText": "Ag síniú isteach...",
|
||||
"forgotPassword": "Pasfhocal Dearmadta?",
|
||||
"emailHint": "Ríomhphost",
|
||||
"passwordHint": "Pasfhocal",
|
||||
"dontHaveAnAccount": "Nach bhfuil cuntas agat?",
|
||||
"createAccount": "Cruthaigh cuntas",
|
||||
"repeatPasswordEmptyError": "Ní féidir an pasfhocal athdhéanta a bheith folamh",
|
||||
"unmatchedPasswordError": "Ní hionann pasfhocal athdhéanta agus pasfhocal",
|
||||
"syncPromptMessage": "Seans go dtógfaidh sé tamall na sonraí a shioncronú. Ná dún an leathanach seo, le do thoil",
|
||||
"or": "NÓ",
|
||||
"signInWithGoogle": "Lean ar aghaidh le Google",
|
||||
"signInWithGithub": "Lean ar aghaidh le GitHub",
|
||||
"signInWithDiscord": "Lean ar aghaidh le Discord",
|
||||
"signInWithApple": "Lean ar aghaidh le Apple",
|
||||
"continueAnotherWay": "Lean ar aghaidh ar bhealach eile",
|
||||
"signUpWithGoogle": "Cláraigh le Google",
|
||||
"signUpWithGithub": "Cláraigh le Github",
|
||||
"signUpWithDiscord": "Cláraigh le Discord",
|
||||
"signInWith": "Lean ar aghaidh le:",
|
||||
"signInWithEmail": "Lean ar aghaidh le Ríomhphost",
|
||||
"signInWithMagicLink": "Lean ort",
|
||||
"signUpWithMagicLink": "Cláraigh le Magic Link",
|
||||
"pleaseInputYourEmail": "Cuir isteach do sheoladh ríomhphoist",
|
||||
"settings": "Socruithe",
|
||||
"magicLinkSent": "Magic Link seolta!",
|
||||
"invalidEmail": "Cuir isteach seoladh ríomhphoist bailí",
|
||||
"alreadyHaveAnAccount": "An bhfuil cuntas agat cheana féin?",
|
||||
"logIn": "Logáil isteach",
|
||||
"generalError": "Chuaigh rud éigin mícheart. Bain triail eile as ar ball",
|
||||
"limitRateError": "Ar chúiseanna slándála, ní féidir leat nasc draíochta a iarraidh ach gach 60 soicind"
|
||||
},
|
||||
"workspace": {
|
||||
"chooseWorkspace": "Roghnaigh do spás oibre",
|
||||
"defaultName": "Mo Spás Oibre",
|
||||
"create": "Cruthaigh spás oibre",
|
||||
"new": "Spás oibre nua",
|
||||
"importFromNotion": "Iompórtáil ó Notion",
|
||||
"learnMore": "Foghlaim níos mó",
|
||||
"reset": "Athshocraigh spás oibre",
|
||||
"renameWorkspace": "Athainmnigh spás oibre",
|
||||
"workspaceNameCannotBeEmpty": "Ní féidir leis an ainm spás oibre a bheith folamh",
|
||||
"hint": "spás oibre",
|
||||
"notFoundError": "Spás oibre gan aimsiú",
|
||||
"errorActions": {
|
||||
"reportIssue": "Tuairiscigh saincheist",
|
||||
"reportIssueOnGithub": "Tuairiscigh ceist faoi Github",
|
||||
"exportLogFiles": "Easpórtáil comhaid logáil",
|
||||
"reachOut": "Bhaint amach le Discord"
|
||||
},
|
||||
"menuTitle": "Spásanna oibre",
|
||||
"createSuccess": "Cruthaíodh spás oibre go rathúil",
|
||||
"leaveCurrentWorkspace": "Fág spás óibre"
|
||||
},
|
||||
"shareAction": {
|
||||
"buttonText": "Comhroinn",
|
||||
"workInProgress": "Ag teacht go luath",
|
||||
"markdown": "Markdown",
|
||||
"html": "HTML",
|
||||
"clipboard": "Cóipeáil chuig an ngearrthaisce",
|
||||
"csv": "CSV",
|
||||
"copyLink": "Cóipeáil nasc",
|
||||
"publishToTheWeb": "Foilsigh don Ghréasán",
|
||||
"publishToTheWebHint": "Cruthaigh suíomh Gréasáin le AppFlowy",
|
||||
"publish": "Foilsigh",
|
||||
"unPublish": "Dífhoilsiú",
|
||||
"visitSite": "Tabhair cuairt ar an suíomh",
|
||||
"publishTab": "Foilsigh",
|
||||
"shareTab": "Comhroinn"
|
||||
},
|
||||
"moreAction": {
|
||||
"small": "beag",
|
||||
"medium": "meánach",
|
||||
"large": "mór",
|
||||
"fontSize": "Clómhéid",
|
||||
"import": "Iompórtáil",
|
||||
"createdAt": "Cruthaithe: {}",
|
||||
"deleteView": "Scrios"
|
||||
}
|
||||
}
|
||||
@@ -206,13 +206,13 @@
|
||||
"addBlockBelow": "Tambahkan blok di bawah ini"
|
||||
},
|
||||
"sideBar": {
|
||||
"closeSidebar": "Close sidebar",
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Tutup sidebar",
|
||||
"openSidebar": "Buka sidebar",
|
||||
"personal": "Pribadi",
|
||||
"favorites": "Favorit",
|
||||
"clickToHidePersonal": "Klik untuk menutup seksi pribadi",
|
||||
"clickToHideFavorites": "Klik untuk menutup seksi favorit",
|
||||
"addAPage": "Tambah sebuah page"
|
||||
"clickToHidePersonal": "Klik untuk menutup Pribadi",
|
||||
"clickToHideFavorites": "Klik untuk menutup Favorit",
|
||||
"addAPage": "Tambah halaman baru"
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
@@ -305,28 +305,28 @@
|
||||
"appearance": {
|
||||
"resetSetting": "Mengatur ulang pengaturan ini",
|
||||
"fontFamily": {
|
||||
"label": "Keluarga Fon",
|
||||
"search": "Mencari"
|
||||
"label": "Jenis Font",
|
||||
"search": "Cari"
|
||||
},
|
||||
"themeMode": {
|
||||
"label": "Theme Mode",
|
||||
"light": "Mode Terang",
|
||||
"dark": "Mode Gelap",
|
||||
"system": "Adapt to System"
|
||||
"label": "Tema",
|
||||
"light": "Terang",
|
||||
"dark": "Gelap",
|
||||
"system": "Sesuai Sistem"
|
||||
},
|
||||
"layoutDirection": {
|
||||
"label": "Arah Layout",
|
||||
"hint": "Mengontrol aliran konten pada layar Anda, dari kiri ke kanan atau kanan ke kiri.",
|
||||
"ltr": "LTR",
|
||||
"rtl": "RTL"
|
||||
"label": "Arah Tampilan",
|
||||
"hint": "Mengatur arah tampilan konten, apakah dari kiri ke kanan atau kanan ke kiri.",
|
||||
"ltr": "Kiri ke Kanan",
|
||||
"rtl": "Kanan ke Kiri"
|
||||
},
|
||||
"textDirection": {
|
||||
"label": "Arah teks default",
|
||||
"hint": "Menentukan apakah teks harus dimulai dari kiri atau kanan sebagai default.",
|
||||
"ltr": "LTR",
|
||||
"rtl": "RTL",
|
||||
"auto": "AUTO",
|
||||
"fallback": "Sama seperti arah layout"
|
||||
"label": "Arah Teks Bawaan",
|
||||
"hint": "Atur arah teks bawaan, apakah dari kiri ke kanan atau kanan ke kiri.",
|
||||
"ltr": "Kiri ke Kanan",
|
||||
"rtl": "Kanan ke Kiri",
|
||||
"auto": "Otomatis",
|
||||
"fallback": "Sesuai Arah Tampilan"
|
||||
},
|
||||
"themeUpload": {
|
||||
"button": "Mengunggah",
|
||||
|
||||
@@ -72,8 +72,8 @@
|
||||
"LogInWithGoogle": "使用 Google 登录",
|
||||
"LogInWithGithub": "使用 Github 登录",
|
||||
"LogInWithDiscord": "使用 Discord 登录",
|
||||
"loginAsGuestButtonText": "开始使用",
|
||||
"logInWithMagicLink": "使用魔法链接登录"
|
||||
"logInWithMagicLink": "使用魔法链接登录",
|
||||
"loginAsGuestButtonText": "开始使用"
|
||||
},
|
||||
"workspace": {
|
||||
"chooseWorkspace": "选择您的工作区",
|
||||
|
||||
Generated
+28
-14
@@ -163,7 +163,7 @@ checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -183,7 +183,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -786,7 +786,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@@ -843,7 +843,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
@@ -856,7 +856,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@@ -1128,7 +1128,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1153,7 +1153,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -1400,7 +1400,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"phf 0.8.0",
|
||||
"phf 0.11.2",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@@ -1548,7 +1548,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -2970,7 +2970,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@@ -2987,7 +2987,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@@ -3598,7 +3598,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -4624,7 +4624,7 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_macros 0.8.0",
|
||||
"phf_shared 0.8.0",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
@@ -4644,6 +4644,7 @@ version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_macros 0.11.3",
|
||||
"phf_shared 0.11.2",
|
||||
]
|
||||
|
||||
@@ -4711,6 +4712,19 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.2",
|
||||
"phf_shared 0.11.2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.94",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.8.0"
|
||||
@@ -6140,7 +6154,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=4a26572a4e43714def9b362d444c640fdf1bc0d9#4a26572a4e43714def9b362d444c640fdf1bc0d9"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c80157a687397a38c7390ec3d7fee034b93db963#c80157a687397a38c7390ec3d7fee034b93db963"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
||||
@@ -103,8 +103,8 @@ dashmap = "6.0.1"
|
||||
# Run the script.add_workspace_members:
|
||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||
# ⚠️⚠️⚠️️
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "4a26572a4e43714def9b362d444c640fdf1bc0d9" }
|
||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "4a26572a4e43714def9b362d444c640fdf1bc0d9" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c80157a687397a38c7390ec3d7fee034b93db963" }
|
||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c80157a687397a38c7390ec3d7fee034b93db963" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use bytes::Bytes;
|
||||
pub use client_api::entity::ai_dto::{
|
||||
AppFlowyOfflineAI, CompleteTextParams, CompletionMetadata, CompletionType, CreateChatContext,
|
||||
LLMModel, LocalAIConfig, ModelInfo, OutputContent, OutputLayout, RelatedQuestion,
|
||||
LLMModel, LocalAIConfig, ModelInfo, ModelList, OutputContent, OutputLayout, RelatedQuestion,
|
||||
RepeatedRelatedQuestion, ResponseFormat, StringOrMessage,
|
||||
};
|
||||
pub use client_api::entity::billing_dto::SubscriptionPlan;
|
||||
@@ -54,7 +54,7 @@ pub trait ChatCloudService: Send + Sync + 'static {
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
message_id: i64,
|
||||
format: ResponseFormat,
|
||||
format: Option<ResponseFormat>,
|
||||
) -> Result<StreamAnswer, FlowyError>;
|
||||
|
||||
async fn get_answer(
|
||||
@@ -119,4 +119,6 @@ pub trait ChatCloudService: Send + Sync + 'static {
|
||||
chat_id: &str,
|
||||
params: UpdateChatParams,
|
||||
) -> Result<(), FlowyError>;
|
||||
|
||||
async fn get_available_models(&self, workspace_id: &str) -> Result<ModelList, FlowyError>;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use std::collections::HashMap;
|
||||
|
||||
use appflowy_plugin::manager::PluginManager;
|
||||
use dashmap::DashMap;
|
||||
use flowy_ai_pub::cloud::{ChatCloudService, ChatSettings, UpdateChatParams};
|
||||
use flowy_ai_pub::cloud::{ChatCloudService, ChatSettings, ModelList, UpdateChatParams};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use flowy_sqlite::kv::KVStorePreferences;
|
||||
use flowy_sqlite::DBConnection;
|
||||
@@ -241,6 +241,15 @@ impl AIManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_available_models(&self) -> FlowyResult<ModelList> {
|
||||
let workspace_id = self.user_service.workspace_id()?;
|
||||
let list = self
|
||||
.cloud_service_wm
|
||||
.get_available_models(&workspace_id)
|
||||
.await?;
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
pub async fn get_or_create_chat_instance(&self, chat_id: &str) -> Result<Arc<Chat>, FlowyError> {
|
||||
let chat = self.chats.get(chat_id).as_deref().cloned();
|
||||
match chat {
|
||||
|
||||
@@ -138,7 +138,7 @@ impl Chat {
|
||||
// Save message to disk
|
||||
save_and_notify_message(uid, &self.chat_id, &self.user_service, question.clone())?;
|
||||
|
||||
let format = params.format.clone().unwrap_or_default().into();
|
||||
let format = params.format.clone().map(Into::into);
|
||||
|
||||
self.stream_response(
|
||||
params.answer_stream_port,
|
||||
@@ -171,7 +171,7 @@ impl Chat {
|
||||
.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
self.stream_buffer.lock().await.clear();
|
||||
|
||||
let format = format.unwrap_or_default().into();
|
||||
let format = format.map(Into::into);
|
||||
|
||||
let answer_stream_buffer = self.stream_buffer.clone();
|
||||
let uid = self.user_service.user_id()?;
|
||||
@@ -196,7 +196,7 @@ impl Chat {
|
||||
uid: i64,
|
||||
workspace_id: String,
|
||||
question_id: i64,
|
||||
format: ResponseFormat,
|
||||
format: Option<ResponseFormat>,
|
||||
) {
|
||||
let stop_stream = self.stop_stream.clone();
|
||||
let chat_id = self.chat_id.clone();
|
||||
@@ -259,6 +259,10 @@ impl Chat {
|
||||
let _ = answer_sink
|
||||
.send("AI_IMAGE_RESPONSE_LIMIT".to_string())
|
||||
.await;
|
||||
} else if err.is_ai_max_required() {
|
||||
let _ = answer_sink
|
||||
.send(format!("AI_MAX_REQUIRED:{}", err.msg))
|
||||
.await;
|
||||
} else {
|
||||
let _ = answer_sink.send(format!("error:{}", err)).await;
|
||||
}
|
||||
|
||||
@@ -104,8 +104,10 @@ impl CompletionTask {
|
||||
custom_prompt: None,
|
||||
metadata: Some(CompletionMetadata {
|
||||
object_id: self.context.object_id,
|
||||
workspace_id: None,
|
||||
rag_ids: Some(self.context.rag_ids),
|
||||
}),
|
||||
format: Default::default(),
|
||||
};
|
||||
|
||||
info!("start completion: {:?}", params);
|
||||
|
||||
@@ -182,6 +182,12 @@ pub struct ChatMessageListPB {
|
||||
pub total: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf, Validate, Clone, Debug)]
|
||||
pub struct ModelConfigPB {
|
||||
#[pb(index = 1)]
|
||||
pub models: String,
|
||||
}
|
||||
|
||||
impl From<RepeatedChatMessage> for ChatMessageListPB {
|
||||
fn from(repeated_chat_message: RepeatedChatMessage) -> Self {
|
||||
let messages = repeated_chat_message
|
||||
|
||||
@@ -13,6 +13,7 @@ use flowy_ai_pub::cloud::{ChatMessageMetadata, ChatMessageType, ChatRAGData, Con
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
|
||||
use lib_infra::isolate_stream::IsolateSink;
|
||||
use serde_json::json;
|
||||
use std::sync::{Arc, Weak};
|
||||
use tracing::trace;
|
||||
use validator::Validate;
|
||||
@@ -107,6 +108,22 @@ pub(crate) async fn regenerate_response_handler(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn get_available_model_list_handler(
|
||||
ai_manager: AFPluginState<Weak<AIManager>>,
|
||||
) -> DataResult<ModelConfigPB, FlowyError> {
|
||||
let ai_manager = upgrade_ai_manager(ai_manager)?;
|
||||
let available_models = ai_manager.get_available_models().await?;
|
||||
let models = available_models
|
||||
.models
|
||||
.into_iter()
|
||||
.map(|m| m.name)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
let models = serde_json::to_string(&json!({"models": models}))?;
|
||||
data_result_ok(ModelConfigPB { models })
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||
pub(crate) async fn load_prev_message_handler(
|
||||
data: AFPluginData<LoadPrevChatMessagePB>,
|
||||
|
||||
@@ -60,6 +60,10 @@ pub fn init(ai_manager: Weak<AIManager>) -> AFPlugin {
|
||||
.event(AIEvent::GetChatSettings, get_chat_settings_handler)
|
||||
.event(AIEvent::UpdateChatSettings, update_chat_settings_handler)
|
||||
.event(AIEvent::RegenerateResponse, regenerate_response_handler)
|
||||
.event(
|
||||
AIEvent::GetAvailableModels,
|
||||
get_available_model_list_handler,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||
@@ -154,4 +158,7 @@ pub enum AIEvent {
|
||||
|
||||
#[event(input = "RegenerateResponsePB")]
|
||||
RegenerateResponse = 27,
|
||||
|
||||
#[event(output = "ModelConfigPB")]
|
||||
GetAvailableModels = 28,
|
||||
}
|
||||
@@ -10,9 +10,9 @@ use std::collections::HashMap;
|
||||
|
||||
use flowy_ai_pub::cloud::{
|
||||
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, ChatSettings,
|
||||
CompleteTextParams, LocalAIConfig, MessageCursor, RelatedQuestion, RepeatedChatMessage,
|
||||
RepeatedRelatedQuestion, ResponseFormat, StreamAnswer, StreamComplete, SubscriptionPlan,
|
||||
UpdateChatParams,
|
||||
CompleteTextParams, LocalAIConfig, MessageCursor, ModelList, RelatedQuestion,
|
||||
RepeatedChatMessage, RepeatedRelatedQuestion, ResponseFormat, StreamAnswer, StreamComplete,
|
||||
SubscriptionPlan, UpdateChatParams,
|
||||
};
|
||||
use flowy_error::{FlowyError, FlowyResult};
|
||||
use futures::{stream, Sink, StreamExt, TryStreamExt};
|
||||
@@ -156,7 +156,7 @@ impl ChatCloudService for AICloudServiceMiddleware {
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
question_id: i64,
|
||||
format: ResponseFormat,
|
||||
format: Option<ResponseFormat>,
|
||||
) -> Result<StreamAnswer, FlowyError> {
|
||||
if self.local_llm_controller.is_running() {
|
||||
let row = self.get_message_record(question_id)?;
|
||||
@@ -353,4 +353,8 @@ impl ChatCloudService for AICloudServiceMiddleware {
|
||||
.update_chat_settings(workspace_id, chat_id, params)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_available_models(&self, workspace_id: &str) -> Result<ModelList, FlowyError> {
|
||||
self.cloud_service.get_available_models(workspace_id).await
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ use collab_integrate::collab_builder::{
|
||||
};
|
||||
use flowy_ai_pub::cloud::{
|
||||
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, ChatSettings,
|
||||
CompleteTextParams, LocalAIConfig, MessageCursor, RepeatedChatMessage, ResponseFormat,
|
||||
CompleteTextParams, LocalAIConfig, MessageCursor, ModelList, RepeatedChatMessage, ResponseFormat,
|
||||
StreamAnswer, StreamComplete, SubscriptionPlan, UpdateChatParams,
|
||||
};
|
||||
use flowy_database_pub::cloud::{
|
||||
@@ -704,7 +704,7 @@ impl ChatCloudService for ServerProvider {
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
message_id: i64,
|
||||
format: ResponseFormat,
|
||||
format: Option<ResponseFormat>,
|
||||
) -> Result<StreamAnswer, FlowyError> {
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let chat_id = chat_id.to_string();
|
||||
@@ -838,6 +838,14 @@ impl ChatCloudService for ServerProvider {
|
||||
.update_chat_settings(workspace_id, chat_id, params)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_available_models(&self, workspace_id: &str) -> Result<ModelList, FlowyError> {
|
||||
self
|
||||
.get_server()?
|
||||
.chat_service()
|
||||
.get_available_models(workspace_id)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -362,6 +362,9 @@ pub enum ErrorCode {
|
||||
|
||||
#[error("AI Image Response limit exceeded")]
|
||||
AIImageResponseLimitExceeded = 124,
|
||||
|
||||
#[error("AI Max Required")]
|
||||
AIMaxRequired = 125,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::code::ErrorCode;
|
||||
pub type FlowyResult<T> = anyhow::Result<T, FlowyError>;
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf, Error)]
|
||||
#[error("{msg}")]
|
||||
#[error("code:{code}, message:{msg}")]
|
||||
pub struct FlowyError {
|
||||
#[pb(index = 1)]
|
||||
pub code: ErrorCode,
|
||||
@@ -95,6 +95,10 @@ impl FlowyError {
|
||||
self.code == ErrorCode::AIImageResponseLimitExceeded
|
||||
}
|
||||
|
||||
pub fn is_ai_max_required(&self) -> bool {
|
||||
self.code == ErrorCode::AIMaxRequired
|
||||
}
|
||||
|
||||
static_flowy_error!(internal, ErrorCode::Internal);
|
||||
static_flowy_error!(record_not_found, ErrorCode::RecordNotFound);
|
||||
static_flowy_error!(workspace_initialize, ErrorCode::WorkspaceInitializeError);
|
||||
|
||||
@@ -25,6 +25,7 @@ impl From<AppResponseError> for FlowyError {
|
||||
AppErrorCode::WorkspaceMemberLimitExceeded => ErrorCode::WorkspaceMemberLimitExceeded,
|
||||
AppErrorCode::AIResponseLimitExceeded => ErrorCode::AIResponseLimitExceeded,
|
||||
AppErrorCode::AIImageResponseLimitExceeded => ErrorCode::AIImageResponseLimitExceeded,
|
||||
AppErrorCode::AIMaxRequired => ErrorCode::AIMaxRequired,
|
||||
AppErrorCode::FileStorageLimitExceeded => ErrorCode::FileStorageLimitExceeded,
|
||||
AppErrorCode::SingleUploadLimitExceeded => ErrorCode::SingleUploadLimitExceeded,
|
||||
AppErrorCode::CustomNamespaceDisabled => ErrorCode::CustomNamespaceRequirePlanUpgrade,
|
||||
|
||||
@@ -8,7 +8,7 @@ use client_api::entity::chat_dto::{
|
||||
};
|
||||
use flowy_ai_pub::cloud::{
|
||||
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, ChatSettings, LocalAIConfig,
|
||||
StreamAnswer, StreamComplete, SubscriptionPlan, UpdateChatParams,
|
||||
ModelList, StreamAnswer, StreamComplete, SubscriptionPlan, UpdateChatParams,
|
||||
};
|
||||
use flowy_error::FlowyError;
|
||||
use futures_util::{StreamExt, TryStreamExt};
|
||||
@@ -100,8 +100,9 @@ where
|
||||
workspace_id: &str,
|
||||
chat_id: &str,
|
||||
message_id: i64,
|
||||
format: ResponseFormat,
|
||||
format: Option<ResponseFormat>,
|
||||
) -> Result<StreamAnswer, FlowyError> {
|
||||
let format = format.unwrap_or_default();
|
||||
trace!(
|
||||
"stream_answer: workspace_id={}, chat_id={}, format={:?}",
|
||||
workspace_id,
|
||||
@@ -270,4 +271,13 @@ where
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_available_models(&self, workspace_id: &str) -> Result<ModelList, FlowyError> {
|
||||
let list = self
|
||||
.inner
|
||||
.try_get_client()?
|
||||
.get_model_list(workspace_id)
|
||||
.await?;
|
||||
Ok(list)
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ use client_api::entity::{
|
||||
use client_api::entity::{QueryCollab, QueryCollabParams};
|
||||
use client_api::{Client, ClientConfiguration};
|
||||
use collab_entity::{CollabObject, CollabType};
|
||||
use tracing::instrument;
|
||||
use tracing::{instrument, trace};
|
||||
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_user_pub::cloud::{UserCloudService, UserCollabParams, UserUpdate, UserUpdateReceiver};
|
||||
@@ -586,6 +586,7 @@ where
|
||||
workspace_id: &str,
|
||||
workspace_settings: AFWorkspaceSettingsChange,
|
||||
) -> Result<AFWorkspaceSettings, FlowyError> {
|
||||
trace!("Sync workspace settings: {:?}", workspace_settings);
|
||||
let workspace_id = workspace_id.to_string();
|
||||
let try_get_client = self.server.try_get_client();
|
||||
let client = try_get_client?;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -7,7 +6,6 @@ use crate::af_cloud::define::ServerUser;
|
||||
use anyhow::Error;
|
||||
use arc_swap::ArcSwap;
|
||||
use client_api::collab_sync::ServerCollabMessage;
|
||||
use client_api::entity::ai_dto::AIModel;
|
||||
use client_api::entity::UserMessage;
|
||||
use client_api::notify::{TokenState, TokenStateReceiver};
|
||||
use client_api::ws::{
|
||||
@@ -124,7 +122,7 @@ impl AppFlowyServer for AppFlowyCloudServer {
|
||||
}
|
||||
|
||||
fn set_ai_model(&self, ai_model: &str) -> Result<(), Error> {
|
||||
self.client.set_ai_model(AIModel::from_str(ai_model)?);
|
||||
self.client.set_ai_model(ai_model.to_string());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use client_api::entity::ai_dto::{LocalAIConfig, RepeatedRelatedQuestion};
|
||||
use flowy_ai_pub::cloud::{
|
||||
ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, ChatSettings,
|
||||
CompleteTextParams, MessageCursor, RepeatedChatMessage, ResponseFormat, StreamAnswer,
|
||||
CompleteTextParams, MessageCursor, ModelList, RepeatedChatMessage, ResponseFormat, StreamAnswer,
|
||||
StreamComplete, SubscriptionPlan, UpdateChatParams,
|
||||
};
|
||||
use flowy_error::FlowyError;
|
||||
@@ -51,7 +51,7 @@ impl ChatCloudService for DefaultChatCloudServiceImpl {
|
||||
_workspace_id: &str,
|
||||
_chat_id: &str,
|
||||
_message_id: i64,
|
||||
_format: ResponseFormat,
|
||||
_format: Option<ResponseFormat>,
|
||||
) -> Result<StreamAnswer, FlowyError> {
|
||||
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
|
||||
}
|
||||
@@ -144,4 +144,8 @@ impl ChatCloudService for DefaultChatCloudServiceImpl {
|
||||
) -> Result<(), FlowyError> {
|
||||
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
|
||||
}
|
||||
|
||||
async fn get_available_models(&self, _workspace_id: &str) -> Result<ModelList, FlowyError> {
|
||||
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
use flowy_user_pub::entities::*;
|
||||
use lib_infra::validator_fn::required_not_empty_str;
|
||||
use std::convert::TryInto;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::entities::parser::{UserEmail, UserIcon, UserName, UserOpenaiKey, UserPassword};
|
||||
use crate::entities::{AIModelPB, AuthenticatorPB};
|
||||
use crate::entities::AuthenticatorPB;
|
||||
use crate::errors::ErrorCode;
|
||||
|
||||
use super::parser::UserStabilityAIKey;
|
||||
@@ -58,7 +56,7 @@ pub struct UserProfilePB {
|
||||
pub stability_ai_key: String,
|
||||
|
||||
#[pb(index = 11)]
|
||||
pub ai_model: AIModelPB,
|
||||
pub ai_model: String,
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf_Enum, Eq, PartialEq, Debug, Clone)]
|
||||
@@ -79,6 +77,10 @@ impl From<UserProfile> for UserProfilePB {
|
||||
EncryptionType::NoEncryption => ("".to_string(), EncryptionTypePB::NoEncryption),
|
||||
EncryptionType::SelfEncryption(sign) => (sign, EncryptionTypePB::Symmetric),
|
||||
};
|
||||
let mut ai_model = user_profile.ai_model;
|
||||
if ai_model.is_empty() {
|
||||
ai_model = "Default".to_string();
|
||||
}
|
||||
Self {
|
||||
id: user_profile.uid,
|
||||
email: user_profile.email,
|
||||
@@ -90,7 +92,7 @@ impl From<UserProfile> for UserProfilePB {
|
||||
encryption_sign,
|
||||
encryption_type: encryption_ty,
|
||||
stability_ai_key: user_profile.stability_ai_key,
|
||||
ai_model: AIModelPB::from_str(&user_profile.ai_model).unwrap_or_default(),
|
||||
ai_model,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use client_api::entity::billing_dto::{
|
||||
WorkspaceSubscriptionStatus, WorkspaceUsageAndLimit,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use validator::Validate;
|
||||
|
||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||
@@ -382,19 +381,19 @@ pub struct UseAISettingPB {
|
||||
pub disable_search_indexing: bool,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub ai_model: AIModelPB,
|
||||
pub ai_model: String,
|
||||
}
|
||||
|
||||
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(),
|
||||
ai_model: value.ai_model,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf, Default, Clone, Validate)]
|
||||
#[derive(ProtoBuf, Default, Clone, Validate, Debug)]
|
||||
pub struct UpdateUserWorkspaceSettingPB {
|
||||
#[pb(index = 1)]
|
||||
#[validate(custom(function = "required_not_empty_str"))]
|
||||
@@ -404,58 +403,22 @@ pub struct UpdateUserWorkspaceSettingPB {
|
||||
pub disable_search_indexing: Option<bool>,
|
||||
|
||||
#[pb(index = 3, one_of)]
|
||||
pub ai_model: Option<AIModelPB>,
|
||||
pub ai_model: Option<String>,
|
||||
}
|
||||
|
||||
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);
|
||||
change.disable_search_indexing = Some(disable_search_indexing);
|
||||
}
|
||||
if let Some(ai_model) = value.ai_model {
|
||||
change = change.ai_model(ai_model.to_str().to_string());
|
||||
change.ai_model = Some(ai_model);
|
||||
}
|
||||
change
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ProtoBuf_Enum, Debug, Clone, Eq, PartialEq, Default)]
|
||||
pub enum AIModelPB {
|
||||
#[default]
|
||||
DefaultModel = 0,
|
||||
GPT4oMini = 1,
|
||||
GPT4o = 2,
|
||||
Claude3Sonnet = 3,
|
||||
Claude3Opus = 4,
|
||||
}
|
||||
|
||||
impl AIModelPB {
|
||||
pub fn to_str(&self) -> &str {
|
||||
match self {
|
||||
AIModelPB::DefaultModel => "default-model",
|
||||
AIModelPB::GPT4oMini => "gpt-4o-mini",
|
||||
AIModelPB::GPT4o => "gpt-4o",
|
||||
AIModelPB::Claude3Sonnet => "claude-3-sonnet",
|
||||
AIModelPB::Claude3Opus => "claude-3-opus",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::GPT4oMini),
|
||||
"gpt-4o" => Ok(AIModelPB::GPT4o),
|
||||
"claude-3-sonnet" => Ok(AIModelPB::Claude3Sonnet),
|
||||
"claude-3-opus" => Ok(AIModelPB::Claude3Opus),
|
||||
_ => Ok(AIModelPB::DefaultModel),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, ProtoBuf, Default, Clone)]
|
||||
pub struct WorkspaceSubscriptionInfoPB {
|
||||
#[pb(index = 1)]
|
||||
|
||||
@@ -559,12 +559,12 @@ impl UserManager {
|
||||
.send();
|
||||
|
||||
if let Some(ai_model) = &ai_model {
|
||||
if let Err(err) = self.cloud_services.set_ai_model(ai_model.to_str()) {
|
||||
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.to_str());
|
||||
let params = UpdateUserProfileParams::new(uid).with_ai_model(ai_model);
|
||||
upsert_user_profile_change(uid, conn, UserTableChangeset::new(params))?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"fa",
|
||||
"fr-CA",
|
||||
"fr-FR",
|
||||
"ga-IE",
|
||||
"he",
|
||||
"hu-HU",
|
||||
"id-ID",
|
||||
|
||||
Reference in new issue
Block a user