mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: ai billing (#5741)
* feat: start on AI plan+billing UI * chore: enable plan and billing * feat: cache workspace subscription + minor fixes (#5705) * feat: update api from billing * feat: add api for workspace subscription info (#5717) * feat: refactor and start integrating AI plans * feat: refine UI and add business logic for AI * feat: complete UIUX for AI and limits * chore: remove resolved todo * chore: localize remove addon dialog * chore: fix spacing issue for usage * fix: interpret subscription + usage on action * chore: update api for billing (#5735) * chore: update revisions * fix: remove subscription cache * fix: copy improvements + use consistent dialog * chore: update to the latest client api * feat: support updating billing period * Feat/ai billing cancel reason (#5752) * chore: add cancellation reason field * fix: ci add one retry for concurrent sign up * chore: merge with main * chore: half merge * chore: fix conflict * chore: observer error * chore: remove unneeded protobuf and remove unwrap * feat: added subscription plan details * chore: check error code and update sidebar toast * chore: periodically check billing state * chore: editor ai error * chore: return file upload error * chore: fmt * chore: clippy * chore: disable upload image when exceed storage limitation * chore: remove todo * chore: remove openai i18n * chore: update log * chore: update client-api to fix stream error * chore: clippy * chore: fix language file * chore: disable billing UI --------- Co-authored-by: Zack Fu Zi Xiang <speed2exe@live.com.sg> Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
parent
864768b3ba
commit
620e027c3e
@ -136,7 +136,7 @@ class DocumentService {
|
|||||||
}) async {
|
}) async {
|
||||||
final workspace = await FolderEventReadCurrentWorkspace().send();
|
final workspace = await FolderEventReadCurrentWorkspace().send();
|
||||||
return workspace.fold((l) async {
|
return workspace.fold((l) async {
|
||||||
final payload = UploadedFilePB(
|
final payload = DownloadFilePB(
|
||||||
url: url,
|
url: url,
|
||||||
);
|
);
|
||||||
final result = await DocumentEventDownloadFile(payload).send();
|
final result = await DocumentEventDownloadFile(payload).send();
|
||||||
|
@ -7,6 +7,7 @@ import 'package:appflowy/startup/startup.dart';
|
|||||||
import 'package:appflowy/util/file_extension.dart';
|
import 'package:appflowy/util/file_extension.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
|
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/uuid.dart';
|
import 'package:flowy_infra/uuid.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
@ -63,6 +64,12 @@ Future<(String? path, String? errorMessage)> saveImageToCloudStorage(
|
|||||||
);
|
);
|
||||||
return (s.url, null);
|
return (s.url, null);
|
||||||
},
|
},
|
||||||
(e) => (null, e.msg),
|
(err) {
|
||||||
|
if (err.code == ErrorCode.FileStorageLimitExceeded) {
|
||||||
|
return (null, LocaleKeys.sideBar_storageLimitDialogTitle.tr());
|
||||||
|
} else {
|
||||||
|
return (null, err.msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,26 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
part 'error.freezed.dart';
|
part 'error.freezed.dart';
|
||||||
part 'error.g.dart';
|
part 'error.g.dart';
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class AIError with _$AIError {
|
class AIError with _$AIError {
|
||||||
const factory AIError({
|
const factory AIError({
|
||||||
String? code,
|
|
||||||
required String message,
|
required String message,
|
||||||
|
@Default(AIErrorCode.other) AIErrorCode code,
|
||||||
}) = _AIError;
|
}) = _AIError;
|
||||||
|
|
||||||
factory AIError.fromJson(Map<String, Object?> json) =>
|
factory AIError.fromJson(Map<String, Object?> json) =>
|
||||||
_$AIErrorFromJson(json);
|
_$AIErrorFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum AIErrorCode {
|
||||||
|
@JsonValue('AIResponseLimitExceeded')
|
||||||
|
aiResponseLimitExceeded,
|
||||||
|
@JsonValue('Other')
|
||||||
|
other,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AIErrorExtension on AIError {
|
||||||
|
bool get isLimitExceeded => code == AIErrorCode.aiResponseLimitExceeded;
|
||||||
|
}
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class _AILimitDialog extends StatelessWidget {
|
||||||
|
const _AILimitDialog({
|
||||||
|
required this.message,
|
||||||
|
required this.onOkPressed,
|
||||||
|
});
|
||||||
|
final VoidCallback onOkPressed;
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return NavigatorOkCancelDialog(
|
||||||
|
message: message,
|
||||||
|
okTitle: LocaleKeys.button_ok.tr(),
|
||||||
|
onOkPressed: onOkPressed,
|
||||||
|
titleUpperCase: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void showAILimitDialog(BuildContext context, String message) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
useRootNavigator: false,
|
||||||
|
builder: (dialogContext) => _AILimitDialog(
|
||||||
|
message: message,
|
||||||
|
onOkPressed: () {},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
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/error.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/ai_service.dart';
|
||||||
@ -17,6 +18,8 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'ai_limit_dialog.dart';
|
||||||
|
|
||||||
class AutoCompletionBlockKeys {
|
class AutoCompletionBlockKeys {
|
||||||
const AutoCompletionBlockKeys._();
|
const AutoCompletionBlockKeys._();
|
||||||
|
|
||||||
@ -225,11 +228,15 @@ class _AutoCompletionBlockComponentState
|
|||||||
onError: (error) async {
|
onError: (error) async {
|
||||||
barrierDialog?.dismiss();
|
barrierDialog?.dismiss();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
showSnackBarMessage(
|
if (error.isLimitExceeded) {
|
||||||
context,
|
showAILimitDialog(context, error.message);
|
||||||
error.message,
|
} else {
|
||||||
showCancel: true,
|
showSnackBarMessage(
|
||||||
);
|
context,
|
||||||
|
error.message,
|
||||||
|
showCancel: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -304,11 +311,15 @@ class _AutoCompletionBlockComponentState
|
|||||||
onEnd: () async {},
|
onEnd: () async {},
|
||||||
onError: (error) async {
|
onError: (error) async {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
showSnackBarMessage(
|
if (error.isLimitExceeded) {
|
||||||
context,
|
showAILimitDialog(context, error.message);
|
||||||
error.message,
|
} else {
|
||||||
showCancel: true,
|
showSnackBarMessage(
|
||||||
);
|
context,
|
||||||
|
error.message,
|
||||||
|
showCancel: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -2,6 +2,7 @@ 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/ai_client.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.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';
|
||||||
@ -16,6 +17,8 @@ import 'package:flutter/material.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';
|
||||||
|
|
||||||
|
import 'ai_limit_dialog.dart';
|
||||||
|
|
||||||
class SmartEditBlockKeys {
|
class SmartEditBlockKeys {
|
||||||
const SmartEditBlockKeys._();
|
const SmartEditBlockKeys._();
|
||||||
|
|
||||||
@ -426,11 +429,15 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) async {
|
onError: (error) async {
|
||||||
showSnackBarMessage(
|
if (error.isLimitExceeded) {
|
||||||
context,
|
showAILimitDialog(context, error.message);
|
||||||
error.message,
|
} else {
|
||||||
showCancel: true,
|
showSnackBarMessage(
|
||||||
);
|
context,
|
||||||
|
error.message,
|
||||||
|
showCancel: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
await _onExit();
|
await _onExit();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -91,6 +91,7 @@ enum FeatureFlag {
|
|||||||
|
|
||||||
bool get isOn {
|
bool get isOn {
|
||||||
if ([
|
if ([
|
||||||
|
FeatureFlag.planBilling,
|
||||||
// release this feature in version 0.6.1
|
// release this feature in version 0.6.1
|
||||||
FeatureFlag.spaceDesign,
|
FeatureFlag.spaceDesign,
|
||||||
// release this feature in version 0.5.9
|
// release this feature in version 0.5.9
|
||||||
@ -110,6 +111,7 @@ enum FeatureFlag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
case FeatureFlag.planBilling:
|
||||||
case FeatureFlag.search:
|
case FeatureFlag.search:
|
||||||
case FeatureFlag.syncDocument:
|
case FeatureFlag.syncDocument:
|
||||||
case FeatureFlag.syncDatabase:
|
case FeatureFlag.syncDatabase:
|
||||||
@ -117,7 +119,6 @@ enum FeatureFlag {
|
|||||||
return true;
|
return true;
|
||||||
case FeatureFlag.collaborativeWorkspace:
|
case FeatureFlag.collaborativeWorkspace:
|
||||||
case FeatureFlag.membersSettings:
|
case FeatureFlag.membersSettings:
|
||||||
case FeatureFlag.planBilling:
|
|
||||||
case FeatureFlag.unknown:
|
case FeatureFlag.unknown:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,9 @@ class AppFlowyCloudDeepLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_isPaymentSuccessUri(uri)) {
|
if (_isPaymentSuccessUri(uri)) {
|
||||||
return getIt<SubscriptionSuccessListenable>().onPaymentSuccess();
|
Log.debug("Payment success deep link: ${uri.toString()}");
|
||||||
|
final plan = uri.queryParameters['plan'];
|
||||||
|
return getIt<SubscriptionSuccessListenable>().onPaymentSuccess(plan);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _isAuthCallbackDeepLink(uri).fold(
|
return _isAuthCallbackDeepLink(uri).fold(
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
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/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/text_completion.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/text_completion.dart';
|
||||||
@ -9,6 +10,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/wid
|
|||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-chat/entities.pb.dart';
|
||||||
import 'package:appflowy_result/appflowy_result.dart';
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:fixnum/fixnum.dart' as fixnum;
|
import 'package:fixnum/fixnum.dart' as fixnum;
|
||||||
|
|
||||||
class AppFlowyAIService implements AIRepository {
|
class AppFlowyAIService implements AIRepository {
|
||||||
@ -85,6 +87,15 @@ class CompletionStream {
|
|||||||
_port.handler = _controller.add;
|
_port.handler = _controller.add;
|
||||||
_subscription = _controller.stream.listen(
|
_subscription = _controller.stream.listen(
|
||||||
(event) async {
|
(event) async {
|
||||||
|
if (event == "AI_RESPONSE_LIMIT") {
|
||||||
|
onError(
|
||||||
|
AIError(
|
||||||
|
message: LocaleKeys.sideBar_aiResponseLitmit.tr(),
|
||||||
|
code: AIErrorCode.aiResponseLimitExceeded,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (event.startsWith("start:")) {
|
if (event.startsWith("start:")) {
|
||||||
await onStart();
|
await onStart();
|
||||||
}
|
}
|
||||||
@ -96,6 +107,7 @@ class CompletionStream {
|
|||||||
if (event.startsWith("finish:")) {
|
if (event.startsWith("finish:")) {
|
||||||
await onEnd();
|
await onEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.startsWith("error:")) {
|
if (event.startsWith("error:")) {
|
||||||
onError(AIError(message: event.substring(6)));
|
onError(AIError(message: event.substring(6)));
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:appflowy/env/cloud_env.dart';
|
import 'package:appflowy/env/cloud_env.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
|
||||||
@ -10,7 +11,10 @@ import 'package:appflowy_result/appflowy_result.dart';
|
|||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
|
||||||
abstract class IUserBackendService {
|
abstract class IUserBackendService {
|
||||||
Future<FlowyResult<void, FlowyError>> cancelSubscription(String workspaceId);
|
Future<FlowyResult<void, FlowyError>> cancelSubscription(
|
||||||
|
String workspaceId,
|
||||||
|
SubscriptionPlanPB plan,
|
||||||
|
);
|
||||||
Future<FlowyResult<PaymentLinkPB, FlowyError>> createSubscription(
|
Future<FlowyResult<PaymentLinkPB, FlowyError>> createSubscription(
|
||||||
String workspaceId,
|
String workspaceId,
|
||||||
SubscriptionPlanPB plan,
|
SubscriptionPlanPB plan,
|
||||||
@ -228,9 +232,10 @@ class UserBackendService implements IUserBackendService {
|
|||||||
return UserEventLeaveWorkspace(data).send();
|
return UserEventLeaveWorkspace(data).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<FlowyResult<RepeatedWorkspaceSubscriptionPB, FlowyError>>
|
static Future<FlowyResult<WorkspaceSubscriptionInfoPB, FlowyError>>
|
||||||
getWorkspaceSubscriptions() {
|
getWorkspaceSubscriptionInfo(String workspaceId) {
|
||||||
return UserEventGetWorkspaceSubscriptions().send();
|
final params = UserWorkspaceIdPB.create()..workspaceId = workspaceId;
|
||||||
|
return UserEventGetWorkspaceSubscriptionInfo(params).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<FlowyResult<WorkspaceMemberPB, FlowyError>>
|
Future<FlowyResult<WorkspaceMemberPB, FlowyError>>
|
||||||
@ -250,15 +255,32 @@ class UserBackendService implements IUserBackendService {
|
|||||||
..recurringInterval = RecurringIntervalPB.Year
|
..recurringInterval = RecurringIntervalPB.Year
|
||||||
..workspaceSubscriptionPlan = plan
|
..workspaceSubscriptionPlan = plan
|
||||||
..successUrl =
|
..successUrl =
|
||||||
'${getIt<AppFlowyCloudSharedEnv>().appflowyCloudConfig.base_url}/web/payment-success';
|
'${getIt<AppFlowyCloudSharedEnv>().appflowyCloudConfig.base_url}/web/payment-success?plan=${plan.toRecognizable()}';
|
||||||
return UserEventSubscribeWorkspace(request).send();
|
return UserEventSubscribeWorkspace(request).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<FlowyResult<void, FlowyError>> cancelSubscription(
|
Future<FlowyResult<void, FlowyError>> cancelSubscription(
|
||||||
String workspaceId,
|
String workspaceId,
|
||||||
|
SubscriptionPlanPB plan,
|
||||||
) {
|
) {
|
||||||
final request = UserWorkspaceIdPB()..workspaceId = workspaceId;
|
final request = CancelWorkspaceSubscriptionPB()
|
||||||
|
..workspaceId = workspaceId
|
||||||
|
..plan = plan;
|
||||||
|
|
||||||
return UserEventCancelWorkspaceSubscription(request).send();
|
return UserEventCancelWorkspaceSubscription(request).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<FlowyResult<void, FlowyError>> updateSubscriptionPeriod(
|
||||||
|
String workspaceId,
|
||||||
|
SubscriptionPlanPB plan,
|
||||||
|
RecurringIntervalPB interval,
|
||||||
|
) {
|
||||||
|
final request = UpdateWorkspaceSubscriptionPaymentPeriodPB()
|
||||||
|
..workspaceId = workspaceId
|
||||||
|
..plan = plan
|
||||||
|
..recurringInterval = interval;
|
||||||
|
|
||||||
|
return UserEventUpdateWorkspaceSubscriptionPaymentPeriod(request).send();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,24 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
|
import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart';
|
||||||
|
import 'package:appflowy/workspace/application/subscription_success_listenable/subscription_success_listenable.dart';
|
||||||
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pbserver.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pbserver.dart';
|
||||||
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
|
||||||
part 'settings_billing_bloc.freezed.dart';
|
part 'settings_billing_bloc.freezed.dart';
|
||||||
|
|
||||||
@ -17,86 +26,273 @@ class SettingsBillingBloc
|
|||||||
extends Bloc<SettingsBillingEvent, SettingsBillingState> {
|
extends Bloc<SettingsBillingEvent, SettingsBillingState> {
|
||||||
SettingsBillingBloc({
|
SettingsBillingBloc({
|
||||||
required this.workspaceId,
|
required this.workspaceId,
|
||||||
|
required Int64 userId,
|
||||||
}) : super(const _Initial()) {
|
}) : super(const _Initial()) {
|
||||||
|
_userService = UserBackendService(userId: userId);
|
||||||
_service = WorkspaceService(workspaceId: workspaceId);
|
_service = WorkspaceService(workspaceId: workspaceId);
|
||||||
|
_successListenable = getIt<SubscriptionSuccessListenable>();
|
||||||
|
_successListenable.addListener(_onPaymentSuccessful);
|
||||||
|
|
||||||
on<SettingsBillingEvent>((event, emit) async {
|
on<SettingsBillingEvent>((event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
started: () async {
|
started: () async {
|
||||||
emit(const SettingsBillingState.loading());
|
emit(const SettingsBillingState.loading());
|
||||||
|
|
||||||
final snapshots = await Future.wait([
|
|
||||||
UserBackendService.getWorkspaceSubscriptions(),
|
|
||||||
_service.getBillingPortal(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
FlowyError? error;
|
FlowyError? error;
|
||||||
|
|
||||||
final subscription = snapshots.first.fold(
|
final result = await UserBackendService.getWorkspaceSubscriptionInfo(
|
||||||
(s) =>
|
workspaceId,
|
||||||
(s as RepeatedWorkspaceSubscriptionPB)
|
);
|
||||||
.items
|
|
||||||
.firstWhereOrNull((i) => i.workspaceId == workspaceId) ??
|
|
||||||
WorkspaceSubscriptionPB(
|
|
||||||
workspaceId: workspaceId,
|
|
||||||
subscriptionPlan: SubscriptionPlanPB.None,
|
|
||||||
isActive: true,
|
|
||||||
),
|
|
||||||
(e) {
|
|
||||||
// Not a Cjstomer yet
|
|
||||||
if (e.code == ErrorCode.InvalidParams) {
|
|
||||||
return WorkspaceSubscriptionPB(
|
|
||||||
workspaceId: workspaceId,
|
|
||||||
subscriptionPlan: SubscriptionPlanPB.None,
|
|
||||||
isActive: true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
final subscriptionInfo = result.fold(
|
||||||
|
(s) => s,
|
||||||
|
(e) {
|
||||||
error = e;
|
error = e;
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final billingPortalResult = snapshots.last;
|
if (subscriptionInfo == null || error != null) {
|
||||||
final billingPortal = billingPortalResult.fold(
|
|
||||||
(s) => s as BillingPortalPB,
|
|
||||||
(e) {
|
|
||||||
// Not a customer yet
|
|
||||||
if (e.code == ErrorCode.InvalidParams) {
|
|
||||||
return BillingPortalPB();
|
|
||||||
}
|
|
||||||
|
|
||||||
error = e;
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (subscription == null || billingPortal == null || error != null) {
|
|
||||||
return emit(SettingsBillingState.error(error: error));
|
return emit(SettingsBillingState.error(error: error));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_billingPortalCompleter.isCompleted) {
|
||||||
|
unawaited(_fetchBillingPortal());
|
||||||
|
unawaited(
|
||||||
|
_billingPortalCompleter.future.then(
|
||||||
|
(result) {
|
||||||
|
if (isClosed) return;
|
||||||
|
|
||||||
|
result.fold(
|
||||||
|
(portal) {
|
||||||
|
_billingPortal = portal;
|
||||||
|
add(
|
||||||
|
SettingsBillingEvent.billingPortalFetched(
|
||||||
|
billingPortal: portal,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(e) => Log.error('Error fetching billing portal: $e'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
SettingsBillingState.ready(
|
SettingsBillingState.ready(
|
||||||
subscription: subscription,
|
subscriptionInfo: subscriptionInfo,
|
||||||
billingPortal: billingPortal,
|
billingPortal: _billingPortal,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
billingPortalFetched: (billingPortal) async => state.maybeWhen(
|
||||||
|
orElse: () {},
|
||||||
|
ready: (subscriptionInfo, _, plan, isLoading) => emit(
|
||||||
|
SettingsBillingState.ready(
|
||||||
|
subscriptionInfo: subscriptionInfo,
|
||||||
|
billingPortal: billingPortal,
|
||||||
|
successfulPlanUpgrade: plan,
|
||||||
|
isLoading: isLoading,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
openCustomerPortal: () async {
|
||||||
|
if (_billingPortalCompleter.isCompleted && _billingPortal != null) {
|
||||||
|
return afLaunchUrlString(_billingPortal!.url);
|
||||||
|
}
|
||||||
|
await _billingPortalCompleter.future;
|
||||||
|
if (_billingPortal != null) {
|
||||||
|
await afLaunchUrlString(_billingPortal!.url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addSubscription: (plan) async {
|
||||||
|
final result =
|
||||||
|
await _userService.createSubscription(workspaceId, plan);
|
||||||
|
|
||||||
|
result.fold(
|
||||||
|
(link) => afLaunchUrlString(link.paymentLink),
|
||||||
|
(f) => Log.error(f.msg, f),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cancelSubscription: (plan) async {
|
||||||
|
final s = state.mapOrNull(ready: (s) => s);
|
||||||
|
if (s == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(s.copyWith(isLoading: true));
|
||||||
|
|
||||||
|
final result =
|
||||||
|
await _userService.cancelSubscription(workspaceId, plan);
|
||||||
|
final successOrNull = result.fold(
|
||||||
|
(_) => true,
|
||||||
|
(f) {
|
||||||
|
Log.error(
|
||||||
|
'Failed to cancel subscription of ${plan.label}: ${f.msg}',
|
||||||
|
f,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (successOrNull != true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final subscriptionInfo = state.mapOrNull(
|
||||||
|
ready: (s) => s.subscriptionInfo,
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is impossible, but for good measure
|
||||||
|
if (subscriptionInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionInfo.freeze();
|
||||||
|
final newInfo = subscriptionInfo.rebuild((value) {
|
||||||
|
if (plan.isAddOn) {
|
||||||
|
value.addOns.removeWhere(
|
||||||
|
(addon) => addon.addOnSubscription.subscriptionPlan == plan,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan == WorkspacePlanPB.ProPlan &&
|
||||||
|
value.plan == WorkspacePlanPB.ProPlan) {
|
||||||
|
value.plan = WorkspacePlanPB.FreePlan;
|
||||||
|
value.planSubscription.freeze();
|
||||||
|
value.planSubscription = value.planSubscription.rebuild((sub) {
|
||||||
|
sub.status = WorkspaceSubscriptionStatusPB.Active;
|
||||||
|
sub.subscriptionPlan = SubscriptionPlanPB.Free;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
emit(
|
||||||
|
SettingsBillingState.ready(
|
||||||
|
subscriptionInfo: newInfo,
|
||||||
|
billingPortal: _billingPortal,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
paymentSuccessful: (plan) async {
|
||||||
|
final result = await UserBackendService.getWorkspaceSubscriptionInfo(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
final subscriptionInfo = result.toNullable();
|
||||||
|
if (subscriptionInfo != null) {
|
||||||
|
emit(
|
||||||
|
SettingsBillingState.ready(
|
||||||
|
subscriptionInfo: subscriptionInfo,
|
||||||
|
billingPortal: _billingPortal,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updatePeriod: (plan, interval) async {
|
||||||
|
final s = state.mapOrNull(ready: (s) => s);
|
||||||
|
if (s == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(s.copyWith(isLoading: true));
|
||||||
|
|
||||||
|
final result = await _userService.updateSubscriptionPeriod(
|
||||||
|
workspaceId,
|
||||||
|
plan,
|
||||||
|
interval,
|
||||||
|
);
|
||||||
|
final successOrNull = result.fold((_) => true, (f) {
|
||||||
|
Log.error(
|
||||||
|
'Failed to update subscription period of ${plan.label}: ${f.msg}',
|
||||||
|
f,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (successOrNull != true) {
|
||||||
|
return emit(s.copyWith(isLoading: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch new subscription info
|
||||||
|
final newResult =
|
||||||
|
await UserBackendService.getWorkspaceSubscriptionInfo(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
final newSubscriptionInfo = newResult.toNullable();
|
||||||
|
if (newSubscriptionInfo != null) {
|
||||||
|
emit(
|
||||||
|
SettingsBillingState.ready(
|
||||||
|
subscriptionInfo: newSubscriptionInfo,
|
||||||
|
billingPortal: _billingPortal,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
late final String workspaceId;
|
late final String workspaceId;
|
||||||
late final WorkspaceService _service;
|
late final WorkspaceService _service;
|
||||||
|
late final UserBackendService _userService;
|
||||||
|
final _billingPortalCompleter =
|
||||||
|
Completer<FlowyResult<BillingPortalPB, FlowyError>>();
|
||||||
|
|
||||||
|
BillingPortalPB? _billingPortal;
|
||||||
|
late final SubscriptionSuccessListenable _successListenable;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
_successListenable.removeListener(_onPaymentSuccessful);
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchBillingPortal() async {
|
||||||
|
final billingPortalResult = await _service.getBillingPortal();
|
||||||
|
_billingPortalCompleter.complete(billingPortalResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onPaymentSuccessful() async => add(
|
||||||
|
SettingsBillingEvent.paymentSuccessful(
|
||||||
|
plan: _successListenable.subscribedPlan,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SettingsBillingEvent with _$SettingsBillingEvent {
|
class SettingsBillingEvent with _$SettingsBillingEvent {
|
||||||
const factory SettingsBillingEvent.started() = _Started;
|
const factory SettingsBillingEvent.started() = _Started;
|
||||||
|
|
||||||
|
const factory SettingsBillingEvent.billingPortalFetched({
|
||||||
|
required BillingPortalPB billingPortal,
|
||||||
|
}) = _BillingPortalFetched;
|
||||||
|
|
||||||
|
const factory SettingsBillingEvent.openCustomerPortal() = _OpenCustomerPortal;
|
||||||
|
|
||||||
|
const factory SettingsBillingEvent.addSubscription(SubscriptionPlanPB plan) =
|
||||||
|
_AddSubscription;
|
||||||
|
|
||||||
|
const factory SettingsBillingEvent.cancelSubscription(
|
||||||
|
SubscriptionPlanPB plan,
|
||||||
|
) = _CancelSubscription;
|
||||||
|
|
||||||
|
const factory SettingsBillingEvent.paymentSuccessful({
|
||||||
|
SubscriptionPlanPB? plan,
|
||||||
|
}) = _PaymentSuccessful;
|
||||||
|
|
||||||
|
const factory SettingsBillingEvent.updatePeriod({
|
||||||
|
required SubscriptionPlanPB plan,
|
||||||
|
required RecurringIntervalPB interval,
|
||||||
|
}) = _UpdatePeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SettingsBillingState with _$SettingsBillingState {
|
class SettingsBillingState extends Equatable with _$SettingsBillingState {
|
||||||
|
const SettingsBillingState._();
|
||||||
|
|
||||||
const factory SettingsBillingState.initial() = _Initial;
|
const factory SettingsBillingState.initial() = _Initial;
|
||||||
|
|
||||||
const factory SettingsBillingState.loading() = _Loading;
|
const factory SettingsBillingState.loading() = _Loading;
|
||||||
@ -106,7 +302,22 @@ class SettingsBillingState with _$SettingsBillingState {
|
|||||||
}) = _Error;
|
}) = _Error;
|
||||||
|
|
||||||
const factory SettingsBillingState.ready({
|
const factory SettingsBillingState.ready({
|
||||||
required WorkspaceSubscriptionPB subscription,
|
required WorkspaceSubscriptionInfoPB subscriptionInfo,
|
||||||
required BillingPortalPB? billingPortal,
|
required BillingPortalPB? billingPortal,
|
||||||
|
@Default(null) SubscriptionPlanPB? successfulPlanUpgrade,
|
||||||
|
@Default(false) bool isLoading,
|
||||||
}) = _Ready;
|
}) = _Ready;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => maybeWhen(
|
||||||
|
orElse: () => const [],
|
||||||
|
error: (error) => [error],
|
||||||
|
ready: (subscription, billingPortal, plan, isLoading) => [
|
||||||
|
subscription,
|
||||||
|
billingPortal,
|
||||||
|
plan,
|
||||||
|
isLoading,
|
||||||
|
...subscription.addOns,
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:appflowy/core/notification/notification_helper.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-storage/notification.pb.dart';
|
||||||
|
import 'package:appflowy_backend/rust_stream.dart';
|
||||||
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
|
|
||||||
|
class StoregeNotificationParser
|
||||||
|
extends NotificationParser<StorageNotification, FlowyError> {
|
||||||
|
StoregeNotificationParser({
|
||||||
|
super.id,
|
||||||
|
required super.callback,
|
||||||
|
}) : super(
|
||||||
|
tyParser: (ty, source) =>
|
||||||
|
source == "storage" ? StorageNotification.valueOf(ty) : null,
|
||||||
|
errorParser: (bytes) => FlowyError.fromBuffer(bytes),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class StoreageNotificationListener {
|
||||||
|
StoreageNotificationListener({
|
||||||
|
void Function(FlowyError error)? onError,
|
||||||
|
}) : _parser = StoregeNotificationParser(
|
||||||
|
callback: (
|
||||||
|
StorageNotification ty,
|
||||||
|
FlowyResult<Uint8List, FlowyError> result,
|
||||||
|
) {
|
||||||
|
result.fold(
|
||||||
|
(data) {
|
||||||
|
try {
|
||||||
|
switch (ty) {
|
||||||
|
case StorageNotification.FileStorageLimitExceeded:
|
||||||
|
onError?.call(FlowyError.fromBuffer(data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error(
|
||||||
|
"$StoreageNotificationListener deserialize PB fail",
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) {
|
||||||
|
Log.error("Error in StoreageNotificationListener", err);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
_subscription =
|
||||||
|
RustStreamReceiver.listen((observable) => _parser?.parse(observable));
|
||||||
|
}
|
||||||
|
|
||||||
|
StoregeNotificationParser? _parser;
|
||||||
|
StreamSubscription<SubscribeObject>? _subscription;
|
||||||
|
|
||||||
|
Future<void> stop() async {
|
||||||
|
_parser = null;
|
||||||
|
await _subscription?.cancel();
|
||||||
|
_subscription = null;
|
||||||
|
}
|
||||||
|
}
|
@ -3,18 +3,18 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
|
import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart';
|
||||||
import 'package:appflowy/workspace/application/subscription_success_listenable/subscription_success_listenable.dart';
|
import 'package:appflowy/workspace/application/subscription_success_listenable/subscription_success_listenable.dart';
|
||||||
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pbserver.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pbserver.dart';
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
|
||||||
part 'settings_plan_bloc.freezed.dart';
|
part 'settings_plan_bloc.freezed.dart';
|
||||||
|
|
||||||
@ -30,13 +30,14 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
|||||||
|
|
||||||
on<SettingsPlanEvent>((event, emit) async {
|
on<SettingsPlanEvent>((event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
started: (withShowSuccessful) async {
|
started: (withSuccessfulUpgrade, shouldLoad) async {
|
||||||
emit(const SettingsPlanState.loading());
|
if (shouldLoad) {
|
||||||
|
emit(const SettingsPlanState.loading());
|
||||||
|
}
|
||||||
|
|
||||||
final snapshots = await Future.wait([
|
final snapshots = await Future.wait([
|
||||||
_service.getWorkspaceUsage(),
|
_service.getWorkspaceUsage(),
|
||||||
UserBackendService.getWorkspaceSubscriptions(),
|
UserBackendService.getWorkspaceSubscriptionInfo(workspaceId),
|
||||||
_service.getBillingPortal(),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
FlowyError? error;
|
FlowyError? error;
|
||||||
@ -49,39 +50,16 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final subscription = snapshots[1].fold(
|
final subscriptionInfo = snapshots[1].fold(
|
||||||
(s) =>
|
(s) => s as WorkspaceSubscriptionInfoPB,
|
||||||
(s as RepeatedWorkspaceSubscriptionPB)
|
|
||||||
.items
|
|
||||||
.firstWhereOrNull((i) => i.workspaceId == workspaceId) ??
|
|
||||||
WorkspaceSubscriptionPB(
|
|
||||||
workspaceId: workspaceId,
|
|
||||||
subscriptionPlan: SubscriptionPlanPB.None,
|
|
||||||
isActive: true,
|
|
||||||
),
|
|
||||||
(f) {
|
(f) {
|
||||||
error = f;
|
error = f;
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final billingPortalResult = snapshots.last;
|
|
||||||
final billingPortal = billingPortalResult.fold(
|
|
||||||
(s) => s as BillingPortalPB,
|
|
||||||
(e) {
|
|
||||||
// Not a customer yet
|
|
||||||
if (e.code == ErrorCode.InvalidParams) {
|
|
||||||
return BillingPortalPB();
|
|
||||||
}
|
|
||||||
|
|
||||||
error = e;
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (usageResult == null ||
|
if (usageResult == null ||
|
||||||
subscription == null ||
|
subscriptionInfo == null ||
|
||||||
billingPortal == null ||
|
|
||||||
error != null) {
|
error != null) {
|
||||||
return emit(SettingsPlanState.error(error: error));
|
return emit(SettingsPlanState.error(error: error));
|
||||||
}
|
}
|
||||||
@ -89,18 +67,16 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
|||||||
emit(
|
emit(
|
||||||
SettingsPlanState.ready(
|
SettingsPlanState.ready(
|
||||||
workspaceUsage: usageResult,
|
workspaceUsage: usageResult,
|
||||||
subscription: subscription,
|
subscriptionInfo: subscriptionInfo,
|
||||||
billingPortal: billingPortal,
|
successfulPlanUpgrade: withSuccessfulUpgrade,
|
||||||
showSuccessDialog: withShowSuccessful,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (withShowSuccessful) {
|
if (withSuccessfulUpgrade != null) {
|
||||||
emit(
|
emit(
|
||||||
SettingsPlanState.ready(
|
SettingsPlanState.ready(
|
||||||
workspaceUsage: usageResult,
|
workspaceUsage: usageResult,
|
||||||
subscription: subscription,
|
subscriptionInfo: subscriptionInfo,
|
||||||
billingPortal: billingPortal,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -108,12 +84,15 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
|||||||
addSubscription: (plan) async {
|
addSubscription: (plan) async {
|
||||||
final result = await _userService.createSubscription(
|
final result = await _userService.createSubscription(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
SubscriptionPlanPB.Pro,
|
plan,
|
||||||
);
|
);
|
||||||
|
|
||||||
result.fold(
|
result.fold(
|
||||||
(pl) => afLaunchUrlString(pl.paymentLink),
|
(pl) => afLaunchUrlString(pl.paymentLink),
|
||||||
(f) => Log.error(f.msg, f),
|
(f) => Log.error(
|
||||||
|
'Failed to fetch paymentlink for $plan: ${f.msg}',
|
||||||
|
f,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
cancelSubscription: () async {
|
cancelSubscription: () async {
|
||||||
@ -121,16 +100,79 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
|||||||
.mapOrNull(ready: (state) => state)
|
.mapOrNull(ready: (state) => state)
|
||||||
?.copyWith(downgradeProcessing: true);
|
?.copyWith(downgradeProcessing: true);
|
||||||
emit(newState ?? state);
|
emit(newState ?? state);
|
||||||
await _userService.cancelSubscription(workspaceId);
|
|
||||||
add(const SettingsPlanEvent.started());
|
// We can hardcode the subscription plan here because we cannot cancel addons
|
||||||
|
// on the Plan page
|
||||||
|
final result = await _userService.cancelSubscription(
|
||||||
|
workspaceId,
|
||||||
|
SubscriptionPlanPB.Pro,
|
||||||
|
);
|
||||||
|
|
||||||
|
final successOrNull = result.fold(
|
||||||
|
(_) => true,
|
||||||
|
(f) {
|
||||||
|
Log.error('Failed to cancel subscription of Pro: ${f.msg}', f);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (successOrNull != true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final subscriptionInfo = state.mapOrNull(
|
||||||
|
ready: (s) => s.subscriptionInfo,
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is impossible, but for good measure
|
||||||
|
if (subscriptionInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We assume their new plan is Free, since we only have Pro plan
|
||||||
|
// at the moment.
|
||||||
|
subscriptionInfo.freeze();
|
||||||
|
final newInfo = subscriptionInfo.rebuild((value) {
|
||||||
|
value.plan = WorkspacePlanPB.FreePlan;
|
||||||
|
value.planSubscription.freeze();
|
||||||
|
value.planSubscription = value.planSubscription.rebuild((sub) {
|
||||||
|
sub.status = WorkspaceSubscriptionStatusPB.Active;
|
||||||
|
sub.subscriptionPlan = SubscriptionPlanPB.Free;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// We need to remove unlimited indicator for storage and
|
||||||
|
// AI usage, if they don't have an addon that changes this behavior.
|
||||||
|
final usage = state.mapOrNull(ready: (s) => s.workspaceUsage)!;
|
||||||
|
|
||||||
|
usage.freeze();
|
||||||
|
final newUsage = usage.rebuild((value) {
|
||||||
|
if (!newInfo.hasAIMax && !newInfo.hasAIOnDevice) {
|
||||||
|
value.aiResponsesUnlimited = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value.storageBytesUnlimited = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
emit(
|
||||||
|
SettingsPlanState.ready(
|
||||||
|
subscriptionInfo: newInfo,
|
||||||
|
workspaceUsage: newUsage,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
paymentSuccessful: () {
|
paymentSuccessful: (plan) {
|
||||||
final readyState = state.mapOrNull(ready: (state) => state);
|
final readyState = state.mapOrNull(ready: (state) => state);
|
||||||
if (readyState == null) {
|
if (readyState == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(const SettingsPlanEvent.started(withShowSuccessful: true));
|
add(
|
||||||
|
SettingsPlanEvent.started(
|
||||||
|
withSuccessfulUpgrade: plan,
|
||||||
|
shouldLoad: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -141,9 +183,11 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
|||||||
late final IUserBackendService _userService;
|
late final IUserBackendService _userService;
|
||||||
late final SubscriptionSuccessListenable _successListenable;
|
late final SubscriptionSuccessListenable _successListenable;
|
||||||
|
|
||||||
void _onPaymentSuccessful() {
|
Future<void> _onPaymentSuccessful() async => add(
|
||||||
add(const SettingsPlanEvent.paymentSuccessful());
|
SettingsPlanEvent.paymentSuccessful(
|
||||||
}
|
plan: _successListenable.subscribedPlan,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
@ -155,12 +199,18 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
|||||||
@freezed
|
@freezed
|
||||||
class SettingsPlanEvent with _$SettingsPlanEvent {
|
class SettingsPlanEvent with _$SettingsPlanEvent {
|
||||||
const factory SettingsPlanEvent.started({
|
const factory SettingsPlanEvent.started({
|
||||||
@Default(false) bool withShowSuccessful,
|
@Default(null) SubscriptionPlanPB? withSuccessfulUpgrade,
|
||||||
|
@Default(true) bool shouldLoad,
|
||||||
}) = _Started;
|
}) = _Started;
|
||||||
|
|
||||||
const factory SettingsPlanEvent.addSubscription(SubscriptionPlanPB plan) =
|
const factory SettingsPlanEvent.addSubscription(SubscriptionPlanPB plan) =
|
||||||
_AddSubscription;
|
_AddSubscription;
|
||||||
|
|
||||||
const factory SettingsPlanEvent.cancelSubscription() = _CancelSubscription;
|
const factory SettingsPlanEvent.cancelSubscription() = _CancelSubscription;
|
||||||
const factory SettingsPlanEvent.paymentSuccessful() = _PaymentSuccessful;
|
|
||||||
|
const factory SettingsPlanEvent.paymentSuccessful({
|
||||||
|
@Default(null) SubscriptionPlanPB? plan,
|
||||||
|
}) = _PaymentSuccessful;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -175,9 +225,8 @@ class SettingsPlanState with _$SettingsPlanState {
|
|||||||
|
|
||||||
const factory SettingsPlanState.ready({
|
const factory SettingsPlanState.ready({
|
||||||
required WorkspaceUsagePB workspaceUsage,
|
required WorkspaceUsagePB workspaceUsage,
|
||||||
required WorkspaceSubscriptionPB subscription,
|
required WorkspaceSubscriptionInfoPB subscriptionInfo,
|
||||||
required BillingPortalPB? billingPortal,
|
@Default(null) SubscriptionPlanPB? successfulPlanUpgrade,
|
||||||
@Default(false) bool showSuccessDialog,
|
|
||||||
@Default(false) bool downgradeProcessing,
|
@Default(false) bool downgradeProcessing,
|
||||||
}) = _Ready;
|
}) = _Ready;
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,115 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pbserver.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
extension SubscriptionLabels on WorkspaceSubscriptionPB {
|
extension SubscriptionLabels on WorkspaceSubscriptionInfoPB {
|
||||||
String get label => switch (subscriptionPlan) {
|
String get label => switch (plan) {
|
||||||
SubscriptionPlanPB.None =>
|
WorkspacePlanPB.FreePlan =>
|
||||||
|
LocaleKeys.settings_planPage_planUsage_currentPlan_freeTitle.tr(),
|
||||||
|
WorkspacePlanPB.ProPlan =>
|
||||||
|
LocaleKeys.settings_planPage_planUsage_currentPlan_proTitle.tr(),
|
||||||
|
WorkspacePlanPB.TeamPlan =>
|
||||||
|
LocaleKeys.settings_planPage_planUsage_currentPlan_teamTitle.tr(),
|
||||||
|
_ => 'N/A',
|
||||||
|
};
|
||||||
|
|
||||||
|
String get info => switch (plan) {
|
||||||
|
WorkspacePlanPB.FreePlan =>
|
||||||
|
LocaleKeys.settings_planPage_planUsage_currentPlan_freeInfo.tr(),
|
||||||
|
WorkspacePlanPB.ProPlan =>
|
||||||
|
LocaleKeys.settings_planPage_planUsage_currentPlan_proInfo.tr(),
|
||||||
|
WorkspacePlanPB.TeamPlan =>
|
||||||
|
LocaleKeys.settings_planPage_planUsage_currentPlan_teamInfo.tr(),
|
||||||
|
_ => 'N/A',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AllSubscriptionLabels on SubscriptionPlanPB {
|
||||||
|
String get label => switch (this) {
|
||||||
|
SubscriptionPlanPB.Free =>
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_freeTitle.tr(),
|
LocaleKeys.settings_planPage_planUsage_currentPlan_freeTitle.tr(),
|
||||||
SubscriptionPlanPB.Pro =>
|
SubscriptionPlanPB.Pro =>
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_proTitle.tr(),
|
LocaleKeys.settings_planPage_planUsage_currentPlan_proTitle.tr(),
|
||||||
SubscriptionPlanPB.Team =>
|
SubscriptionPlanPB.Team =>
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_teamTitle.tr(),
|
LocaleKeys.settings_planPage_planUsage_currentPlan_teamTitle.tr(),
|
||||||
_ => 'N/A',
|
SubscriptionPlanPB.AiMax =>
|
||||||
};
|
LocaleKeys.settings_billingPage_addons_aiMax_label.tr(),
|
||||||
|
SubscriptionPlanPB.AiLocal =>
|
||||||
String get info => switch (subscriptionPlan) {
|
LocaleKeys.settings_billingPage_addons_aiOnDevice_label.tr(),
|
||||||
SubscriptionPlanPB.None =>
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_freeInfo.tr(),
|
|
||||||
SubscriptionPlanPB.Pro =>
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_proInfo.tr(),
|
|
||||||
SubscriptionPlanPB.Team =>
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_teamInfo.tr(),
|
|
||||||
_ => 'N/A',
|
_ => 'N/A',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension WorkspaceSubscriptionStatusExt on WorkspaceSubscriptionInfoPB {
|
||||||
|
bool get isCanceled =>
|
||||||
|
planSubscription.status == WorkspaceSubscriptionStatusPB.Canceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension WorkspaceAddonsExt on WorkspaceSubscriptionInfoPB {
|
||||||
|
bool get hasAIMax =>
|
||||||
|
addOns.any((addon) => addon.type == WorkspaceAddOnPBType.AddOnAiMax);
|
||||||
|
|
||||||
|
bool get hasAIOnDevice =>
|
||||||
|
addOns.any((addon) => addon.type == WorkspaceAddOnPBType.AddOnAiLocal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// These have to match [SubscriptionSuccessListenable.subscribedPlan] labels
|
||||||
|
extension ToRecognizable on SubscriptionPlanPB {
|
||||||
|
String? toRecognizable() => switch (this) {
|
||||||
|
SubscriptionPlanPB.Free => 'free',
|
||||||
|
SubscriptionPlanPB.Pro => 'pro',
|
||||||
|
SubscriptionPlanPB.Team => 'team',
|
||||||
|
SubscriptionPlanPB.AiMax => 'ai_max',
|
||||||
|
SubscriptionPlanPB.AiLocal => 'ai_local',
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PlanHelper on SubscriptionPlanPB {
|
||||||
|
/// Returns true if the plan is an add-on and not
|
||||||
|
/// a workspace plan.
|
||||||
|
///
|
||||||
|
bool get isAddOn => switch (this) {
|
||||||
|
SubscriptionPlanPB.AiMax => true,
|
||||||
|
SubscriptionPlanPB.AiLocal => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
String get priceMonthBilling => switch (this) {
|
||||||
|
SubscriptionPlanPB.Free => 'US\$0',
|
||||||
|
SubscriptionPlanPB.Pro => 'US\$12.5',
|
||||||
|
SubscriptionPlanPB.Team => 'US\$15',
|
||||||
|
SubscriptionPlanPB.AiMax => 'US\$10',
|
||||||
|
SubscriptionPlanPB.AiLocal => 'US\$10',
|
||||||
|
_ => 'US\$0',
|
||||||
|
};
|
||||||
|
|
||||||
|
String get priceAnnualBilling => switch (this) {
|
||||||
|
SubscriptionPlanPB.Free => 'US\$0',
|
||||||
|
SubscriptionPlanPB.Pro => 'US\$10',
|
||||||
|
SubscriptionPlanPB.Team => 'US\$12.5',
|
||||||
|
SubscriptionPlanPB.AiMax => 'US\$8',
|
||||||
|
SubscriptionPlanPB.AiLocal => 'US\$8',
|
||||||
|
_ => 'US\$0',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IntervalLabel on RecurringIntervalPB {
|
||||||
|
String get label => switch (this) {
|
||||||
|
RecurringIntervalPB.Month =>
|
||||||
|
LocaleKeys.settings_billingPage_monthlyInterval.tr(),
|
||||||
|
RecurringIntervalPB.Year =>
|
||||||
|
LocaleKeys.settings_billingPage_annualInterval.tr(),
|
||||||
|
_ => LocaleKeys.settings_billingPage_monthlyInterval.tr(),
|
||||||
|
};
|
||||||
|
|
||||||
|
String get priceInfo => switch (this) {
|
||||||
|
RecurringIntervalPB.Month =>
|
||||||
|
LocaleKeys.settings_billingPage_monthlyPriceInfo.tr(),
|
||||||
|
RecurringIntervalPB.Year =>
|
||||||
|
LocaleKeys.settings_billingPage_annualPriceInfo.tr(),
|
||||||
|
_ => LocaleKeys.settings_billingPage_monthlyPriceInfo.tr(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -6,8 +6,13 @@ final _storageNumberFormat = NumberFormat()
|
|||||||
..minimumFractionDigits = 0;
|
..minimumFractionDigits = 0;
|
||||||
|
|
||||||
extension PresentableUsage on WorkspaceUsagePB {
|
extension PresentableUsage on WorkspaceUsagePB {
|
||||||
String get totalBlobInGb =>
|
String get totalBlobInGb {
|
||||||
(totalBlobBytesLimit.toInt() / 1024 / 1024 / 1024).round().toString();
|
if (storageBytesLimit == 0) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
return _storageNumberFormat
|
||||||
|
.format(storageBytesLimit.toInt() / (1024 * 1024 * 1024));
|
||||||
|
}
|
||||||
|
|
||||||
/// We use [NumberFormat] to format the current blob in GB.
|
/// We use [NumberFormat] to format the current blob in GB.
|
||||||
///
|
///
|
||||||
@ -16,5 +21,5 @@ extension PresentableUsage on WorkspaceUsagePB {
|
|||||||
/// And [NumberFormat.minimumFractionDigits] is set to 0.
|
/// And [NumberFormat.minimumFractionDigits] is set to 0.
|
||||||
///
|
///
|
||||||
String get currentBlobInGb =>
|
String get currentBlobInGb =>
|
||||||
_storageNumberFormat.format(totalBlobBytes.toInt() / 1024 / 1024 / 1024);
|
_storageNumberFormat.format(storageBytes.toInt() / 1024 / 1024 / 1024);
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,11 @@ enum SettingsPage {
|
|||||||
|
|
||||||
class SettingsDialogBloc
|
class SettingsDialogBloc
|
||||||
extends Bloc<SettingsDialogEvent, SettingsDialogState> {
|
extends Bloc<SettingsDialogEvent, SettingsDialogState> {
|
||||||
SettingsDialogBloc(this.userProfile)
|
SettingsDialogBloc(
|
||||||
: _userListener = UserListener(userProfile: userProfile),
|
this.userProfile, {
|
||||||
super(SettingsDialogState.initial(userProfile)) {
|
SettingsPage? initPage,
|
||||||
|
}) : _userListener = UserListener(userProfile: userProfile),
|
||||||
|
super(SettingsDialogState.initial(userProfile, initPage)) {
|
||||||
_dispatch();
|
_dispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,9 +89,12 @@ class SettingsDialogState with _$SettingsDialogState {
|
|||||||
required SettingsPage page,
|
required SettingsPage page,
|
||||||
}) = _SettingsDialogState;
|
}) = _SettingsDialogState;
|
||||||
|
|
||||||
factory SettingsDialogState.initial(UserProfilePB userProfile) =>
|
factory SettingsDialogState.initial(
|
||||||
|
UserProfilePB userProfile,
|
||||||
|
SettingsPage? page,
|
||||||
|
) =>
|
||||||
SettingsDialogState(
|
SettingsDialogState(
|
||||||
userProfile: userProfile,
|
userProfile: userProfile,
|
||||||
page: SettingsPage.account,
|
page: page ?? SettingsPage.account,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,206 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/workspace/application/settings/file_storage/file_storage_listener.dart';
|
||||||
|
import 'package:appflowy/workspace/application/subscription_success_listenable/subscription_success_listenable.dart';
|
||||||
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
|
import 'package:appflowy_backend/dispatch/error.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
part 'sidebar_plan_bloc.freezed.dart';
|
||||||
|
|
||||||
|
class SidebarPlanBloc extends Bloc<SidebarPlanEvent, SidebarPlanState> {
|
||||||
|
SidebarPlanBloc() : super(const SidebarPlanState()) {
|
||||||
|
// After user pays for the subscription, the subscription success listenable will be triggered
|
||||||
|
final subscriptionListener = getIt<SubscriptionSuccessListenable>();
|
||||||
|
subscriptionListener.addListener(() {
|
||||||
|
final plan = subscriptionListener.subscribedPlan;
|
||||||
|
Log.info("Subscription success listenable triggered: $plan");
|
||||||
|
|
||||||
|
if (!isClosed) {
|
||||||
|
// Notify the user that they have switched to a new plan. It would be better if we use websocket to
|
||||||
|
// notify the client when plan switching.
|
||||||
|
if (state.workspaceId != null) {
|
||||||
|
final payload = SuccessWorkspaceSubscriptionPB(
|
||||||
|
workspaceId: state.workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (plan != null) {
|
||||||
|
payload.plan = plan;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserEventNotifyDidSwitchPlan(payload).send().then((result) {
|
||||||
|
result.fold(
|
||||||
|
// After the user has switched to a new plan, we need to refresh the workspace usage.
|
||||||
|
(_) => _checkWorkspaceUsage(),
|
||||||
|
(error) => Log.error("NotifyDidSwitchPlan failed: $error"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Log.error(
|
||||||
|
"Unexpected empty workspace id when subscription success listenable triggered. It should not happen. If happens, it must be a bug",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_storageListener = StoreageNotificationListener(
|
||||||
|
onError: (error) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(SidebarPlanEvent.receiveError(error));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
_globalErrorListener = GlobalErrorCodeNotifier.add(
|
||||||
|
onError: (error) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(SidebarPlanEvent.receiveError(error));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onErrorIf: (error) {
|
||||||
|
const relevantErrorCodes = {
|
||||||
|
ErrorCode.AIResponseLimitExceeded,
|
||||||
|
ErrorCode.FileStorageLimitExceeded,
|
||||||
|
};
|
||||||
|
return relevantErrorCodes.contains(error.code);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
on<SidebarPlanEvent>(_handleEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> dispose() async {
|
||||||
|
if (_globalErrorListener != null) {
|
||||||
|
GlobalErrorCodeNotifier.remove(_globalErrorListener!);
|
||||||
|
}
|
||||||
|
await _storageListener?.stop();
|
||||||
|
_storageListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorListener? _globalErrorListener;
|
||||||
|
StoreageNotificationListener? _storageListener;
|
||||||
|
|
||||||
|
Future<void> _handleEvent(
|
||||||
|
SidebarPlanEvent event,
|
||||||
|
Emitter<SidebarPlanState> emit,
|
||||||
|
) async {
|
||||||
|
await event.when(
|
||||||
|
receiveError: (FlowyError error) async {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
tierIndicator: const SidebarToastTierIndicator.storageLimitHit(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
init: (String workspaceId, UserProfilePB userProfile) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
userProfile: userProfile,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_checkWorkspaceUsage();
|
||||||
|
},
|
||||||
|
updateWorkspaceUsage: (WorkspaceUsagePB usage) {
|
||||||
|
// when the user's storage bytes are limited, show the upgrade tier button
|
||||||
|
if (!usage.storageBytesUnlimited) {
|
||||||
|
if (usage.storageBytes >= usage.storageBytesLimit) {
|
||||||
|
add(
|
||||||
|
const SidebarPlanEvent.updateTierIndicator(
|
||||||
|
SidebarToastTierIndicator.storageLimitHit(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Checks if the user needs to upgrade to the Pro Plan.
|
||||||
|
/// If the user needs to upgrade, it means they don't need to enable the AI max tier.
|
||||||
|
/// This function simply returns without performing any further actions.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when user's AI responses are limited, show the AI max tier button.
|
||||||
|
if (!usage.aiResponsesUnlimited) {
|
||||||
|
if (usage.aiResponsesCount >= usage.aiResponsesCountLimit) {
|
||||||
|
add(
|
||||||
|
const SidebarPlanEvent.updateTierIndicator(
|
||||||
|
SidebarToastTierIndicator.aiMaxiLimitHit(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide the tier indicator
|
||||||
|
add(
|
||||||
|
const SidebarPlanEvent.updateTierIndicator(
|
||||||
|
SidebarToastTierIndicator.loading(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
updateTierIndicator: (SidebarToastTierIndicator indicator) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
tierIndicator: indicator,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkWorkspaceUsage() {
|
||||||
|
if (state.workspaceId != null) {
|
||||||
|
final payload = UserWorkspaceIdPB(workspaceId: state.workspaceId!);
|
||||||
|
UserEventGetWorkspaceUsage(payload).send().then((result) {
|
||||||
|
result.fold(
|
||||||
|
(usage) {
|
||||||
|
add(SidebarPlanEvent.updateWorkspaceUsage(usage));
|
||||||
|
},
|
||||||
|
(error) {
|
||||||
|
Log.error("Failed to get workspace usage, error: $error");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SidebarPlanEvent with _$SidebarPlanEvent {
|
||||||
|
const factory SidebarPlanEvent.init(
|
||||||
|
String workspaceId,
|
||||||
|
UserProfilePB userProfile,
|
||||||
|
) = _Init;
|
||||||
|
const factory SidebarPlanEvent.updateWorkspaceUsage(
|
||||||
|
WorkspaceUsagePB usage,
|
||||||
|
) = _UpdateWorkspaceUsage;
|
||||||
|
const factory SidebarPlanEvent.updateTierIndicator(
|
||||||
|
SidebarToastTierIndicator indicator,
|
||||||
|
) = _UpdateTierIndicator;
|
||||||
|
const factory SidebarPlanEvent.receiveError(FlowyError error) = _ReceiveError;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SidebarPlanState with _$SidebarPlanState {
|
||||||
|
const factory SidebarPlanState({
|
||||||
|
FlowyError? error,
|
||||||
|
UserProfilePB? userProfile,
|
||||||
|
String? workspaceId,
|
||||||
|
WorkspaceUsagePB? usage,
|
||||||
|
@Default(SidebarToastTierIndicator.loading())
|
||||||
|
SidebarToastTierIndicator tierIndicator,
|
||||||
|
}) = _SidebarPlanState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SidebarToastTierIndicator with _$SidebarToastTierIndicator {
|
||||||
|
// when start downloading the model
|
||||||
|
const factory SidebarToastTierIndicator.storageLimitHit() = _StorageLimitHit;
|
||||||
|
const factory SidebarToastTierIndicator.aiMaxiLimitHit() = _aiMaxLimitHit;
|
||||||
|
const factory SidebarToastTierIndicator.loading() = _Loading;
|
||||||
|
}
|
@ -1,7 +1,25 @@
|
|||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
|
|
||||||
class SubscriptionSuccessListenable extends ChangeNotifier {
|
class SubscriptionSuccessListenable extends ChangeNotifier {
|
||||||
SubscriptionSuccessListenable();
|
SubscriptionSuccessListenable();
|
||||||
|
|
||||||
void onPaymentSuccess() => notifyListeners();
|
String? _plan;
|
||||||
|
|
||||||
|
SubscriptionPlanPB? get subscribedPlan => switch (_plan) {
|
||||||
|
'free' => SubscriptionPlanPB.Free,
|
||||||
|
'pro' => SubscriptionPlanPB.Pro,
|
||||||
|
'team' => SubscriptionPlanPB.Team,
|
||||||
|
'ai_max' => SubscriptionPlanPB.AiMax,
|
||||||
|
'ai_local' => SubscriptionPlanPB.AiLocal,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
void onPaymentSuccess(String? plan) {
|
||||||
|
Log.info("Payment success: $plan");
|
||||||
|
_plan = plan;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,195 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/sidebar/billing/sidebar_plan_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.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:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class SidebarToast extends StatefulWidget {
|
||||||
|
const SidebarToast({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SidebarToast> createState() => _SidebarToastState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SidebarToastState extends State<SidebarToast> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocConsumer<SidebarPlanBloc, SidebarPlanState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
// Show a dialog when the user hits the storage limit, After user click ok, it will navigate to the plan page.
|
||||||
|
// Even though the dislog is dissmissed, if the user triggers the storage limit again, the dialog will show again.
|
||||||
|
state.tierIndicator.maybeWhen(
|
||||||
|
storageLimitHit: () {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
|
(_) => _showStorageLimitDialog(context),
|
||||||
|
debugLabel: 'Sidebar.showStorageLimit',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
orElse: () {
|
||||||
|
// Do nothing
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
builder: (context, state) {
|
||||||
|
return BlocBuilder<SidebarPlanBloc, SidebarPlanState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return state.tierIndicator.when(
|
||||||
|
storageLimitHit: () => Column(
|
||||||
|
children: [
|
||||||
|
const Divider(height: 0.6),
|
||||||
|
PlanIndicator(
|
||||||
|
planName: "Pro",
|
||||||
|
text: LocaleKeys.sideBar_upgradeToPro.tr(),
|
||||||
|
onTap: () {
|
||||||
|
_hanldeOnTap(context, SubscriptionPlanPB.Pro);
|
||||||
|
},
|
||||||
|
reason: LocaleKeys.sideBar_storageLimitDialogTitle.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
aiMaxiLimitHit: () => Column(
|
||||||
|
children: [
|
||||||
|
const Divider(height: 0.6),
|
||||||
|
PlanIndicator(
|
||||||
|
planName: "AI Max",
|
||||||
|
text: LocaleKeys.sideBar_upgradeToAIMax.tr(),
|
||||||
|
onTap: () {
|
||||||
|
_hanldeOnTap(context, SubscriptionPlanPB.AiMax);
|
||||||
|
},
|
||||||
|
reason: LocaleKeys.sideBar_aiResponseLitmitDialogTitle.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
loading: () {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showStorageLimitDialog(BuildContext context) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
useRootNavigator: false,
|
||||||
|
builder: (dialogContext) => _StorageLimitDialog(
|
||||||
|
onOkPressed: () {
|
||||||
|
final userProfile = context.read<SidebarPlanBloc>().state.userProfile;
|
||||||
|
final userWorkspaceBloc = context.read<UserWorkspaceBloc>();
|
||||||
|
if (userProfile != null) {
|
||||||
|
showSettingsDialog(
|
||||||
|
context,
|
||||||
|
userProfile,
|
||||||
|
userWorkspaceBloc,
|
||||||
|
SettingsPage.plan,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Log.error(
|
||||||
|
"UserProfile is null. It should not happen. If you see this error, it's a bug.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hanldeOnTap(BuildContext context, SubscriptionPlanPB plan) {
|
||||||
|
final userProfile = context.read<SidebarPlanBloc>().state.userProfile;
|
||||||
|
final userWorkspaceBloc = context.read<UserWorkspaceBloc>();
|
||||||
|
if (userProfile != null) {
|
||||||
|
showSettingsDialog(
|
||||||
|
context,
|
||||||
|
userProfile,
|
||||||
|
userWorkspaceBloc,
|
||||||
|
SettingsPage.plan,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlanIndicator extends StatelessWidget {
|
||||||
|
const PlanIndicator({
|
||||||
|
required this.planName,
|
||||||
|
required this.text,
|
||||||
|
required this.onTap,
|
||||||
|
required this.reason,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String planName;
|
||||||
|
final String reason;
|
||||||
|
final String text;
|
||||||
|
final Function() onTap;
|
||||||
|
|
||||||
|
final textColor = const Color(0xFFE8E2EE);
|
||||||
|
final secondaryColor = const Color(0xFF653E8C);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
FlowyButton(
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||||
|
text: FlowyText(
|
||||||
|
text,
|
||||||
|
color: textColor,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
radius: BorderRadius.zero,
|
||||||
|
leftIconSize: const Size(40, 20),
|
||||||
|
leftIcon: Badge(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
|
backgroundColor: secondaryColor,
|
||||||
|
label: FlowyText.semibold(
|
||||||
|
planName,
|
||||||
|
fontSize: 12,
|
||||||
|
color: textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: onTap,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 6),
|
||||||
|
child: Opacity(
|
||||||
|
opacity: 0.4,
|
||||||
|
child: FlowyText(
|
||||||
|
reason,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
color: textColor,
|
||||||
|
fontSize: 8,
|
||||||
|
maxLines: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StorageLimitDialog extends StatelessWidget {
|
||||||
|
const _StorageLimitDialog({
|
||||||
|
required this.onOkPressed,
|
||||||
|
});
|
||||||
|
final VoidCallback onOkPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return NavigatorOkCancelDialog(
|
||||||
|
message: LocaleKeys.sideBar_storageLimitDialogTitle.tr(),
|
||||||
|
okTitle: LocaleKeys.sideBar_purchaseStorageSpace.tr(),
|
||||||
|
onOkPressed: onOkPressed,
|
||||||
|
titleUpperCase: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
@ -93,6 +94,7 @@ void showSettingsDialog(
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
UserProfilePB userProfile, [
|
UserProfilePB userProfile, [
|
||||||
UserWorkspaceBloc? bloc,
|
UserWorkspaceBloc? bloc,
|
||||||
|
SettingsPage? initPage,
|
||||||
]) {
|
]) {
|
||||||
AFFocusManager.of(context).notifyLoseFocus();
|
AFFocusManager.of(context).notifyLoseFocus();
|
||||||
showDialog(
|
showDialog(
|
||||||
@ -107,6 +109,7 @@ void showSettingsDialog(
|
|||||||
],
|
],
|
||||||
child: SettingsDialog(
|
child: SettingsDialog(
|
||||||
userProfile,
|
userProfile,
|
||||||
|
initPage: initPage,
|
||||||
didLogout: () async {
|
didLogout: () async {
|
||||||
// Pop the dialog using the dialog context
|
// Pop the dialog using the dialog context
|
||||||
Navigator.of(dialogContext).pop();
|
Navigator.of(dialogContext).pop();
|
||||||
|
@ -13,6 +13,7 @@ import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
|||||||
import 'package:appflowy/workspace/application/favorite/prelude.dart';
|
import 'package:appflowy/workspace/application/favorite/prelude.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
|
import 'package:appflowy/workspace/application/recent/cached_recent_service.dart';
|
||||||
|
import 'package:appflowy/workspace/application/sidebar/billing/sidebar_plan_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||||
@ -100,6 +101,9 @@ class HomeSideBar extends StatelessWidget {
|
|||||||
if (state.currentWorkspace == null) {
|
if (state.currentWorkspace == null) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final workspaceId =
|
||||||
|
state.currentWorkspace?.workspaceId ?? workspaceSetting.workspaceId;
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider.value(value: getIt<ActionNavigationBloc>()),
|
BlocProvider.value(value: getIt<ActionNavigationBloc>()),
|
||||||
@ -108,8 +112,7 @@ class HomeSideBar extends StatelessWidget {
|
|||||||
..add(
|
..add(
|
||||||
SidebarSectionsEvent.initial(
|
SidebarSectionsEvent.initial(
|
||||||
userProfile,
|
userProfile,
|
||||||
state.currentWorkspace?.workspaceId ??
|
workspaceId,
|
||||||
workspaceSetting.workspaceId,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -118,12 +121,15 @@ class HomeSideBar extends StatelessWidget {
|
|||||||
..add(
|
..add(
|
||||||
SpaceEvent.initial(
|
SpaceEvent.initial(
|
||||||
userProfile,
|
userProfile,
|
||||||
state.currentWorkspace?.workspaceId ??
|
workspaceId,
|
||||||
workspaceSetting.workspaceId,
|
|
||||||
openFirstPage: false,
|
openFirstPage: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
BlocProvider(
|
||||||
|
create: (_) => SidebarPlanBloc()
|
||||||
|
..add(SidebarPlanEvent.init(workspaceId, userProfile)),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: MultiBlocListener(
|
child: MultiBlocListener(
|
||||||
listeners: [
|
listeners: [
|
||||||
@ -357,6 +363,9 @@ class _SidebarState extends State<_Sidebar> {
|
|||||||
child: const SidebarFooter(),
|
child: const SidebarFooter(),
|
||||||
),
|
),
|
||||||
const VSpace(14),
|
const VSpace(14),
|
||||||
|
|
||||||
|
// toast
|
||||||
|
// const SidebarToast(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/util/theme_extension.dart';
|
import 'package:appflowy/util/theme_extension.dart';
|
||||||
@ -18,9 +22,6 @@ 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:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class SpacePermissionSwitch extends StatefulWidget {
|
class SpacePermissionSwitch extends StatefulWidget {
|
||||||
|
@ -151,7 +151,7 @@ class _ToggleLocalAIDialog extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return NavigatorOkCancelDialog(
|
return NavigatorOkCancelDialog(
|
||||||
title: LocaleKeys.settings_aiPage_keys_disableLocalAIDialog.tr(),
|
message: LocaleKeys.settings_aiPage_keys_disableLocalAIDialog.tr(),
|
||||||
okTitle: LocaleKeys.button_confirm.tr(),
|
okTitle: LocaleKeys.button_confirm.tr(),
|
||||||
cancelTitle: LocaleKeys.button_cancel.tr(),
|
cancelTitle: LocaleKeys.button_cancel.tr(),
|
||||||
onOkPressed: onOkPressed,
|
onOkPressed: onOkPressed,
|
||||||
|
@ -10,7 +10,6 @@ import 'package:appflowy/user/application/auth/auth_service.dart';
|
|||||||
import 'package:appflowy/user/application/prelude.dart';
|
import 'package:appflowy/user/application/prelude.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart';
|
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart';
|
||||||
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
|
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_input_field.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_input_field.dart';
|
||||||
@ -210,18 +209,19 @@ class SignInOutButton extends StatelessWidget {
|
|||||||
if (signIn) {
|
if (signIn) {
|
||||||
_showSignInDialog(context);
|
_showSignInDialog(context);
|
||||||
} else {
|
} else {
|
||||||
SettingsAlertDialog(
|
showConfirmDialog(
|
||||||
|
context: context,
|
||||||
title: LocaleKeys.settings_accountPage_login_logoutLabel.tr(),
|
title: LocaleKeys.settings_accountPage_login_logoutLabel.tr(),
|
||||||
subtitle: switch (userProfile.encryptionType) {
|
description: switch (userProfile.encryptionType) {
|
||||||
EncryptionTypePB.Symmetric =>
|
EncryptionTypePB.Symmetric =>
|
||||||
LocaleKeys.settings_menu_selfEncryptionLogoutPrompt.tr(),
|
LocaleKeys.settings_menu_selfEncryptionLogoutPrompt.tr(),
|
||||||
_ => LocaleKeys.settings_menu_logoutPrompt.tr(),
|
_ => LocaleKeys.settings_menu_logoutPrompt.tr(),
|
||||||
},
|
},
|
||||||
confirm: () async {
|
onConfirm: () async {
|
||||||
await getIt<AuthService>().signOut();
|
await getIt<AuthService>().signOut();
|
||||||
onAction();
|
onAction();
|
||||||
},
|
},
|
||||||
).show(context);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -1,22 +1,34 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
import 'package:appflowy/util/int64_extension.dart';
|
||||||
|
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/billing/settings_billing_bloc.dart';
|
import 'package:appflowy/workspace/application/settings/billing/settings_billing_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/plan/settings_plan_bloc.dart';
|
import 'package:appflowy/workspace/application/settings/plan/settings_plan_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart';
|
import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart';
|
import 'package:appflowy/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_dashed_divider.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/single_setting_action.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/single_setting_action.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../../../generated/locale_keys.g.dart';
|
import '../../../../generated/locale_keys.g.dart';
|
||||||
|
import '../../../../plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
|
||||||
|
|
||||||
class SettingsBillingView extends StatelessWidget {
|
const _buttonsMinWidth = 100.0;
|
||||||
|
|
||||||
|
class SettingsBillingView extends StatefulWidget {
|
||||||
const SettingsBillingView({
|
const SettingsBillingView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.workspaceId,
|
required this.workspaceId,
|
||||||
@ -26,12 +38,34 @@ class SettingsBillingView extends StatelessWidget {
|
|||||||
final String workspaceId;
|
final String workspaceId;
|
||||||
final UserProfilePB user;
|
final UserProfilePB user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SettingsBillingView> createState() => _SettingsBillingViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsBillingViewState extends State<SettingsBillingView> {
|
||||||
|
Loading? loadingIndicator;
|
||||||
|
RecurringIntervalPB? selectedInterval;
|
||||||
|
final ValueNotifier<bool> enablePlanChangeNotifier = ValueNotifier(false);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<SettingsBillingBloc>(
|
return BlocProvider<SettingsBillingBloc>(
|
||||||
create: (context) => SettingsBillingBloc(workspaceId: workspaceId)
|
create: (_) => SettingsBillingBloc(
|
||||||
..add(const SettingsBillingEvent.started()),
|
workspaceId: widget.workspaceId,
|
||||||
child: BlocBuilder<SettingsBillingBloc, SettingsBillingState>(
|
userId: widget.user.id,
|
||||||
|
)..add(const SettingsBillingEvent.started()),
|
||||||
|
child: BlocConsumer<SettingsBillingBloc, SettingsBillingState>(
|
||||||
|
listenWhen: (previous, current) =>
|
||||||
|
previous.mapOrNull(ready: (s) => s.isLoading) !=
|
||||||
|
current.mapOrNull(ready: (s) => s.isLoading),
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state.mapOrNull(ready: (s) => s.isLoading) == true) {
|
||||||
|
loadingIndicator = Loading(context)..start();
|
||||||
|
} else {
|
||||||
|
loadingIndicator?.stop();
|
||||||
|
loadingIndicator = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return state.map(
|
return state.map(
|
||||||
initial: (_) => const SizedBox.shrink(),
|
initial: (_) => const SizedBox.shrink(),
|
||||||
@ -56,8 +90,8 @@ class SettingsBillingView extends StatelessWidget {
|
|||||||
return ErrorWidget.withDetails(message: 'Something went wrong!');
|
return ErrorWidget.withDetails(message: 'Something went wrong!');
|
||||||
},
|
},
|
||||||
ready: (state) {
|
ready: (state) {
|
||||||
final billingPortalEnabled = state.billingPortal != null &&
|
final billingPortalEnabled =
|
||||||
state.billingPortal!.url.isNotEmpty;
|
state.subscriptionInfo.plan != WorkspacePlanPB.FreePlan;
|
||||||
|
|
||||||
return SettingsBody(
|
return SettingsBody(
|
||||||
title: LocaleKeys.settings_billingPage_title.tr(),
|
title: LocaleKeys.settings_billingPage_title.tr(),
|
||||||
@ -68,27 +102,67 @@ class SettingsBillingView extends StatelessWidget {
|
|||||||
SingleSettingAction(
|
SingleSettingAction(
|
||||||
onPressed: () => _openPricingDialog(
|
onPressed: () => _openPricingDialog(
|
||||||
context,
|
context,
|
||||||
workspaceId,
|
widget.workspaceId,
|
||||||
user.id,
|
widget.user.id,
|
||||||
state.subscription,
|
state.subscriptionInfo,
|
||||||
),
|
),
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
label: state.subscription.label,
|
label: state.subscriptionInfo.label,
|
||||||
buttonLabel: LocaleKeys
|
buttonLabel: LocaleKeys
|
||||||
.settings_billingPage_plan_planButtonLabel
|
.settings_billingPage_plan_planButtonLabel
|
||||||
.tr(),
|
.tr(),
|
||||||
|
minWidth: _buttonsMinWidth,
|
||||||
),
|
),
|
||||||
if (billingPortalEnabled)
|
if (billingPortalEnabled)
|
||||||
SingleSettingAction(
|
SingleSettingAction(
|
||||||
onPressed: () =>
|
onPressed: () {
|
||||||
afLaunchUrlString(state.billingPortal!.url),
|
SettingsAlertDialog(
|
||||||
|
title: LocaleKeys
|
||||||
|
.settings_billingPage_changePeriod
|
||||||
|
.tr(),
|
||||||
|
enableConfirmNotifier: enablePlanChangeNotifier,
|
||||||
|
children: [
|
||||||
|
ChangePeriod(
|
||||||
|
plan: state.subscriptionInfo.planSubscription
|
||||||
|
.subscriptionPlan,
|
||||||
|
selectedInterval: state.subscriptionInfo
|
||||||
|
.planSubscription.interval,
|
||||||
|
onSelected: (interval) {
|
||||||
|
enablePlanChangeNotifier.value = interval !=
|
||||||
|
state.subscriptionInfo.planSubscription
|
||||||
|
.interval;
|
||||||
|
selectedInterval = interval;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
confirm: () {
|
||||||
|
if (selectedInterval !=
|
||||||
|
state.subscriptionInfo.planSubscription
|
||||||
|
.interval) {
|
||||||
|
context.read<SettingsBillingBloc>().add(
|
||||||
|
SettingsBillingEvent.updatePeriod(
|
||||||
|
plan: state
|
||||||
|
.subscriptionInfo
|
||||||
|
.planSubscription
|
||||||
|
.subscriptionPlan,
|
||||||
|
interval: selectedInterval!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
).show(context);
|
||||||
|
},
|
||||||
label: LocaleKeys
|
label: LocaleKeys
|
||||||
.settings_billingPage_plan_billingPeriod
|
.settings_billingPage_plan_billingPeriod
|
||||||
.tr(),
|
.tr(),
|
||||||
|
description: state
|
||||||
|
.subscriptionInfo.planSubscription.interval.label,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
buttonLabel: LocaleKeys
|
buttonLabel: LocaleKeys
|
||||||
.settings_billingPage_plan_periodButtonLabel
|
.settings_billingPage_plan_periodButtonLabel
|
||||||
.tr(),
|
.tr(),
|
||||||
|
minWidth: _buttonsMinWidth,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -99,8 +173,11 @@ class SettingsBillingView extends StatelessWidget {
|
|||||||
.tr(),
|
.tr(),
|
||||||
children: [
|
children: [
|
||||||
SingleSettingAction(
|
SingleSettingAction(
|
||||||
onPressed: () =>
|
onPressed: () => context
|
||||||
afLaunchUrlString(state.billingPortal!.url),
|
.read<SettingsBillingBloc>()
|
||||||
|
.add(
|
||||||
|
const SettingsBillingEvent.openCustomerPortal(),
|
||||||
|
),
|
||||||
label: LocaleKeys
|
label: LocaleKeys
|
||||||
.settings_billingPage_paymentDetails_methodLabel
|
.settings_billingPage_paymentDetails_methodLabel
|
||||||
.tr(),
|
.tr(),
|
||||||
@ -108,9 +185,48 @@ class SettingsBillingView extends StatelessWidget {
|
|||||||
buttonLabel: LocaleKeys
|
buttonLabel: LocaleKeys
|
||||||
.settings_billingPage_paymentDetails_methodButtonLabel
|
.settings_billingPage_paymentDetails_methodButtonLabel
|
||||||
.tr(),
|
.tr(),
|
||||||
|
minWidth: _buttonsMinWidth,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
SettingsCategory(
|
||||||
|
title: LocaleKeys.settings_billingPage_addons_title.tr(),
|
||||||
|
children: [
|
||||||
|
_AITile(
|
||||||
|
plan: SubscriptionPlanPB.AiMax,
|
||||||
|
label: LocaleKeys
|
||||||
|
.settings_billingPage_addons_aiMax_label
|
||||||
|
.tr(),
|
||||||
|
description: LocaleKeys
|
||||||
|
.settings_billingPage_addons_aiMax_description,
|
||||||
|
activeDescription: LocaleKeys
|
||||||
|
.settings_billingPage_addons_aiMax_activeDescription,
|
||||||
|
canceledDescription: LocaleKeys
|
||||||
|
.settings_billingPage_addons_aiMax_canceledDescription,
|
||||||
|
subscriptionInfo:
|
||||||
|
state.subscriptionInfo.addOns.firstWhereOrNull(
|
||||||
|
(a) => a.type == WorkspaceAddOnPBType.AddOnAiMax,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SettingsDashedDivider(),
|
||||||
|
_AITile(
|
||||||
|
plan: SubscriptionPlanPB.AiLocal,
|
||||||
|
label: LocaleKeys
|
||||||
|
.settings_billingPage_addons_aiOnDevice_label
|
||||||
|
.tr(),
|
||||||
|
description: LocaleKeys
|
||||||
|
.settings_billingPage_addons_aiOnDevice_description,
|
||||||
|
activeDescription: LocaleKeys
|
||||||
|
.settings_billingPage_addons_aiOnDevice_activeDescription,
|
||||||
|
canceledDescription: LocaleKeys
|
||||||
|
.settings_billingPage_addons_aiOnDevice_canceledDescription,
|
||||||
|
subscriptionInfo:
|
||||||
|
state.subscriptionInfo.addOns.firstWhereOrNull(
|
||||||
|
(a) => a.type == WorkspaceAddOnPBType.AddOnAiLocal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -124,17 +240,17 @@ class SettingsBillingView extends StatelessWidget {
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
String workspaceId,
|
String workspaceId,
|
||||||
Int64 userId,
|
Int64 userId,
|
||||||
WorkspaceSubscriptionPB subscription,
|
WorkspaceSubscriptionInfoPB subscriptionInfo,
|
||||||
) =>
|
) =>
|
||||||
showDialog<bool?>(
|
showDialog<bool?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => BlocProvider<SettingsPlanBloc>(
|
builder: (_) => BlocProvider<SettingsPlanBloc>(
|
||||||
create: (_) =>
|
create: (_) =>
|
||||||
SettingsPlanBloc(workspaceId: workspaceId, userId: user.id)
|
SettingsPlanBloc(workspaceId: workspaceId, userId: widget.user.id)
|
||||||
..add(const SettingsPlanEvent.started()),
|
..add(const SettingsPlanEvent.started()),
|
||||||
child: SettingsPlanComparisonDialog(
|
child: SettingsPlanComparisonDialog(
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
subscription: subscription,
|
subscriptionInfo: subscriptionInfo,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).then((didChangePlan) {
|
).then((didChangePlan) {
|
||||||
@ -145,3 +261,341 @@ class SettingsBillingView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _AITile extends StatefulWidget {
|
||||||
|
const _AITile({
|
||||||
|
required this.label,
|
||||||
|
required this.description,
|
||||||
|
required this.canceledDescription,
|
||||||
|
required this.activeDescription,
|
||||||
|
required this.plan,
|
||||||
|
this.subscriptionInfo,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
final String description;
|
||||||
|
final String canceledDescription;
|
||||||
|
final String activeDescription;
|
||||||
|
final SubscriptionPlanPB plan;
|
||||||
|
final WorkspaceAddOnPB? subscriptionInfo;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_AITile> createState() => _AITileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AITileState extends State<_AITile> {
|
||||||
|
RecurringIntervalPB? selectedInterval;
|
||||||
|
|
||||||
|
final enableConfirmNotifier = ValueNotifier<bool>(false);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isCanceled = widget.subscriptionInfo?.addOnSubscription.status ==
|
||||||
|
WorkspaceSubscriptionStatusPB.Canceled;
|
||||||
|
|
||||||
|
final dateFormat = context.read<AppearanceSettingsCubit>().state.dateFormat;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
SingleSettingAction(
|
||||||
|
label: widget.label,
|
||||||
|
description: widget.subscriptionInfo != null && isCanceled
|
||||||
|
? widget.canceledDescription.tr(
|
||||||
|
args: [
|
||||||
|
dateFormat.formatDate(
|
||||||
|
widget.subscriptionInfo!.addOnSubscription.endDate
|
||||||
|
.toDateTime(),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: widget.subscriptionInfo != null
|
||||||
|
? widget.activeDescription.tr(
|
||||||
|
args: [
|
||||||
|
dateFormat.formatDate(
|
||||||
|
widget.subscriptionInfo!.addOnSubscription.endDate
|
||||||
|
.toDateTime(),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: widget.description.tr(),
|
||||||
|
buttonLabel: widget.subscriptionInfo != null
|
||||||
|
? isCanceled
|
||||||
|
? LocaleKeys.settings_billingPage_addons_renewLabel.tr()
|
||||||
|
: LocaleKeys.settings_billingPage_addons_removeLabel.tr()
|
||||||
|
: LocaleKeys.settings_billingPage_addons_addLabel.tr(),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
minWidth: _buttonsMinWidth,
|
||||||
|
onPressed: () {
|
||||||
|
if (widget.subscriptionInfo != null && isCanceled) {
|
||||||
|
// Show customer portal to renew
|
||||||
|
context
|
||||||
|
.read<SettingsBillingBloc>()
|
||||||
|
.add(const SettingsBillingEvent.openCustomerPortal());
|
||||||
|
} else if (widget.subscriptionInfo != null) {
|
||||||
|
showConfirmDialog(
|
||||||
|
context: context,
|
||||||
|
style: ConfirmPopupStyle.cancelAndOk,
|
||||||
|
title: LocaleKeys.settings_billingPage_addons_removeDialog_title
|
||||||
|
.tr(args: [widget.plan.label]).tr(),
|
||||||
|
description: LocaleKeys
|
||||||
|
.settings_billingPage_addons_removeDialog_description
|
||||||
|
.tr(namedArgs: {"plan": widget.plan.label.tr()}),
|
||||||
|
confirmLabel: LocaleKeys.button_confirm.tr(),
|
||||||
|
onConfirm: () {
|
||||||
|
context.read<SettingsBillingBloc>().add(
|
||||||
|
SettingsBillingEvent.cancelSubscription(widget.plan),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Add the addon
|
||||||
|
context
|
||||||
|
.read<SettingsBillingBloc>()
|
||||||
|
.add(SettingsBillingEvent.addSubscription(widget.plan));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (widget.subscriptionInfo != null) ...[
|
||||||
|
const VSpace(10),
|
||||||
|
SingleSettingAction(
|
||||||
|
label: LocaleKeys.settings_billingPage_planPeriod.tr(
|
||||||
|
args: [
|
||||||
|
widget
|
||||||
|
.subscriptionInfo!.addOnSubscription.subscriptionPlan.label,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
description:
|
||||||
|
widget.subscriptionInfo!.addOnSubscription.interval.label,
|
||||||
|
buttonLabel:
|
||||||
|
LocaleKeys.settings_billingPage_plan_periodButtonLabel.tr(),
|
||||||
|
minWidth: _buttonsMinWidth,
|
||||||
|
onPressed: () {
|
||||||
|
enableConfirmNotifier.value = false;
|
||||||
|
SettingsAlertDialog(
|
||||||
|
title: LocaleKeys.settings_billingPage_changePeriod.tr(),
|
||||||
|
enableConfirmNotifier: enableConfirmNotifier,
|
||||||
|
children: [
|
||||||
|
ChangePeriod(
|
||||||
|
plan: widget
|
||||||
|
.subscriptionInfo!.addOnSubscription.subscriptionPlan,
|
||||||
|
selectedInterval:
|
||||||
|
widget.subscriptionInfo!.addOnSubscription.interval,
|
||||||
|
onSelected: (interval) {
|
||||||
|
enableConfirmNotifier.value = interval !=
|
||||||
|
widget.subscriptionInfo!.addOnSubscription.interval;
|
||||||
|
selectedInterval = interval;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
confirm: () {
|
||||||
|
if (selectedInterval !=
|
||||||
|
widget.subscriptionInfo!.addOnSubscription.interval) {
|
||||||
|
context.read<SettingsBillingBloc>().add(
|
||||||
|
SettingsBillingEvent.updatePeriod(
|
||||||
|
plan: widget.subscriptionInfo!.addOnSubscription
|
||||||
|
.subscriptionPlan,
|
||||||
|
interval: selectedInterval!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
).show(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangePeriod extends StatefulWidget {
|
||||||
|
const ChangePeriod({
|
||||||
|
super.key,
|
||||||
|
required this.plan,
|
||||||
|
required this.selectedInterval,
|
||||||
|
required this.onSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
final SubscriptionPlanPB plan;
|
||||||
|
final RecurringIntervalPB selectedInterval;
|
||||||
|
final Function(RecurringIntervalPB interval) onSelected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChangePeriod> createState() => _ChangePeriodState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChangePeriodState extends State<ChangePeriod> {
|
||||||
|
RecurringIntervalPB? _selectedInterval;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_selectedInterval = widget.selectedInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
_selectedInterval = widget.selectedInterval;
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
_PeriodSelector(
|
||||||
|
price: widget.plan.priceMonthBilling,
|
||||||
|
interval: RecurringIntervalPB.Month,
|
||||||
|
isSelected: _selectedInterval == RecurringIntervalPB.Month,
|
||||||
|
isCurrent: widget.selectedInterval == RecurringIntervalPB.Month,
|
||||||
|
onSelected: () {
|
||||||
|
widget.onSelected(RecurringIntervalPB.Month);
|
||||||
|
setState(
|
||||||
|
() => _selectedInterval = RecurringIntervalPB.Month,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const VSpace(16),
|
||||||
|
_PeriodSelector(
|
||||||
|
price: widget.plan.priceAnnualBilling,
|
||||||
|
interval: RecurringIntervalPB.Year,
|
||||||
|
isSelected: _selectedInterval == RecurringIntervalPB.Year,
|
||||||
|
isCurrent: widget.selectedInterval == RecurringIntervalPB.Year,
|
||||||
|
onSelected: () {
|
||||||
|
widget.onSelected(RecurringIntervalPB.Year);
|
||||||
|
setState(
|
||||||
|
() => _selectedInterval = RecurringIntervalPB.Year,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PeriodSelector extends StatelessWidget {
|
||||||
|
const _PeriodSelector({
|
||||||
|
required this.price,
|
||||||
|
required this.interval,
|
||||||
|
required this.onSelected,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.isCurrent,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String price;
|
||||||
|
final RecurringIntervalPB interval;
|
||||||
|
final VoidCallback onSelected;
|
||||||
|
final bool isSelected;
|
||||||
|
final bool isCurrent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Opacity(
|
||||||
|
opacity: isCurrent && !isSelected ? 0.7 : 1,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: isCurrent ? null : onSelected,
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).dividerColor,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
FlowyText(
|
||||||
|
interval.label,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
if (isCurrent) ...[
|
||||||
|
const HSpace(8),
|
||||||
|
DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 6,
|
||||||
|
vertical: 1,
|
||||||
|
),
|
||||||
|
child: FlowyText(
|
||||||
|
LocaleKeys
|
||||||
|
.settings_billingPage_currentPeriodBadge
|
||||||
|
.tr(),
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const VSpace(8),
|
||||||
|
FlowyText(
|
||||||
|
price,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
const VSpace(4),
|
||||||
|
FlowyText(
|
||||||
|
interval.priceInfo,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (!isCurrent && !isSelected || isSelected) ...[
|
||||||
|
DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
width: 1.5,
|
||||||
|
color: isSelected
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).dividerColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 22,
|
||||||
|
width: 22,
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: isSelected
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Colors.transparent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
@ -12,7 +15,6 @@ import 'package:appflowy/workspace/application/settings/settings_location_cubit.
|
|||||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/pages/fix_data_widget.dart';
|
import 'package:appflowy/workspace/presentation/settings/pages/fix_data_widget.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/setting_action.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/setting_action.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/single_setting_action.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/single_setting_action.dart';
|
||||||
@ -27,8 +29,6 @@ import 'package:flowy_infra_ui/style_widget/button.dart';
|
|||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
|
||||||
@ -63,15 +63,15 @@ class SettingsManageDataView extends StatelessWidget {
|
|||||||
size: Size.square(20),
|
size: Size.square(20),
|
||||||
),
|
),
|
||||||
label: LocaleKeys.settings_common_reset.tr(),
|
label: LocaleKeys.settings_common_reset.tr(),
|
||||||
onPressed: () => SettingsAlertDialog(
|
onPressed: () => showConfirmDialog(
|
||||||
|
context: context,
|
||||||
title: LocaleKeys
|
title: LocaleKeys
|
||||||
.settings_manageDataPage_dataStorage_resetDialog_title
|
.settings_manageDataPage_dataStorage_resetDialog_title
|
||||||
.tr(),
|
.tr(),
|
||||||
subtitle: LocaleKeys
|
description: LocaleKeys
|
||||||
.settings_manageDataPage_dataStorage_resetDialog_description
|
.settings_manageDataPage_dataStorage_resetDialog_description
|
||||||
.tr(),
|
.tr(),
|
||||||
implyLeading: true,
|
onConfirm: () async {
|
||||||
confirm: () async {
|
|
||||||
final directory =
|
final directory =
|
||||||
await appFlowyApplicationDataDirectory();
|
await appFlowyApplicationDataDirectory();
|
||||||
final path = directory.path;
|
final path = directory.path;
|
||||||
@ -85,10 +85,8 @@ class SettingsManageDataView extends StatelessWidget {
|
|||||||
.read<SettingsLocationCubit>()
|
.read<SettingsLocationCubit>()
|
||||||
.resetDataStoragePathToApplicationDefault();
|
.resetDataStoragePathToApplicationDefault();
|
||||||
await runAppFlowy(isAnon: true);
|
await runAppFlowy(isAnon: true);
|
||||||
|
|
||||||
if (context.mounted) Navigator.of(context).pop();
|
|
||||||
},
|
},
|
||||||
).show(context),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
children: state
|
children: state
|
||||||
|
@ -1,27 +1,30 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
|
||||||
import 'package:appflowy/util/theme_extension.dart';
|
import 'package:appflowy/util/theme_extension.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/plan/settings_plan_bloc.dart';
|
import 'package:appflowy/workspace/application/settings/plan/settings_plan_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart';
|
import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../../../generated/locale_keys.g.dart';
|
||||||
|
import '../../../../plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
|
||||||
|
|
||||||
class SettingsPlanComparisonDialog extends StatefulWidget {
|
class SettingsPlanComparisonDialog extends StatefulWidget {
|
||||||
const SettingsPlanComparisonDialog({
|
const SettingsPlanComparisonDialog({
|
||||||
super.key,
|
super.key,
|
||||||
required this.workspaceId,
|
required this.workspaceId,
|
||||||
required this.subscription,
|
required this.subscriptionInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String workspaceId;
|
final String workspaceId;
|
||||||
final WorkspaceSubscriptionPB subscription;
|
final WorkspaceSubscriptionInfoPB subscriptionInfo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SettingsPlanComparisonDialog> createState() =>
|
State<SettingsPlanComparisonDialog> createState() =>
|
||||||
@ -33,7 +36,9 @@ class _SettingsPlanComparisonDialogState
|
|||||||
final horizontalController = ScrollController();
|
final horizontalController = ScrollController();
|
||||||
final verticalController = ScrollController();
|
final verticalController = ScrollController();
|
||||||
|
|
||||||
late WorkspaceSubscriptionPB currentSubscription = widget.subscription;
|
late WorkspaceSubscriptionInfoPB currentInfo = widget.subscriptionInfo;
|
||||||
|
|
||||||
|
Loading? loadingIndicator;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -54,32 +59,27 @@ class _SettingsPlanComparisonDialogState
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readyState.showSuccessDialog) {
|
if (readyState.downgradeProcessing) {
|
||||||
SettingsAlertDialog(
|
loadingIndicator = Loading(context)..start();
|
||||||
icon: Center(
|
} else {
|
||||||
child: SizedBox(
|
loadingIndicator?.stop();
|
||||||
height: 90,
|
loadingIndicator = null;
|
||||||
width: 90,
|
|
||||||
child: FlowySvg(
|
|
||||||
FlowySvgs.check_circle_s,
|
|
||||||
color: AFThemeExtension.of(context).success,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: LocaleKeys.settings_comparePlanDialog_paymentSuccess_title
|
|
||||||
.tr(args: [readyState.subscription.label]),
|
|
||||||
subtitle: LocaleKeys
|
|
||||||
.settings_comparePlanDialog_paymentSuccess_description
|
|
||||||
.tr(args: [readyState.subscription.label]),
|
|
||||||
hideCancelButton: true,
|
|
||||||
confirm: Navigator.of(context).pop,
|
|
||||||
confirmLabel: LocaleKeys.button_close.tr(),
|
|
||||||
).show(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setState(() {
|
if (readyState.successfulPlanUpgrade != null) {
|
||||||
currentSubscription = readyState.subscription;
|
showConfirmDialog(
|
||||||
});
|
context: context,
|
||||||
|
title: LocaleKeys.settings_comparePlanDialog_paymentSuccess_title
|
||||||
|
.tr(args: [readyState.successfulPlanUpgrade!.label]),
|
||||||
|
description: LocaleKeys
|
||||||
|
.settings_comparePlanDialog_paymentSuccess_description
|
||||||
|
.tr(args: [readyState.successfulPlanUpgrade!.label]),
|
||||||
|
confirmLabel: LocaleKeys.button_close.tr(),
|
||||||
|
onConfirm: () {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => currentInfo = readyState.subscriptionInfo);
|
||||||
},
|
},
|
||||||
builder: (context, state) => FlowyDialog(
|
builder: (context, state) => FlowyDialog(
|
||||||
constraints: const BoxConstraints(maxWidth: 784, minWidth: 674),
|
constraints: const BoxConstraints(maxWidth: 784, minWidth: 674),
|
||||||
@ -99,8 +99,7 @@ class _SettingsPlanComparisonDialogState
|
|||||||
const Spacer(),
|
const Spacer(),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => Navigator.of(context).pop(
|
onTap: () => Navigator.of(context).pop(
|
||||||
currentSubscription.subscriptionPlan !=
|
currentInfo.plan != widget.subscriptionInfo.plan,
|
||||||
widget.subscription.subscriptionPlan,
|
|
||||||
),
|
),
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
@ -154,7 +153,7 @@ class _SettingsPlanComparisonDialogState
|
|||||||
: const Color(0xFFE8E0FF),
|
: const Color(0xFFE8E0FF),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 64),
|
const SizedBox(height: 96),
|
||||||
const SizedBox(height: 56),
|
const SizedBox(height: 56),
|
||||||
..._planLabels.map(
|
..._planLabels.map(
|
||||||
(e) => _ComparisonCell(
|
(e) => _ComparisonCell(
|
||||||
@ -172,56 +171,55 @@ class _SettingsPlanComparisonDialogState
|
|||||||
description: LocaleKeys
|
description: LocaleKeys
|
||||||
.settings_comparePlanDialog_freePlan_description
|
.settings_comparePlanDialog_freePlan_description
|
||||||
.tr(),
|
.tr(),
|
||||||
// TODO(Mathias): the price should be dynamic based on the country and currency
|
|
||||||
price: LocaleKeys
|
price: LocaleKeys
|
||||||
.settings_comparePlanDialog_freePlan_price
|
.settings_comparePlanDialog_freePlan_price
|
||||||
.tr(args: ['\$0']),
|
.tr(
|
||||||
|
args: [
|
||||||
|
SubscriptionPlanPB.Free.priceMonthBilling,
|
||||||
|
],
|
||||||
|
),
|
||||||
priceInfo: LocaleKeys
|
priceInfo: LocaleKeys
|
||||||
.settings_comparePlanDialog_freePlan_priceInfo
|
.settings_comparePlanDialog_freePlan_priceInfo
|
||||||
.tr(),
|
.tr(),
|
||||||
cells: _freeLabels,
|
cells: _freeLabels,
|
||||||
isCurrent: currentSubscription.subscriptionPlan ==
|
isCurrent:
|
||||||
SubscriptionPlanPB.None,
|
currentInfo.plan == WorkspacePlanPB.FreePlan,
|
||||||
canDowngrade:
|
canDowngrade:
|
||||||
currentSubscription.subscriptionPlan !=
|
currentInfo.plan != WorkspacePlanPB.FreePlan,
|
||||||
SubscriptionPlanPB.None,
|
currentCanceled: currentInfo.isCanceled ||
|
||||||
currentCanceled: currentSubscription.hasCanceled ||
|
|
||||||
(context
|
(context
|
||||||
.watch<SettingsPlanBloc>()
|
.watch<SettingsPlanBloc>()
|
||||||
.state
|
.state
|
||||||
.mapOrNull(
|
.mapOrNull(
|
||||||
loading: (_) => true,
|
loading: (_) => true,
|
||||||
ready: (state) =>
|
ready: (s) => s.downgradeProcessing,
|
||||||
state.downgradeProcessing,
|
|
||||||
) ??
|
) ??
|
||||||
false),
|
false),
|
||||||
onSelected: () async {
|
onSelected: () async {
|
||||||
if (currentSubscription.subscriptionPlan ==
|
if (currentInfo.plan ==
|
||||||
SubscriptionPlanPB.None ||
|
WorkspacePlanPB.FreePlan ||
|
||||||
currentSubscription.hasCanceled) {
|
currentInfo.isCanceled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SettingsAlertDialog(
|
await showConfirmDialog(
|
||||||
|
context: context,
|
||||||
title: LocaleKeys
|
title: LocaleKeys
|
||||||
.settings_comparePlanDialog_downgradeDialog_title
|
.settings_comparePlanDialog_downgradeDialog_title
|
||||||
.tr(args: [currentSubscription.label]),
|
.tr(args: [currentInfo.label]),
|
||||||
subtitle: LocaleKeys
|
description: LocaleKeys
|
||||||
.settings_comparePlanDialog_downgradeDialog_description
|
.settings_comparePlanDialog_downgradeDialog_description
|
||||||
.tr(),
|
.tr(),
|
||||||
isDangerous: true,
|
|
||||||
confirm: () {
|
|
||||||
context.read<SettingsPlanBloc>().add(
|
|
||||||
const SettingsPlanEvent
|
|
||||||
.cancelSubscription(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
confirmLabel: LocaleKeys
|
confirmLabel: LocaleKeys
|
||||||
.settings_comparePlanDialog_downgradeDialog_downgradeLabel
|
.settings_comparePlanDialog_downgradeDialog_downgradeLabel
|
||||||
.tr(),
|
.tr(),
|
||||||
).show(context);
|
style: ConfirmPopupStyle.cancelAndOk,
|
||||||
|
onConfirm: () =>
|
||||||
|
context.read<SettingsPlanBloc>().add(
|
||||||
|
const SettingsPlanEvent
|
||||||
|
.cancelSubscription(),
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_PlanTable(
|
_PlanTable(
|
||||||
@ -231,19 +229,22 @@ class _SettingsPlanComparisonDialogState
|
|||||||
description: LocaleKeys
|
description: LocaleKeys
|
||||||
.settings_comparePlanDialog_proPlan_description
|
.settings_comparePlanDialog_proPlan_description
|
||||||
.tr(),
|
.tr(),
|
||||||
// TODO(Mathias): the price should be dynamic based on the country and currency
|
|
||||||
price: LocaleKeys
|
price: LocaleKeys
|
||||||
.settings_comparePlanDialog_proPlan_price
|
.settings_comparePlanDialog_proPlan_price
|
||||||
.tr(args: ['\$10 ']),
|
.tr(
|
||||||
|
args: [SubscriptionPlanPB.Pro.priceAnnualBilling],
|
||||||
|
),
|
||||||
priceInfo: LocaleKeys
|
priceInfo: LocaleKeys
|
||||||
.settings_comparePlanDialog_proPlan_priceInfo
|
.settings_comparePlanDialog_proPlan_priceInfo
|
||||||
.tr(),
|
.tr(
|
||||||
|
args: [SubscriptionPlanPB.Pro.priceMonthBilling],
|
||||||
|
),
|
||||||
cells: _proLabels,
|
cells: _proLabels,
|
||||||
isCurrent: currentSubscription.subscriptionPlan ==
|
isCurrent:
|
||||||
SubscriptionPlanPB.Pro,
|
currentInfo.plan == WorkspacePlanPB.ProPlan,
|
||||||
canUpgrade: currentSubscription.subscriptionPlan ==
|
canUpgrade:
|
||||||
SubscriptionPlanPB.None,
|
currentInfo.plan == WorkspacePlanPB.FreePlan,
|
||||||
currentCanceled: currentSubscription.hasCanceled,
|
currentCanceled: currentInfo.isCanceled,
|
||||||
onSelected: () =>
|
onSelected: () =>
|
||||||
context.read<SettingsPlanBloc>().add(
|
context.read<SettingsPlanBloc>().add(
|
||||||
const SettingsPlanEvent.addSubscription(
|
const SettingsPlanEvent.addSubscription(
|
||||||
@ -335,7 +336,7 @@ class _PlanTable extends StatelessWidget {
|
|||||||
title: price,
|
title: price,
|
||||||
description: priceInfo,
|
description: priceInfo,
|
||||||
isPrimary: !highlightPlan,
|
isPrimary: !highlightPlan,
|
||||||
height: 64,
|
height: 96,
|
||||||
),
|
),
|
||||||
if (canUpgrade || canDowngrade) ...[
|
if (canUpgrade || canDowngrade) ...[
|
||||||
Opacity(
|
Opacity(
|
||||||
@ -589,21 +590,28 @@ class _Heading extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: 175,
|
width: 185,
|
||||||
height: height,
|
height: height,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(left: 12 + (!isPrimary ? 12 : 0)),
|
padding: EdgeInsets.only(left: 12 + (!isPrimary ? 12 : 0)),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
FlowyText.semibold(
|
Row(
|
||||||
title,
|
children: [
|
||||||
fontSize: 24,
|
Expanded(
|
||||||
color: isPrimary
|
child: FlowyText.semibold(
|
||||||
? AFThemeExtension.of(context).strongText
|
title,
|
||||||
: Theme.of(context).isLightMode
|
fontSize: 24,
|
||||||
? const Color(0xFF5C3699)
|
overflow: TextOverflow.ellipsis,
|
||||||
: const Color(0xFFC49BEC),
|
color: isPrimary
|
||||||
|
? AFThemeExtension.of(context).strongText
|
||||||
|
: Theme.of(context).isLightMode
|
||||||
|
? const Color(0xFF5C3699)
|
||||||
|
: const Color(0xFFC49BEC),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
if (description != null && description!.isNotEmpty) ...[
|
if (description != null && description!.isNotEmpty) ...[
|
||||||
const VSpace(4),
|
const VSpace(4),
|
||||||
@ -637,24 +645,20 @@ final _planLabels = [
|
|||||||
),
|
),
|
||||||
_PlanItem(
|
_PlanItem(
|
||||||
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemThree.tr(),
|
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemThree.tr(),
|
||||||
tooltip: LocaleKeys.settings_comparePlanDialog_planLabels_tooltipThree.tr(),
|
|
||||||
),
|
),
|
||||||
_PlanItem(
|
_PlanItem(
|
||||||
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemFour.tr(),
|
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemFour.tr(),
|
||||||
tooltip: LocaleKeys.settings_comparePlanDialog_planLabels_tooltipFour.tr(),
|
|
||||||
),
|
),
|
||||||
_PlanItem(
|
_PlanItem(
|
||||||
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemFive.tr(),
|
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemFive.tr(),
|
||||||
),
|
),
|
||||||
_PlanItem(
|
_PlanItem(
|
||||||
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemSix.tr(),
|
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemSix.tr(),
|
||||||
|
tooltip: LocaleKeys.settings_comparePlanDialog_planLabels_tooltipSix.tr(),
|
||||||
),
|
),
|
||||||
_PlanItem(
|
_PlanItem(
|
||||||
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemSeven.tr(),
|
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemSeven.tr(),
|
||||||
),
|
tooltip: LocaleKeys.settings_comparePlanDialog_planLabels_tooltipSeven.tr(),
|
||||||
_PlanItem(
|
|
||||||
label: LocaleKeys.settings_comparePlanDialog_planLabels_itemEight.tr(),
|
|
||||||
tooltip: LocaleKeys.settings_comparePlanDialog_planLabels_tooltipEight.tr(),
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -677,20 +681,17 @@ final List<_CellItem> _freeLabels = [
|
|||||||
),
|
),
|
||||||
_CellItem(
|
_CellItem(
|
||||||
LocaleKeys.settings_comparePlanDialog_freeLabels_itemFour.tr(),
|
LocaleKeys.settings_comparePlanDialog_freeLabels_itemFour.tr(),
|
||||||
|
icon: FlowySvgs.check_m,
|
||||||
),
|
),
|
||||||
_CellItem(
|
_CellItem(
|
||||||
LocaleKeys.settings_comparePlanDialog_freeLabels_itemFive.tr(),
|
LocaleKeys.settings_comparePlanDialog_freeLabels_itemFive.tr(),
|
||||||
|
icon: FlowySvgs.check_m,
|
||||||
),
|
),
|
||||||
_CellItem(
|
_CellItem(
|
||||||
LocaleKeys.settings_comparePlanDialog_freeLabels_itemSix.tr(),
|
LocaleKeys.settings_comparePlanDialog_freeLabels_itemSix.tr(),
|
||||||
icon: FlowySvgs.check_m,
|
|
||||||
),
|
),
|
||||||
_CellItem(
|
_CellItem(
|
||||||
LocaleKeys.settings_comparePlanDialog_freeLabels_itemSeven.tr(),
|
LocaleKeys.settings_comparePlanDialog_freeLabels_itemSeven.tr(),
|
||||||
icon: FlowySvgs.check_m,
|
|
||||||
),
|
|
||||||
_CellItem(
|
|
||||||
LocaleKeys.settings_comparePlanDialog_freeLabels_itemEight.tr(),
|
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -706,19 +707,17 @@ final List<_CellItem> _proLabels = [
|
|||||||
),
|
),
|
||||||
_CellItem(
|
_CellItem(
|
||||||
LocaleKeys.settings_comparePlanDialog_proLabels_itemFour.tr(),
|
LocaleKeys.settings_comparePlanDialog_proLabels_itemFour.tr(),
|
||||||
|
icon: FlowySvgs.check_m,
|
||||||
),
|
),
|
||||||
_CellItem(
|
_CellItem(
|
||||||
LocaleKeys.settings_comparePlanDialog_proLabels_itemFive.tr(),
|
LocaleKeys.settings_comparePlanDialog_proLabels_itemFive.tr(),
|
||||||
|
icon: FlowySvgs.check_m,
|
||||||
),
|
),
|
||||||
_CellItem(
|
_CellItem(
|
||||||
LocaleKeys.settings_comparePlanDialog_proLabels_itemSix.tr(),
|
LocaleKeys.settings_comparePlanDialog_proLabels_itemSix.tr(),
|
||||||
icon: FlowySvgs.check_m,
|
|
||||||
),
|
),
|
||||||
_CellItem(
|
_CellItem(
|
||||||
LocaleKeys.settings_comparePlanDialog_proLabels_itemSeven.tr(),
|
LocaleKeys.settings_comparePlanDialog_proLabels_itemSeven.tr(),
|
||||||
icon: FlowySvgs.check_m,
|
icon: FlowySvgs.check_m,
|
||||||
),
|
),
|
||||||
_CellItem(
|
|
||||||
LocaleKeys.settings_comparePlanDialog_proLabels_itemEight.tr(),
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/util/int64_extension.dart';
|
import 'package:appflowy/util/int64_extension.dart';
|
||||||
@ -14,13 +16,15 @@ import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class SettingsPlanView extends StatelessWidget {
|
import '../../../../plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
|
||||||
|
|
||||||
|
class SettingsPlanView extends StatefulWidget {
|
||||||
const SettingsPlanView({
|
const SettingsPlanView({
|
||||||
super.key,
|
super.key,
|
||||||
required this.workspaceId,
|
required this.workspaceId,
|
||||||
@ -30,14 +34,32 @@ class SettingsPlanView extends StatelessWidget {
|
|||||||
final String workspaceId;
|
final String workspaceId;
|
||||||
final UserProfilePB user;
|
final UserProfilePB user;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SettingsPlanView> createState() => _SettingsPlanViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SettingsPlanViewState extends State<SettingsPlanView> {
|
||||||
|
Loading? loadingIndicator;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<SettingsPlanBloc>(
|
return BlocProvider<SettingsPlanBloc>(
|
||||||
create: (context) => SettingsPlanBloc(
|
create: (context) => SettingsPlanBloc(
|
||||||
workspaceId: workspaceId,
|
workspaceId: widget.workspaceId,
|
||||||
userId: user.id,
|
userId: widget.user.id,
|
||||||
)..add(const SettingsPlanEvent.started()),
|
)..add(const SettingsPlanEvent.started()),
|
||||||
child: BlocBuilder<SettingsPlanBloc, SettingsPlanState>(
|
child: BlocConsumer<SettingsPlanBloc, SettingsPlanState>(
|
||||||
|
listenWhen: (previous, current) =>
|
||||||
|
previous.mapOrNull(ready: (s) => s.downgradeProcessing) !=
|
||||||
|
current.mapOrNull(ready: (s) => s.downgradeProcessing),
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state.mapOrNull(ready: (s) => s.downgradeProcessing) == true) {
|
||||||
|
loadingIndicator = Loading(context)..start();
|
||||||
|
} else {
|
||||||
|
loadingIndicator?.stop();
|
||||||
|
loadingIndicator = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return state.map(
|
return state.map(
|
||||||
initial: (_) => const SizedBox.shrink(),
|
initial: (_) => const SizedBox.shrink(),
|
||||||
@ -67,10 +89,87 @@ class SettingsPlanView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
_PlanUsageSummary(
|
_PlanUsageSummary(
|
||||||
usage: state.workspaceUsage,
|
usage: state.workspaceUsage,
|
||||||
subscription: state.subscription,
|
subscriptionInfo: state.subscriptionInfo,
|
||||||
),
|
),
|
||||||
const VSpace(16),
|
const VSpace(16),
|
||||||
_CurrentPlanBox(subscription: state.subscription),
|
_CurrentPlanBox(subscriptionInfo: state.subscriptionInfo),
|
||||||
|
const VSpace(16),
|
||||||
|
FlowyText(
|
||||||
|
LocaleKeys.settings_planPage_planUsage_addons_title.tr(),
|
||||||
|
fontSize: 18,
|
||||||
|
color: AFThemeExtension.of(context).strongText,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
const VSpace(8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: _AddOnBox(
|
||||||
|
title: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_aiMax_title
|
||||||
|
.tr(),
|
||||||
|
description: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_aiMax_description
|
||||||
|
.tr(),
|
||||||
|
price: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_aiMax_price
|
||||||
|
.tr(
|
||||||
|
args: [SubscriptionPlanPB.AiMax.priceAnnualBilling],
|
||||||
|
),
|
||||||
|
priceInfo: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_aiMax_priceInfo
|
||||||
|
.tr(),
|
||||||
|
billingInfo: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_aiMax_billingInfo
|
||||||
|
.tr(
|
||||||
|
args: [SubscriptionPlanPB.AiMax.priceMonthBilling],
|
||||||
|
),
|
||||||
|
buttonText: state.subscriptionInfo.hasAIMax
|
||||||
|
? LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_activeLabel
|
||||||
|
.tr()
|
||||||
|
: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_addLabel
|
||||||
|
.tr(),
|
||||||
|
isActive: state.subscriptionInfo.hasAIMax,
|
||||||
|
plan: SubscriptionPlanPB.AiMax,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const HSpace(8),
|
||||||
|
Flexible(
|
||||||
|
child: _AddOnBox(
|
||||||
|
title: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_aiOnDevice_title
|
||||||
|
.tr(),
|
||||||
|
description: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_aiOnDevice_description
|
||||||
|
.tr(),
|
||||||
|
price: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_aiOnDevice_price
|
||||||
|
.tr(
|
||||||
|
args: [SubscriptionPlanPB.AiLocal.priceAnnualBilling],
|
||||||
|
),
|
||||||
|
priceInfo: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_aiOnDevice_priceInfo
|
||||||
|
.tr(),
|
||||||
|
billingInfo: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_aiOnDevice_billingInfo
|
||||||
|
.tr(
|
||||||
|
args: [SubscriptionPlanPB.AiLocal.priceMonthBilling],
|
||||||
|
),
|
||||||
|
buttonText: state.subscriptionInfo.hasAIOnDevice
|
||||||
|
? LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_activeLabel
|
||||||
|
.tr()
|
||||||
|
: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_addons_addLabel
|
||||||
|
.tr(),
|
||||||
|
isActive: state.subscriptionInfo.hasAIOnDevice,
|
||||||
|
plan: SubscriptionPlanPB.AiLocal,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -81,9 +180,9 @@ class SettingsPlanView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _CurrentPlanBox extends StatefulWidget {
|
class _CurrentPlanBox extends StatefulWidget {
|
||||||
const _CurrentPlanBox({required this.subscription});
|
const _CurrentPlanBox({required this.subscriptionInfo});
|
||||||
|
|
||||||
final WorkspaceSubscriptionPB subscription;
|
final WorkspaceSubscriptionInfoPB subscriptionInfo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_CurrentPlanBox> createState() => _CurrentPlanBoxState();
|
State<_CurrentPlanBox> createState() => _CurrentPlanBoxState();
|
||||||
@ -115,68 +214,67 @@ class _CurrentPlanBoxState extends State<_CurrentPlanBox> {
|
|||||||
border: Border.all(color: const Color(0xFFBDBDBD)),
|
border: Border.all(color: const Color(0xFFBDBDBD)),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Row(
|
||||||
child: Column(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Expanded(
|
||||||
children: [
|
flex: 6,
|
||||||
const VSpace(4),
|
child: Column(
|
||||||
FlowyText.semibold(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
widget.subscription.label,
|
children: [
|
||||||
fontSize: 24,
|
const VSpace(4),
|
||||||
color: AFThemeExtension.of(context).strongText,
|
FlowyText.semibold(
|
||||||
),
|
widget.subscriptionInfo.label,
|
||||||
const VSpace(8),
|
fontSize: 24,
|
||||||
FlowyText.regular(
|
color: AFThemeExtension.of(context).strongText,
|
||||||
widget.subscription.info,
|
|
||||||
fontSize: 16,
|
|
||||||
color: AFThemeExtension.of(context).strongText,
|
|
||||||
maxLines: 3,
|
|
||||||
),
|
|
||||||
const VSpace(16),
|
|
||||||
FlowyGradientButton(
|
|
||||||
label: LocaleKeys
|
|
||||||
.settings_planPage_planUsage_currentPlan_upgrade
|
|
||||||
.tr(),
|
|
||||||
onPressed: () => _openPricingDialog(
|
|
||||||
context,
|
|
||||||
context.read<SettingsPlanBloc>().workspaceId,
|
|
||||||
widget.subscription,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (widget.subscription.hasCanceled) ...[
|
|
||||||
const VSpace(12),
|
|
||||||
FlowyText(
|
|
||||||
LocaleKeys
|
|
||||||
.settings_planPage_planUsage_currentPlan_canceledInfo
|
|
||||||
.tr(
|
|
||||||
args: [_canceledDate(context)],
|
|
||||||
),
|
),
|
||||||
maxLines: 5,
|
const VSpace(8),
|
||||||
fontSize: 12,
|
FlowyText.regular(
|
||||||
color: Theme.of(context).colorScheme.error,
|
widget.subscriptionInfo.info,
|
||||||
),
|
fontSize: 16,
|
||||||
],
|
color: AFThemeExtension.of(context).strongText,
|
||||||
],
|
maxLines: 3,
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const HSpace(16),
|
|
||||||
Expanded(
|
|
||||||
child: SeparatedColumn(
|
|
||||||
separatorBuilder: () => const VSpace(4),
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
..._getPros(widget.subscription.subscriptionPlan).map(
|
|
||||||
(s) => _ProConItem(label: s),
|
|
||||||
),
|
),
|
||||||
..._getCons(widget.subscription.subscriptionPlan).map(
|
),
|
||||||
(s) => _ProConItem(label: s, isPro: false),
|
Flexible(
|
||||||
|
flex: 5,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 220),
|
||||||
|
child: FlowyGradientButton(
|
||||||
|
label: LocaleKeys
|
||||||
|
.settings_planPage_planUsage_currentPlan_upgrade
|
||||||
|
.tr(),
|
||||||
|
onPressed: () => _openPricingDialog(
|
||||||
|
context,
|
||||||
|
context.read<SettingsPlanBloc>().workspaceId,
|
||||||
|
widget.subscriptionInfo,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
|
if (widget.subscriptionInfo.isCanceled) ...[
|
||||||
|
const VSpace(12),
|
||||||
|
FlowyText(
|
||||||
|
LocaleKeys
|
||||||
|
.settings_planPage_planUsage_currentPlan_canceledInfo
|
||||||
|
.tr(
|
||||||
|
args: [_canceledDate(context)],
|
||||||
|
),
|
||||||
|
maxLines: 5,
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -184,14 +282,21 @@ class _CurrentPlanBoxState extends State<_CurrentPlanBox> {
|
|||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 32,
|
height: 30,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
decoration: const BoxDecoration(color: Color(0xFF4F3F5F)),
|
decoration: const BoxDecoration(
|
||||||
|
color: Color(0xFF4F3F5F),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(4),
|
||||||
|
topRight: Radius.circular(4),
|
||||||
|
bottomRight: Radius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: FlowyText.semibold(
|
child: FlowyText.semibold(
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_bannerLabel
|
LocaleKeys.settings_planPage_planUsage_currentPlan_bannerLabel
|
||||||
.tr(),
|
.tr(),
|
||||||
fontSize: 16,
|
fontSize: 14,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -204,16 +309,15 @@ class _CurrentPlanBoxState extends State<_CurrentPlanBox> {
|
|||||||
String _canceledDate(BuildContext context) {
|
String _canceledDate(BuildContext context) {
|
||||||
final appearance = context.read<AppearanceSettingsCubit>().state;
|
final appearance = context.read<AppearanceSettingsCubit>().state;
|
||||||
return appearance.dateFormat.formatDate(
|
return appearance.dateFormat.formatDate(
|
||||||
widget.subscription.canceledAt.toDateTime(),
|
widget.subscriptionInfo.planSubscription.endDate.toDateTime(),
|
||||||
true,
|
false,
|
||||||
appearance.timeFormat,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openPricingDialog(
|
void _openPricingDialog(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String workspaceId,
|
String workspaceId,
|
||||||
WorkspaceSubscriptionPB subscription,
|
WorkspaceSubscriptionInfoPB subscriptionInfo,
|
||||||
) =>
|
) =>
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@ -221,101 +325,20 @@ class _CurrentPlanBoxState extends State<_CurrentPlanBox> {
|
|||||||
value: planBloc,
|
value: planBloc,
|
||||||
child: SettingsPlanComparisonDialog(
|
child: SettingsPlanComparisonDialog(
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
subscription: subscription,
|
subscriptionInfo: subscriptionInfo,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
List<String> _getPros(SubscriptionPlanPB plan) => switch (plan) {
|
|
||||||
SubscriptionPlanPB.Pro => _proPros(),
|
|
||||||
_ => _freePros(),
|
|
||||||
};
|
|
||||||
|
|
||||||
List<String> _getCons(SubscriptionPlanPB plan) => switch (plan) {
|
|
||||||
SubscriptionPlanPB.Pro => _proCons(),
|
|
||||||
_ => _freeCons(),
|
|
||||||
};
|
|
||||||
|
|
||||||
List<String> _freePros() => [
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_freeProOne.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_freeProTwo.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_freeProThree.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_freeProFour.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_freeProFive.tr(),
|
|
||||||
];
|
|
||||||
|
|
||||||
List<String> _freeCons() => [
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_freeConOne.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_freeConTwo.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_freeConThree.tr(),
|
|
||||||
];
|
|
||||||
|
|
||||||
List<String> _proPros() => [
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_professionalProOne
|
|
||||||
.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_professionalProTwo
|
|
||||||
.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_professionalProThree
|
|
||||||
.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_professionalProFour
|
|
||||||
.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_professionalProFive
|
|
||||||
.tr(),
|
|
||||||
];
|
|
||||||
|
|
||||||
List<String> _proCons() => [
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_professionalConOne
|
|
||||||
.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_professionalConTwo
|
|
||||||
.tr(),
|
|
||||||
LocaleKeys.settings_planPage_planUsage_currentPlan_professionalConThree
|
|
||||||
.tr(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ProConItem extends StatelessWidget {
|
|
||||||
const _ProConItem({
|
|
||||||
required this.label,
|
|
||||||
this.isPro = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String label;
|
|
||||||
final bool isPro;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 18,
|
|
||||||
child: FlowySvg(
|
|
||||||
isPro ? FlowySvgs.check_m : FlowySvgs.close_error_s,
|
|
||||||
size: const Size.square(18),
|
|
||||||
color: isPro
|
|
||||||
? AFThemeExtension.of(context).strongText
|
|
||||||
: const Color(0xFF900000),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const HSpace(4),
|
|
||||||
Flexible(
|
|
||||||
child: FlowyText.regular(
|
|
||||||
label,
|
|
||||||
fontSize: 12,
|
|
||||||
color: AFThemeExtension.of(context).strongText,
|
|
||||||
maxLines: 3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PlanUsageSummary extends StatelessWidget {
|
class _PlanUsageSummary extends StatelessWidget {
|
||||||
const _PlanUsageSummary({required this.usage, required this.subscription});
|
const _PlanUsageSummary({
|
||||||
|
required this.usage,
|
||||||
|
required this.subscriptionInfo,
|
||||||
|
});
|
||||||
|
|
||||||
final WorkspaceUsagePB usage;
|
final WorkspaceUsagePB usage;
|
||||||
final WorkspaceSubscriptionPB subscription;
|
final WorkspaceSubscriptionInfoPB subscriptionInfo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -331,61 +354,101 @@ class _PlanUsageSummary extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const VSpace(16),
|
const VSpace(16),
|
||||||
Row(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _UsageBox(
|
child: _UsageBox(
|
||||||
title: LocaleKeys.settings_planPage_planUsage_storageLabel.tr(),
|
title: LocaleKeys.settings_planPage_planUsage_storageLabel.tr(),
|
||||||
replacementText: subscription.subscriptionPlan ==
|
unlimitedLabel: LocaleKeys
|
||||||
SubscriptionPlanPB.Pro
|
.settings_planPage_planUsage_unlimitedStorageLabel
|
||||||
? LocaleKeys.settings_planPage_planUsage_storageUnlimited
|
.tr(),
|
||||||
.tr()
|
unlimited: usage.storageBytesUnlimited,
|
||||||
: null,
|
|
||||||
label: LocaleKeys.settings_planPage_planUsage_storageUsage.tr(
|
label: LocaleKeys.settings_planPage_planUsage_storageUsage.tr(
|
||||||
args: [
|
args: [
|
||||||
usage.currentBlobInGb,
|
usage.currentBlobInGb,
|
||||||
usage.totalBlobInGb,
|
usage.totalBlobInGb,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
value: usage.totalBlobBytes.toInt() /
|
value: usage.storageBytes.toInt() /
|
||||||
usage.totalBlobBytesLimit.toInt(),
|
usage.storageBytesLimit.toInt(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _UsageBox(
|
child: _UsageBox(
|
||||||
title: LocaleKeys.settings_planPage_planUsage_collaboratorsLabel
|
title:
|
||||||
.tr(),
|
LocaleKeys.settings_planPage_planUsage_aiResponseLabel.tr(),
|
||||||
label: LocaleKeys.settings_planPage_planUsage_collaboratorsUsage
|
label:
|
||||||
.tr(
|
LocaleKeys.settings_planPage_planUsage_aiResponseUsage.tr(
|
||||||
args: [
|
args: [
|
||||||
usage.memberCount.toString(),
|
usage.aiResponsesCount.toString(),
|
||||||
usage.memberCountLimit.toString(),
|
usage.aiResponsesCountLimit.toString(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
value:
|
unlimitedLabel: LocaleKeys
|
||||||
usage.memberCount.toInt() / usage.memberCountLimit.toInt(),
|
.settings_planPage_planUsage_unlimitedAILabel
|
||||||
|
.tr(),
|
||||||
|
unlimited: usage.aiResponsesUnlimited,
|
||||||
|
value: usage.aiResponsesCount.toInt() /
|
||||||
|
usage.aiResponsesCountLimit.toInt(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const VSpace(16),
|
const VSpace(16),
|
||||||
Column(
|
SeparatedColumn(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
separatorBuilder: () => const VSpace(4),
|
||||||
children: [
|
children: [
|
||||||
_ToggleMore(
|
if (subscriptionInfo.plan == WorkspacePlanPB.FreePlan) ...[
|
||||||
value: subscription.subscriptionPlan == SubscriptionPlanPB.Pro,
|
_ToggleMore(
|
||||||
label:
|
value: false,
|
||||||
LocaleKeys.settings_planPage_planUsage_memberProToggle.tr(),
|
label:
|
||||||
subscription: subscription,
|
LocaleKeys.settings_planPage_planUsage_memberProToggle.tr(),
|
||||||
badgeLabel: LocaleKeys.settings_planPage_planUsage_proBadge.tr(),
|
badgeLabel:
|
||||||
),
|
LocaleKeys.settings_planPage_planUsage_proBadge.tr(),
|
||||||
const VSpace(8),
|
onTap: () async {
|
||||||
_ToggleMore(
|
context.read<SettingsPlanBloc>().add(
|
||||||
value: subscription.subscriptionPlan == SubscriptionPlanPB.Pro,
|
const SettingsPlanEvent.addSubscription(
|
||||||
label:
|
SubscriptionPlanPB.Pro,
|
||||||
LocaleKeys.settings_planPage_planUsage_guestCollabToggle.tr(),
|
),
|
||||||
subscription: subscription,
|
);
|
||||||
badgeLabel: LocaleKeys.settings_planPage_planUsage_proBadge.tr(),
|
await Future.delayed(const Duration(seconds: 2), () {});
|
||||||
),
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (!subscriptionInfo.hasAIMax && !usage.aiResponsesUnlimited) ...[
|
||||||
|
_ToggleMore(
|
||||||
|
value: false,
|
||||||
|
label: LocaleKeys.settings_planPage_planUsage_aiMaxToggle.tr(),
|
||||||
|
badgeLabel:
|
||||||
|
LocaleKeys.settings_planPage_planUsage_aiMaxBadge.tr(),
|
||||||
|
onTap: () async {
|
||||||
|
context.read<SettingsPlanBloc>().add(
|
||||||
|
const SettingsPlanEvent.addSubscription(
|
||||||
|
SubscriptionPlanPB.AiMax,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await Future.delayed(const Duration(seconds: 2), () {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (!subscriptionInfo.hasAIOnDevice) ...[
|
||||||
|
_ToggleMore(
|
||||||
|
value: false,
|
||||||
|
label: LocaleKeys.settings_planPage_planUsage_aiOnDeviceToggle
|
||||||
|
.tr(),
|
||||||
|
badgeLabel:
|
||||||
|
LocaleKeys.settings_planPage_planUsage_aiOnDeviceBadge.tr(),
|
||||||
|
onTap: () async {
|
||||||
|
context.read<SettingsPlanBloc>().add(
|
||||||
|
const SettingsPlanEvent.addSubscription(
|
||||||
|
SubscriptionPlanPB.AiLocal,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await Future.delayed(const Duration(seconds: 2), () {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -398,15 +461,18 @@ class _UsageBox extends StatelessWidget {
|
|||||||
required this.title,
|
required this.title,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.value,
|
required this.value,
|
||||||
this.replacementText,
|
required this.unlimitedLabel,
|
||||||
|
this.unlimited = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String label;
|
final String label;
|
||||||
final double value;
|
final double value;
|
||||||
|
|
||||||
/// Replaces the progress indicator if not null
|
final String unlimitedLabel;
|
||||||
final String? replacementText;
|
|
||||||
|
// Replaces the progress bar with an unlimited badge
|
||||||
|
final bool unlimited;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -418,19 +484,27 @@ class _UsageBox extends StatelessWidget {
|
|||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color: AFThemeExtension.of(context).secondaryTextColor,
|
color: AFThemeExtension.of(context).secondaryTextColor,
|
||||||
),
|
),
|
||||||
if (replacementText != null) ...[
|
if (unlimited) ...[
|
||||||
Row(
|
Padding(
|
||||||
children: [
|
padding: const EdgeInsets.only(top: 4),
|
||||||
Flexible(
|
child: Row(
|
||||||
child: FlowyText.medium(
|
mainAxisSize: MainAxisSize.min,
|
||||||
replacementText!,
|
children: [
|
||||||
fontSize: 11,
|
const FlowySvg(
|
||||||
color: AFThemeExtension.of(context).secondaryTextColor,
|
FlowySvgs.check_circle_outlined_s,
|
||||||
|
color: Color(0xFF9C00FB),
|
||||||
),
|
),
|
||||||
),
|
const HSpace(4),
|
||||||
],
|
FlowyText(
|
||||||
|
unlimitedLabel,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
] else ...[
|
] else ...[
|
||||||
|
const VSpace(4),
|
||||||
_PlanProgressIndicator(label: label, progress: value),
|
_PlanProgressIndicator(label: label, progress: value),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -442,14 +516,14 @@ class _ToggleMore extends StatefulWidget {
|
|||||||
const _ToggleMore({
|
const _ToggleMore({
|
||||||
required this.value,
|
required this.value,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.subscription,
|
|
||||||
this.badgeLabel,
|
this.badgeLabel,
|
||||||
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool value;
|
final bool value;
|
||||||
final String label;
|
final String label;
|
||||||
final WorkspaceSubscriptionPB subscription;
|
|
||||||
final String? badgeLabel;
|
final String? badgeLabel;
|
||||||
|
final Future<void> Function()? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_ToggleMore> createState() => _ToggleMoreState();
|
State<_ToggleMore> createState() => _ToggleMoreState();
|
||||||
@ -471,29 +545,17 @@ class _ToggleMoreState extends State<_ToggleMore> {
|
|||||||
Toggle(
|
Toggle(
|
||||||
value: toggleValue,
|
value: toggleValue,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
onChanged: (_) {
|
onChanged: (_) async {
|
||||||
setState(() => toggleValue = !toggleValue);
|
if (widget.onTap == null || toggleValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Future.delayed(const Duration(milliseconds: 150), () {
|
setState(() => toggleValue = !toggleValue);
|
||||||
if (mounted) {
|
await widget.onTap!();
|
||||||
showDialog(
|
|
||||||
context: context,
|
if (mounted) {
|
||||||
builder: (_) => BlocProvider<SettingsPlanBloc>.value(
|
setState(() => toggleValue = !toggleValue);
|
||||||
value: context.read<SettingsPlanBloc>(),
|
}
|
||||||
child: SettingsPlanComparisonDialog(
|
|
||||||
workspaceId: context.read<SettingsPlanBloc>().workspaceId,
|
|
||||||
subscription: widget.subscription,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).then((_) {
|
|
||||||
Future.delayed(const Duration(milliseconds: 150), () {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() => toggleValue = !toggleValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const HSpace(10),
|
const HSpace(10),
|
||||||
@ -553,7 +615,9 @@ class _PlanProgressIndicator extends StatelessWidget {
|
|||||||
widthFactor: progress,
|
widthFactor: progress,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.primary,
|
color: progress >= 1
|
||||||
|
? theme.colorScheme.error
|
||||||
|
: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -574,6 +638,135 @@ class _PlanProgressIndicator extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _AddOnBox extends StatelessWidget {
|
||||||
|
const _AddOnBox({
|
||||||
|
required this.title,
|
||||||
|
required this.description,
|
||||||
|
required this.price,
|
||||||
|
required this.priceInfo,
|
||||||
|
required this.billingInfo,
|
||||||
|
required this.buttonText,
|
||||||
|
required this.isActive,
|
||||||
|
required this.plan,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String description;
|
||||||
|
final String price;
|
||||||
|
final String priceInfo;
|
||||||
|
final String billingInfo;
|
||||||
|
final String buttonText;
|
||||||
|
final bool isActive;
|
||||||
|
final SubscriptionPlanPB plan;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isLM = Theme.of(context).isLightMode;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
height: 220,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: isActive ? const Color(0xFFBDBDBD) : const Color(0xFF9C00FB),
|
||||||
|
),
|
||||||
|
color: const Color(0xFFF7F8FC).withOpacity(0.05),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
FlowyText.semibold(
|
||||||
|
title,
|
||||||
|
fontSize: 14,
|
||||||
|
color: AFThemeExtension.of(context).strongText,
|
||||||
|
),
|
||||||
|
const VSpace(10),
|
||||||
|
FlowyText.regular(
|
||||||
|
description,
|
||||||
|
fontSize: 12,
|
||||||
|
color: AFThemeExtension.of(context).secondaryTextColor,
|
||||||
|
maxLines: 4,
|
||||||
|
),
|
||||||
|
const VSpace(10),
|
||||||
|
FlowyText(
|
||||||
|
price,
|
||||||
|
fontSize: 24,
|
||||||
|
color: AFThemeExtension.of(context).strongText,
|
||||||
|
),
|
||||||
|
FlowyText(
|
||||||
|
priceInfo,
|
||||||
|
fontSize: 12,
|
||||||
|
color: AFThemeExtension.of(context).strongText,
|
||||||
|
),
|
||||||
|
const VSpace(12),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: FlowyText(
|
||||||
|
billingInfo,
|
||||||
|
color: AFThemeExtension.of(context).secondaryTextColor,
|
||||||
|
fontSize: 11,
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: FlowyTextButton(
|
||||||
|
buttonText,
|
||||||
|
heading: isActive
|
||||||
|
? const FlowySvg(
|
||||||
|
FlowySvgs.check_circle_outlined_s,
|
||||||
|
color: Color(0xFF9C00FB),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 7),
|
||||||
|
fillColor: isActive
|
||||||
|
? const Color(0xFFE8E2EE)
|
||||||
|
: isLM
|
||||||
|
? Colors.transparent
|
||||||
|
: const Color(0xFF5C3699),
|
||||||
|
constraints: const BoxConstraints(minWidth: 115),
|
||||||
|
radius: Corners.s16Border,
|
||||||
|
hoverColor: isActive
|
||||||
|
? const Color(0xFFE8E2EE)
|
||||||
|
: isLM
|
||||||
|
? const Color(0xFF5C3699)
|
||||||
|
: const Color(0xFF4d3472),
|
||||||
|
fontColor:
|
||||||
|
isLM || isActive ? const Color(0xFF5C3699) : Colors.white,
|
||||||
|
fontHoverColor:
|
||||||
|
isActive ? const Color(0xFF5C3699) : Colors.white,
|
||||||
|
borderColor: isActive
|
||||||
|
? const Color(0xFFE8E2EE)
|
||||||
|
: isLM
|
||||||
|
? const Color(0xFF5C3699)
|
||||||
|
: const Color(0xFF4d3472),
|
||||||
|
fontSize: 12,
|
||||||
|
onPressed: isActive
|
||||||
|
? null
|
||||||
|
: () => context
|
||||||
|
.read<SettingsPlanBloc>()
|
||||||
|
.add(SettingsPlanEvent.addSubscription(plan)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Uncomment if we need it in the future
|
/// Uncomment if we need it in the future
|
||||||
// class _DealBox extends StatelessWidget {
|
// class _DealBox extends StatelessWidget {
|
||||||
// const _DealBox();
|
// const _DealBox();
|
||||||
|
@ -22,7 +22,6 @@ import 'package:appflowy/workspace/presentation/settings/shared/af_dropdown_menu
|
|||||||
import 'package:appflowy/workspace/presentation/settings/shared/document_color_setting_button.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/document_color_setting_button.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/setting_action.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/setting_action.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/setting_list_tile.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/setting_list_tile.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_category_spacer.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_category_spacer.dart';
|
||||||
@ -183,7 +182,8 @@ class SettingsWorkspaceView extends StatelessWidget {
|
|||||||
.tr(),
|
.tr(),
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
onPressed: () => SettingsAlertDialog(
|
onPressed: () => showConfirmDialog(
|
||||||
|
context: context,
|
||||||
title: workspaceMember?.role.isOwner ?? false
|
title: workspaceMember?.role.isOwner ?? false
|
||||||
? LocaleKeys
|
? LocaleKeys
|
||||||
.settings_workspacePage_deleteWorkspacePrompt_title
|
.settings_workspacePage_deleteWorkspacePrompt_title
|
||||||
@ -191,24 +191,21 @@ class SettingsWorkspaceView extends StatelessWidget {
|
|||||||
: LocaleKeys
|
: LocaleKeys
|
||||||
.settings_workspacePage_leaveWorkspacePrompt_title
|
.settings_workspacePage_leaveWorkspacePrompt_title
|
||||||
.tr(),
|
.tr(),
|
||||||
subtitle: workspaceMember?.role.isOwner ?? false
|
description: workspaceMember?.role.isOwner ?? false
|
||||||
? LocaleKeys
|
? LocaleKeys
|
||||||
.settings_workspacePage_deleteWorkspacePrompt_content
|
.settings_workspacePage_deleteWorkspacePrompt_content
|
||||||
.tr()
|
.tr()
|
||||||
: LocaleKeys
|
: LocaleKeys
|
||||||
.settings_workspacePage_leaveWorkspacePrompt_content
|
.settings_workspacePage_leaveWorkspacePrompt_content
|
||||||
.tr(),
|
.tr(),
|
||||||
isDangerous: true,
|
style: ConfirmPopupStyle.cancelAndOk,
|
||||||
confirm: () {
|
onConfirm: () => context.read<WorkspaceSettingsBloc>().add(
|
||||||
context.read<WorkspaceSettingsBloc>().add(
|
workspaceMember?.role.isOwner ?? false
|
||||||
workspaceMember?.role.isOwner ?? false
|
? const WorkspaceSettingsEvent.deleteWorkspace()
|
||||||
? const WorkspaceSettingsEvent.deleteWorkspace()
|
: const WorkspaceSettingsEvent.leaveWorkspace(),
|
||||||
: const WorkspaceSettingsEvent.leaveWorkspace(),
|
),
|
||||||
);
|
),
|
||||||
Navigator.of(context).pop();
|
buttonType: SingleSettingsButtonType.danger,
|
||||||
},
|
|
||||||
).show(context),
|
|
||||||
isDangerous: true,
|
|
||||||
buttonLabel: workspaceMember?.role.isOwner ?? false
|
buttonLabel: workspaceMember?.role.isOwner ?? false
|
||||||
? LocaleKeys
|
? LocaleKeys
|
||||||
.settings_workspacePage_manageWorkspace_deleteWorkspace
|
.settings_workspacePage_manageWorkspace_deleteWorkspace
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/startup/startup.dart';
|
|
||||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
import 'package:appflowy/workspace/application/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';
|
||||||
@ -26,18 +25,22 @@ class SettingsDialog extends StatelessWidget {
|
|||||||
required this.dismissDialog,
|
required this.dismissDialog,
|
||||||
required this.didLogout,
|
required this.didLogout,
|
||||||
required this.restartApp,
|
required this.restartApp,
|
||||||
|
this.initPage,
|
||||||
}) : super(key: ValueKey(user.id));
|
}) : super(key: ValueKey(user.id));
|
||||||
|
|
||||||
final VoidCallback dismissDialog;
|
final VoidCallback dismissDialog;
|
||||||
final VoidCallback didLogout;
|
final VoidCallback didLogout;
|
||||||
final VoidCallback restartApp;
|
final VoidCallback restartApp;
|
||||||
final UserProfilePB user;
|
final UserProfilePB user;
|
||||||
|
final SettingsPage? initPage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<SettingsDialogBloc>(
|
return BlocProvider<SettingsDialogBloc>(
|
||||||
create: (context) => getIt<SettingsDialogBloc>(param1: user)
|
create: (context) => SettingsDialogBloc(
|
||||||
..add(const SettingsDialogEvent.initial()),
|
user,
|
||||||
|
initPage: initPage,
|
||||||
|
)..add(const SettingsDialogEvent.initial()),
|
||||||
child: BlocBuilder<SettingsDialogBloc, SettingsDialogState>(
|
child: BlocBuilder<SettingsDialogBloc, SettingsDialogState>(
|
||||||
builder: (context, state) => FlowyDialog(
|
builder: (context, state) => FlowyDialog(
|
||||||
width: MediaQuery.of(context).size.width * 0.7,
|
width: MediaQuery.of(context).size.width * 0.7,
|
||||||
@ -120,7 +123,10 @@ class SettingsDialog extends StatelessWidget {
|
|||||||
return const AIFeatureOnlySupportedWhenUsingAppFlowyCloud();
|
return const AIFeatureOnlySupportedWhenUsingAppFlowyCloud();
|
||||||
}
|
}
|
||||||
case SettingsPage.member:
|
case SettingsPage.member:
|
||||||
return WorkspaceMembersPage(userProfile: user);
|
return WorkspaceMembersPage(
|
||||||
|
userProfile: user,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
);
|
||||||
case SettingsPage.plan:
|
case SettingsPage.plan:
|
||||||
return SettingsPlanView(workspaceId: workspaceId, user: user);
|
return SettingsPlanView(workspaceId: workspaceId, user: user);
|
||||||
case SettingsPage.billing:
|
case SettingsPage.billing:
|
||||||
|
@ -71,7 +71,7 @@ class _FlowyGradientButtonState extends State<FlowyGradientButton> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||||
child: FlowyText(
|
child: FlowyText(
|
||||||
widget.label,
|
widget.label,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
@ -21,6 +21,7 @@ class SettingsAlertDialog extends StatefulWidget {
|
|||||||
this.hideCancelButton = false,
|
this.hideCancelButton = false,
|
||||||
this.isDangerous = false,
|
this.isDangerous = false,
|
||||||
this.implyLeading = false,
|
this.implyLeading = false,
|
||||||
|
this.enableConfirmNotifier,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Widget? icon;
|
final Widget? icon;
|
||||||
@ -32,6 +33,7 @@ class SettingsAlertDialog extends StatefulWidget {
|
|||||||
final String? confirmLabel;
|
final String? confirmLabel;
|
||||||
final bool hideCancelButton;
|
final bool hideCancelButton;
|
||||||
final bool isDangerous;
|
final bool isDangerous;
|
||||||
|
final ValueNotifier<bool>? enableConfirmNotifier;
|
||||||
|
|
||||||
/// If true, a back button will show in the top left corner
|
/// If true, a back button will show in the top left corner
|
||||||
final bool implyLeading;
|
final bool implyLeading;
|
||||||
@ -41,6 +43,37 @@ class SettingsAlertDialog extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SettingsAlertDialogState extends State<SettingsAlertDialog> {
|
class _SettingsAlertDialogState extends State<SettingsAlertDialog> {
|
||||||
|
bool enableConfirm = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.enableConfirmNotifier != null) {
|
||||||
|
widget.enableConfirmNotifier!.addListener(_updateEnableConfirm);
|
||||||
|
enableConfirm = widget.enableConfirmNotifier!.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateEnableConfirm() {
|
||||||
|
setState(() => enableConfirm = widget.enableConfirmNotifier!.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (widget.enableConfirmNotifier != null) {
|
||||||
|
widget.enableConfirmNotifier!.removeListener(_updateEnableConfirm);
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant SettingsAlertDialog oldWidget) {
|
||||||
|
oldWidget.enableConfirmNotifier?.removeListener(_updateEnableConfirm);
|
||||||
|
widget.enableConfirmNotifier?.addListener(_updateEnableConfirm);
|
||||||
|
enableConfirm = widget.enableConfirmNotifier?.value ?? true;
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return StyledDialog(
|
return StyledDialog(
|
||||||
@ -136,6 +169,7 @@ class _SettingsAlertDialogState extends State<SettingsAlertDialog> {
|
|||||||
cancel: widget.cancel,
|
cancel: widget.cancel,
|
||||||
confirm: widget.confirm,
|
confirm: widget.confirm,
|
||||||
isDangerous: widget.isDangerous,
|
isDangerous: widget.isDangerous,
|
||||||
|
enableConfirm: enableConfirm,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -150,6 +184,7 @@ class _Actions extends StatelessWidget {
|
|||||||
this.cancel,
|
this.cancel,
|
||||||
this.confirm,
|
this.confirm,
|
||||||
this.isDangerous = false,
|
this.isDangerous = false,
|
||||||
|
this.enableConfirm = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
final bool hideCancelButton;
|
final bool hideCancelButton;
|
||||||
@ -157,6 +192,7 @@ class _Actions extends StatelessWidget {
|
|||||||
final VoidCallback? cancel;
|
final VoidCallback? cancel;
|
||||||
final VoidCallback? confirm;
|
final VoidCallback? confirm;
|
||||||
final bool isDangerous;
|
final bool isDangerous;
|
||||||
|
final bool enableConfirm;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -197,14 +233,18 @@ class _Actions extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
radius: Corners.s12Border,
|
radius: Corners.s12Border,
|
||||||
fontColor: isDangerous ? Colors.white : null,
|
fontColor: isDangerous ? Colors.white : null,
|
||||||
fontHoverColor: Colors.white,
|
fontHoverColor: !enableConfirm ? null : Colors.white,
|
||||||
fillColor: isDangerous
|
fillColor: !enableConfirm
|
||||||
? Theme.of(context).colorScheme.error
|
? Theme.of(context).dividerColor
|
||||||
: Theme.of(context).colorScheme.primary,
|
: isDangerous
|
||||||
hoverColor: isDangerous
|
? Theme.of(context).colorScheme.error
|
||||||
? Theme.of(context).colorScheme.error
|
: Theme.of(context).colorScheme.primary,
|
||||||
: const Color(0xFF005483),
|
hoverColor: !enableConfirm
|
||||||
onPressed: confirm,
|
? Theme.of(context).dividerColor
|
||||||
|
: isDangerous
|
||||||
|
? Theme.of(context).colorScheme.error
|
||||||
|
: const Color(0xFF005483),
|
||||||
|
onPressed: enableConfirm ? confirm : null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -6,6 +6,16 @@ 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/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
|
||||||
|
enum SingleSettingsButtonType {
|
||||||
|
primary,
|
||||||
|
danger,
|
||||||
|
highlight;
|
||||||
|
|
||||||
|
bool get isPrimary => this == primary;
|
||||||
|
bool get isDangerous => this == danger;
|
||||||
|
bool get isHighlight => this == highlight;
|
||||||
|
}
|
||||||
|
|
||||||
/// This is used to describe a single setting action
|
/// This is used to describe a single setting action
|
||||||
///
|
///
|
||||||
/// This will render a simple action that takes the title,
|
/// This will render a simple action that takes the title,
|
||||||
@ -18,15 +28,18 @@ class SingleSettingAction extends StatelessWidget {
|
|||||||
const SingleSettingAction({
|
const SingleSettingAction({
|
||||||
super.key,
|
super.key,
|
||||||
required this.label,
|
required this.label,
|
||||||
|
this.description,
|
||||||
this.labelMaxLines,
|
this.labelMaxLines,
|
||||||
required this.buttonLabel,
|
required this.buttonLabel,
|
||||||
this.onPressed,
|
this.onPressed,
|
||||||
this.isDangerous = false,
|
this.buttonType = SingleSettingsButtonType.primary,
|
||||||
this.fontSize = 14,
|
this.fontSize = 14,
|
||||||
this.fontWeight = FontWeight.normal,
|
this.fontWeight = FontWeight.normal,
|
||||||
|
this.minWidth,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
final String? description;
|
||||||
final int? labelMaxLines;
|
final int? labelMaxLines;
|
||||||
final String buttonLabel;
|
final String buttonLabel;
|
||||||
|
|
||||||
@ -36,46 +49,115 @@ class SingleSettingAction extends StatelessWidget {
|
|||||||
///
|
///
|
||||||
final VoidCallback? onPressed;
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
/// If isDangerous is true, the button will be rendered as a dangerous
|
final SingleSettingsButtonType buttonType;
|
||||||
/// action, with a red outline.
|
|
||||||
///
|
|
||||||
final bool isDangerous;
|
|
||||||
|
|
||||||
final double fontSize;
|
final double fontSize;
|
||||||
final FontWeight fontWeight;
|
final FontWeight fontWeight;
|
||||||
|
final double? minWidth;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: FlowyText(
|
child: Column(
|
||||||
label,
|
children: [
|
||||||
fontSize: fontSize,
|
Row(
|
||||||
fontWeight: fontWeight,
|
children: [
|
||||||
maxLines: labelMaxLines,
|
Expanded(
|
||||||
overflow: TextOverflow.ellipsis,
|
child: FlowyText(
|
||||||
color: AFThemeExtension.of(context).secondaryTextColor,
|
label,
|
||||||
|
fontSize: fontSize,
|
||||||
|
fontWeight: fontWeight,
|
||||||
|
maxLines: labelMaxLines,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
color: AFThemeExtension.of(context).secondaryTextColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (description != null) ...[
|
||||||
|
const VSpace(4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: FlowyText.regular(
|
||||||
|
description!,
|
||||||
|
fontSize: 11,
|
||||||
|
color: AFThemeExtension.of(context).secondaryTextColor,
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const HSpace(24),
|
const HSpace(24),
|
||||||
SizedBox(
|
ConstrainedBox(
|
||||||
height: 32,
|
constraints: BoxConstraints(
|
||||||
|
minWidth: minWidth ?? 0.0,
|
||||||
|
maxHeight: 32,
|
||||||
|
minHeight: 32,
|
||||||
|
),
|
||||||
child: FlowyTextButton(
|
child: FlowyTextButton(
|
||||||
buttonLabel,
|
buttonLabel,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 7),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 7),
|
||||||
fillColor:
|
fillColor: fillColor(context),
|
||||||
isDangerous ? null : Theme.of(context).colorScheme.primary,
|
radius: Corners.s8Border,
|
||||||
radius: Corners.s12Border,
|
hoverColor: hoverColor(context),
|
||||||
hoverColor: isDangerous ? null : const Color(0xFF005483),
|
fontColor: fontColor(context),
|
||||||
fontColor: isDangerous ? Theme.of(context).colorScheme.error : null,
|
fontHoverColor: fontHoverColor(context),
|
||||||
fontHoverColor: Colors.white,
|
borderColor: borderColor(context),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
isDangerous: isDangerous,
|
isDangerous: buttonType.isDangerous,
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Color? fillColor(BuildContext context) {
|
||||||
|
if (buttonType.isPrimary) {
|
||||||
|
return Theme.of(context).colorScheme.primary;
|
||||||
|
}
|
||||||
|
return Colors.transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color? hoverColor(BuildContext context) {
|
||||||
|
if (buttonType.isPrimary) {
|
||||||
|
return const Color(0xFF005483);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonType.isHighlight) {
|
||||||
|
return const Color(0xFF5C3699);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color? fontColor(BuildContext context) {
|
||||||
|
if (buttonType.isDangerous) {
|
||||||
|
return Theme.of(context).colorScheme.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonType.isHighlight) {
|
||||||
|
return const Color(0xFF5C3699);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color? fontHoverColor(BuildContext context) {
|
||||||
|
return Colors.white;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color? borderColor(BuildContext context) {
|
||||||
|
if (buttonType.isHighlight) {
|
||||||
|
return const Color(0xFF5C3699);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -24,13 +27,14 @@ class WorkspaceMemberBloc
|
|||||||
extends Bloc<WorkspaceMemberEvent, WorkspaceMemberState> {
|
extends Bloc<WorkspaceMemberEvent, WorkspaceMemberState> {
|
||||||
WorkspaceMemberBloc({
|
WorkspaceMemberBloc({
|
||||||
required this.userProfile,
|
required this.userProfile,
|
||||||
|
String? workspaceId,
|
||||||
this.workspace,
|
this.workspace,
|
||||||
}) : _userBackendService = UserBackendService(userId: userProfile.id),
|
}) : _userBackendService = UserBackendService(userId: userProfile.id),
|
||||||
super(WorkspaceMemberState.initial()) {
|
super(WorkspaceMemberState.initial()) {
|
||||||
on<WorkspaceMemberEvent>((event, emit) async {
|
on<WorkspaceMemberEvent>((event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
initial: () async {
|
initial: () async {
|
||||||
await _setCurrentWorkspaceId();
|
await _setCurrentWorkspaceId(workspaceId);
|
||||||
|
|
||||||
final result = await _userBackendService.getWorkspaceMembers(
|
final result = await _userBackendService.getWorkspaceMembers(
|
||||||
_workspaceId,
|
_workspaceId,
|
||||||
@ -135,9 +139,7 @@ class WorkspaceMemberBloc
|
|||||||
(s) => state.members.map((e) {
|
(s) => state.members.map((e) {
|
||||||
if (e.email == email) {
|
if (e.email == email) {
|
||||||
e.freeze();
|
e.freeze();
|
||||||
return e.rebuild((p0) {
|
return e.rebuild((p0) => p0.role = role);
|
||||||
p0.role = role;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
}).toList(),
|
}).toList(),
|
||||||
@ -153,6 +155,26 @@ class WorkspaceMemberBloc
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
updateSubscriptionInfo: (info) async =>
|
||||||
|
emit(state.copyWith(subscriptionInfo: info)),
|
||||||
|
upgradePlan: () async {
|
||||||
|
final plan = state.subscriptionInfo?.plan;
|
||||||
|
if (plan == null) {
|
||||||
|
return Log.error('Failed to upgrade plan: plan is null');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan == WorkspacePlanPB.FreePlan) {
|
||||||
|
final checkoutLink = await _userBackendService.createSubscription(
|
||||||
|
_workspaceId,
|
||||||
|
SubscriptionPlanPB.Pro,
|
||||||
|
);
|
||||||
|
|
||||||
|
checkoutLink.fold(
|
||||||
|
(pl) => afLaunchUrlString(pl.paymentLink),
|
||||||
|
(f) => Log.error('Failed to create subscription: ${f.msg}', f),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -178,9 +200,11 @@ class WorkspaceMemberBloc
|
|||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _setCurrentWorkspaceId() async {
|
Future<void> _setCurrentWorkspaceId(String? workspaceId) async {
|
||||||
if (workspace != null) {
|
if (workspace != null) {
|
||||||
_workspaceId = workspace!.workspaceId;
|
_workspaceId = workspace!.workspaceId;
|
||||||
|
} else if (workspaceId != null && workspaceId.isNotEmpty) {
|
||||||
|
_workspaceId = workspaceId;
|
||||||
} else {
|
} else {
|
||||||
final currentWorkspace = await FolderEventReadCurrentWorkspace().send();
|
final currentWorkspace = await FolderEventReadCurrentWorkspace().send();
|
||||||
currentWorkspace.fold((s) {
|
currentWorkspace.fold((s) {
|
||||||
@ -191,6 +215,20 @@ class WorkspaceMemberBloc
|
|||||||
_workspaceId = '';
|
_workspaceId = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unawaited(_fetchWorkspaceSubscriptionInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
// We fetch workspace subscription info lazily as it's not needed in the first
|
||||||
|
// render of the page.
|
||||||
|
Future<void> _fetchWorkspaceSubscriptionInfo() async {
|
||||||
|
final result =
|
||||||
|
await UserBackendService.getWorkspaceSubscriptionInfo(_workspaceId);
|
||||||
|
|
||||||
|
result.fold(
|
||||||
|
(info) => add(WorkspaceMemberEvent.updateSubscriptionInfo(info)),
|
||||||
|
(f) => Log.error('Failed to fetch subscription info: ${f.msg}', f),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +247,11 @@ class WorkspaceMemberEvent with _$WorkspaceMemberEvent {
|
|||||||
String email,
|
String email,
|
||||||
AFRolePB role,
|
AFRolePB role,
|
||||||
) = UpdateWorkspaceMember;
|
) = UpdateWorkspaceMember;
|
||||||
|
const factory WorkspaceMemberEvent.updateSubscriptionInfo(
|
||||||
|
WorkspaceSubscriptionInfoPB subscriptionInfo,
|
||||||
|
) = UpdateSubscriptionInfo;
|
||||||
|
|
||||||
|
const factory WorkspaceMemberEvent.upgradePlan() = UpgradePlan;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum WorkspaceMemberActionType {
|
enum WorkspaceMemberActionType {
|
||||||
@ -241,6 +284,7 @@ class WorkspaceMemberState with _$WorkspaceMemberState {
|
|||||||
@Default(AFRolePB.Guest) AFRolePB myRole,
|
@Default(AFRolePB.Guest) AFRolePB myRole,
|
||||||
@Default(null) WorkspaceMemberActionResult? actionResult,
|
@Default(null) WorkspaceMemberActionResult? actionResult,
|
||||||
@Default(true) bool isLoading,
|
@Default(true) bool isLoading,
|
||||||
|
@Default(null) WorkspaceSubscriptionInfoPB? subscriptionInfo,
|
||||||
}) = _WorkspaceMemberState;
|
}) = _WorkspaceMemberState;
|
||||||
|
|
||||||
factory WorkspaceMemberState.initial() => const WorkspaceMemberState();
|
factory WorkspaceMemberState.initial() => const WorkspaceMemberState();
|
||||||
@ -255,6 +299,7 @@ class WorkspaceMemberState with _$WorkspaceMemberState {
|
|||||||
return other is WorkspaceMemberState &&
|
return other is WorkspaceMemberState &&
|
||||||
other.members == members &&
|
other.members == members &&
|
||||||
other.myRole == myRole &&
|
other.myRole == myRole &&
|
||||||
|
other.subscriptionInfo == subscriptionInfo &&
|
||||||
identical(other.actionResult, actionResult);
|
identical(other.actionResult, actionResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/shared/af_role_pb_extension.dart';
|
import 'package:appflowy/shared/af_role_pb_extension.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/shared/settings_category_spacer.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.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/log.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.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';
|
||||||
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||||
@ -20,22 +22,34 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:string_validator/string_validator.dart';
|
import 'package:string_validator/string_validator.dart';
|
||||||
|
|
||||||
class WorkspaceMembersPage extends StatelessWidget {
|
class WorkspaceMembersPage extends StatelessWidget {
|
||||||
const WorkspaceMembersPage({super.key, required this.userProfile});
|
const WorkspaceMembersPage({
|
||||||
|
super.key,
|
||||||
|
required this.userProfile,
|
||||||
|
required this.workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
final UserProfilePB userProfile;
|
final UserProfilePB userProfile;
|
||||||
|
final String workspaceId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<WorkspaceMemberBloc>(
|
return BlocProvider<WorkspaceMemberBloc>(
|
||||||
create: (context) => WorkspaceMemberBloc(userProfile: userProfile)
|
create: (context) => WorkspaceMemberBloc(userProfile: userProfile)
|
||||||
..add(const WorkspaceMemberEvent.initial()),
|
..add(const WorkspaceMemberEvent.initial()),
|
||||||
child: BlocConsumer<WorkspaceMemberBloc, WorkspaceMemberState>(
|
child: BlocBuilder<WorkspaceMemberBloc, WorkspaceMemberState>(
|
||||||
listener: _showResultDialog,
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SettingsBody(
|
return SettingsBody(
|
||||||
title: LocaleKeys.settings_appearance_members_title.tr(),
|
title: LocaleKeys.settings_appearance_members_title.tr(),
|
||||||
|
autoSeparate: false,
|
||||||
children: [
|
children: [
|
||||||
if (state.myRole.canInvite) const _InviteMember(),
|
if (state.actionResult != null) ...[
|
||||||
|
_showMemberLimitWarning(context, state),
|
||||||
|
const VSpace(16),
|
||||||
|
],
|
||||||
|
if (state.myRole.canInvite) ...[
|
||||||
|
const _InviteMember(),
|
||||||
|
const SettingsCategorySpacer(),
|
||||||
|
],
|
||||||
if (state.members.isNotEmpty)
|
if (state.members.isNotEmpty)
|
||||||
_MemberList(
|
_MemberList(
|
||||||
members: state.members,
|
members: state.members,
|
||||||
@ -49,62 +63,105 @@ class WorkspaceMembersPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showResultDialog(BuildContext context, WorkspaceMemberState state) {
|
Widget _showMemberLimitWarning(
|
||||||
final actionResult = state.actionResult;
|
BuildContext context,
|
||||||
if (actionResult == null) {
|
WorkspaceMemberState state,
|
||||||
return;
|
) {
|
||||||
|
// We promise that state.actionResult != null before calling
|
||||||
|
// this method
|
||||||
|
final actionResult = state.actionResult!.result;
|
||||||
|
final actionType = state.actionResult!.actionType;
|
||||||
|
|
||||||
|
debugPrint("Plan: ${state.subscriptionInfo?.plan}");
|
||||||
|
|
||||||
|
if (actionType == WorkspaceMemberActionType.invite &&
|
||||||
|
actionResult.isFailure) {
|
||||||
|
final error = actionResult.getFailure().code;
|
||||||
|
if (error == ErrorCode.WorkspaceMemberLimitExceeded) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
const FlowySvg(
|
||||||
|
FlowySvgs.warning_s,
|
||||||
|
blendMode: BlendMode.dst,
|
||||||
|
size: Size.square(20),
|
||||||
|
),
|
||||||
|
const HSpace(12),
|
||||||
|
Expanded(
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
if (state.subscriptionInfo?.plan ==
|
||||||
|
WorkspacePlanPB.ProPlan) ...[
|
||||||
|
TextSpan(
|
||||||
|
text: LocaleKeys
|
||||||
|
.settings_appearance_members_memberLimitExceededPro
|
||||||
|
.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: AFThemeExtension.of(context).strongText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
WidgetSpan(
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
|
// Hardcoded support email, in the future we might
|
||||||
|
// want to add this to an environment variable
|
||||||
|
onTap: () async => afLaunchUrlString(
|
||||||
|
'mailto:support@appflowy.io',
|
||||||
|
),
|
||||||
|
child: FlowyText(
|
||||||
|
LocaleKeys
|
||||||
|
.settings_appearance_members_memberLimitExceededProContact
|
||||||
|
.tr(),
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
] else ...[
|
||||||
|
TextSpan(
|
||||||
|
text: LocaleKeys
|
||||||
|
.settings_appearance_members_memberLimitExceeded
|
||||||
|
.tr(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: AFThemeExtension.of(context).strongText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
WidgetSpan(
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => context
|
||||||
|
.read<WorkspaceMemberBloc>()
|
||||||
|
.add(const WorkspaceMemberEvent.upgradePlan()),
|
||||||
|
child: FlowyText(
|
||||||
|
LocaleKeys
|
||||||
|
.settings_appearance_members_memberLimitExceededUpgrade
|
||||||
|
.tr(),
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final actionType = actionResult.actionType;
|
return const SizedBox.shrink();
|
||||||
final result = actionResult.result;
|
|
||||||
|
|
||||||
// only show the result dialog when the action is WorkspaceMemberActionType.add
|
|
||||||
if (actionType == WorkspaceMemberActionType.add) {
|
|
||||||
result.fold(
|
|
||||||
(s) {
|
|
||||||
showSnackBarMessage(
|
|
||||||
context,
|
|
||||||
LocaleKeys.settings_appearance_members_addMemberSuccess.tr(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(f) {
|
|
||||||
Log.error('add workspace member failed: $f');
|
|
||||||
final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
|
|
||||||
? LocaleKeys.settings_appearance_members_memberLimitExceeded.tr()
|
|
||||||
: LocaleKeys.settings_appearance_members_failedToAddMember.tr();
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => NavigatorOkCancelDialog(message: message),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else if (actionType == WorkspaceMemberActionType.invite) {
|
|
||||||
result.fold(
|
|
||||||
(s) {
|
|
||||||
showSnackBarMessage(
|
|
||||||
context,
|
|
||||||
LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(f) {
|
|
||||||
Log.error('invite workspace member failed: $f');
|
|
||||||
final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
|
|
||||||
? LocaleKeys.settings_appearance_members_memberLimitExceeded.tr()
|
|
||||||
: LocaleKeys.settings_appearance_members_failedToInviteMember
|
|
||||||
.tr();
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => NavigatorOkCancelDialog(message: message),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.onFailure((f) {
|
|
||||||
Log.error(
|
|
||||||
'[Member] Failed to perform ${actionType.toString()} action: $f',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/shared/af_role_pb_extension.dart';
|
|
||||||
import 'package:appflowy/shared/feature_flags.dart';
|
import 'package:appflowy/shared/feature_flags.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/presentation/settings/widgets/settings_menu_element.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
||||||
@ -112,26 +111,26 @@ class SettingsMenu extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
changeSelectedPage: changeSelectedPage,
|
changeSelectedPage: changeSelectedPage,
|
||||||
),
|
),
|
||||||
if (FeatureFlag.planBilling.isOn &&
|
// if (FeatureFlag.planBilling.isOn &&
|
||||||
userProfile.authenticator ==
|
// userProfile.authenticator ==
|
||||||
AuthenticatorPB.AppFlowyCloud &&
|
// AuthenticatorPB.AppFlowyCloud &&
|
||||||
member != null &&
|
// member != null &&
|
||||||
member!.role.isOwner) ...[
|
// member!.role.isOwner) ...[
|
||||||
SettingsMenuElement(
|
// SettingsMenuElement(
|
||||||
page: SettingsPage.plan,
|
// page: SettingsPage.plan,
|
||||||
selectedPage: currentPage,
|
// selectedPage: currentPage,
|
||||||
label: LocaleKeys.settings_planPage_menuLabel.tr(),
|
// label: LocaleKeys.settings_planPage_menuLabel.tr(),
|
||||||
icon: const FlowySvg(FlowySvgs.settings_plan_m),
|
// icon: const FlowySvg(FlowySvgs.settings_plan_m),
|
||||||
changeSelectedPage: changeSelectedPage,
|
// changeSelectedPage: changeSelectedPage,
|
||||||
),
|
// ),
|
||||||
SettingsMenuElement(
|
// SettingsMenuElement(
|
||||||
page: SettingsPage.billing,
|
// page: SettingsPage.billing,
|
||||||
selectedPage: currentPage,
|
// selectedPage: currentPage,
|
||||||
label: LocaleKeys.settings_billingPage_menuLabel.tr(),
|
// label: LocaleKeys.settings_billingPage_menuLabel.tr(),
|
||||||
icon: const FlowySvg(FlowySvgs.settings_billing_m),
|
// icon: const FlowySvg(FlowySvgs.settings_billing_m),
|
||||||
changeSelectedPage: changeSelectedPage,
|
// changeSelectedPage: changeSelectedPage,
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
if (kDebugMode)
|
if (kDebugMode)
|
||||||
SettingsMenuElement(
|
SettingsMenuElement(
|
||||||
// no need to translate this page
|
// no need to translate this page
|
||||||
|
@ -186,6 +186,7 @@ class NavigatorOkCancelDialog extends StatelessWidget {
|
|||||||
this.message,
|
this.message,
|
||||||
this.maxWidth,
|
this.maxWidth,
|
||||||
this.titleUpperCase = true,
|
this.titleUpperCase = true,
|
||||||
|
this.autoDismiss = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
final VoidCallback? onOkPressed;
|
final VoidCallback? onOkPressed;
|
||||||
@ -196,9 +197,18 @@ class NavigatorOkCancelDialog extends StatelessWidget {
|
|||||||
final String? message;
|
final String? message;
|
||||||
final double? maxWidth;
|
final double? maxWidth;
|
||||||
final bool titleUpperCase;
|
final bool titleUpperCase;
|
||||||
|
final bool autoDismiss;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final onCancel = onCancelPressed == null
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
onCancelPressed?.call();
|
||||||
|
if (autoDismiss) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
return StyledDialog(
|
return StyledDialog(
|
||||||
maxWidth: maxWidth ?? 500,
|
maxWidth: maxWidth ?? 500,
|
||||||
padding: EdgeInsets.symmetric(horizontal: Insets.xl, vertical: Insets.l),
|
padding: EdgeInsets.symmetric(horizontal: Insets.xl, vertical: Insets.l),
|
||||||
@ -227,12 +237,11 @@ class NavigatorOkCancelDialog extends StatelessWidget {
|
|||||||
OkCancelButton(
|
OkCancelButton(
|
||||||
onOkPressed: () {
|
onOkPressed: () {
|
||||||
onOkPressed?.call();
|
onOkPressed?.call();
|
||||||
Navigator.of(context).pop();
|
if (autoDismiss) {
|
||||||
},
|
Navigator.of(context).pop();
|
||||||
onCancelPressed: () {
|
}
|
||||||
onCancelPressed?.call();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
},
|
||||||
|
onCancelPressed: onCancel,
|
||||||
okTitle: okTitle?.toUpperCase(),
|
okTitle: okTitle?.toUpperCase(),
|
||||||
cancelTitle: cancelTitle?.toUpperCase(),
|
cancelTitle: cancelTitle?.toUpperCase(),
|
||||||
),
|
),
|
||||||
|
@ -73,7 +73,9 @@ Future<FlowyResult<Uint8List, Uint8List>> _extractPayload(
|
|||||||
case FFIStatusCode.Ok:
|
case FFIStatusCode.Ok:
|
||||||
return FlowySuccess(Uint8List.fromList(response.payload));
|
return FlowySuccess(Uint8List.fromList(response.payload));
|
||||||
case FFIStatusCode.Err:
|
case FFIStatusCode.Err:
|
||||||
return FlowyFailure(Uint8List.fromList(response.payload));
|
final errorBytes = Uint8List.fromList(response.payload);
|
||||||
|
GlobalErrorCodeNotifier.receiveErrorBytes(errorBytes);
|
||||||
|
return FlowyFailure(errorBytes);
|
||||||
case FFIStatusCode.Internal:
|
case FFIStatusCode.Internal:
|
||||||
final error = utf8.decode(response.payload);
|
final error = utf8.decode(response.payload);
|
||||||
Log.error("Dispatch internal error: $error");
|
Log.error("Dispatch internal error: $error");
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/dart-ffi/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/dart-ffi/protobuf.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
class FlowyInternalError {
|
class FlowyInternalError {
|
||||||
late FFIStatusCode _statusCode;
|
late FFIStatusCode _statusCode;
|
||||||
@ -20,15 +23,13 @@ class FlowyInternalError {
|
|||||||
return "$_statusCode: $_error";
|
return "$_statusCode: $_error";
|
||||||
}
|
}
|
||||||
|
|
||||||
FlowyInternalError(
|
FlowyInternalError({
|
||||||
{required FFIStatusCode statusCode, required String error}) {
|
required FFIStatusCode statusCode,
|
||||||
|
required String error,
|
||||||
|
}) {
|
||||||
_statusCode = statusCode;
|
_statusCode = statusCode;
|
||||||
_error = error;
|
_error = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
factory FlowyInternalError.from(FFIResponse resp) {
|
|
||||||
return FlowyInternalError(statusCode: resp.code, error: "");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class StackTraceError {
|
class StackTraceError {
|
||||||
@ -48,3 +49,61 @@ class StackTraceError {
|
|||||||
return '${error.runtimeType}. Stack trace: $trace';
|
return '${error.runtimeType}. Stack trace: $trace';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef void ErrorListener();
|
||||||
|
|
||||||
|
class GlobalErrorCodeNotifier extends ChangeNotifier {
|
||||||
|
// Static instance with lazy initialization
|
||||||
|
static final GlobalErrorCodeNotifier _instance =
|
||||||
|
GlobalErrorCodeNotifier._internal();
|
||||||
|
|
||||||
|
FlowyError? _error;
|
||||||
|
|
||||||
|
// Private internal constructor
|
||||||
|
GlobalErrorCodeNotifier._internal();
|
||||||
|
|
||||||
|
// Factory constructor to return the same instance
|
||||||
|
factory GlobalErrorCodeNotifier() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void receiveError(FlowyError error) {
|
||||||
|
if (_instance._error?.code != error.code) {
|
||||||
|
_instance._error = error;
|
||||||
|
_instance.notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void receiveErrorBytes(Uint8List bytes) {
|
||||||
|
try {
|
||||||
|
final error = FlowyError.fromBuffer(bytes);
|
||||||
|
if (_instance._error?.code != error.code) {
|
||||||
|
_instance._error = error;
|
||||||
|
_instance.notifyListeners();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Log.error("Can not parse error bytes: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ErrorListener add({
|
||||||
|
required void Function(FlowyError error) onError,
|
||||||
|
bool Function(FlowyError code)? onErrorIf,
|
||||||
|
}) {
|
||||||
|
void listener() {
|
||||||
|
final error = _instance._error;
|
||||||
|
if (error != null) {
|
||||||
|
if (onErrorIf == null || onErrorIf(error)) {
|
||||||
|
onError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_instance.addListener(listener);
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove(ErrorListener listener) {
|
||||||
|
_instance.removeListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -82,4 +82,7 @@ class Corners {
|
|||||||
|
|
||||||
static const BorderRadius s12Border = BorderRadius.all(s12Radius);
|
static const BorderRadius s12Border = BorderRadius.all(s12Radius);
|
||||||
static const Radius s12Radius = Radius.circular(12);
|
static const Radius s12Radius = Radius.circular(12);
|
||||||
|
|
||||||
|
static const BorderRadius s16Border = BorderRadius.all(s16Radius);
|
||||||
|
static const Radius s16Radius = Radius.circular(16);
|
||||||
}
|
}
|
||||||
|
@ -165,6 +165,7 @@ class FlowyTextButton extends StatelessWidget {
|
|||||||
this.decoration,
|
this.decoration,
|
||||||
this.fontFamily,
|
this.fontFamily,
|
||||||
this.isDangerous = false,
|
this.isDangerous = false,
|
||||||
|
this.borderColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String text;
|
final String text;
|
||||||
@ -188,6 +189,7 @@ class FlowyTextButton extends StatelessWidget {
|
|||||||
|
|
||||||
final String? fontFamily;
|
final String? fontFamily;
|
||||||
final bool isDangerous;
|
final bool isDangerous;
|
||||||
|
final Color? borderColor;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -211,7 +213,7 @@ class FlowyTextButton extends StatelessWidget {
|
|||||||
child = ConstrainedBox(
|
child = ConstrainedBox(
|
||||||
constraints: constraints,
|
constraints: constraints,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: onPressed ?? () {},
|
onPressed: onPressed,
|
||||||
focusNode: FocusNode(skipTraversal: onPressed == null),
|
focusNode: FocusNode(skipTraversal: onPressed == null),
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
overlayColor: const WidgetStatePropertyAll(Colors.transparent),
|
overlayColor: const WidgetStatePropertyAll(Colors.transparent),
|
||||||
@ -222,9 +224,10 @@ class FlowyTextButton extends StatelessWidget {
|
|||||||
shape: WidgetStateProperty.all(
|
shape: WidgetStateProperty.all(
|
||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
color: isDangerous
|
color: borderColor ??
|
||||||
? Theme.of(context).colorScheme.error
|
(isDangerous
|
||||||
: Colors.transparent,
|
? Theme.of(context).colorScheme.error
|
||||||
|
: Colors.transparent),
|
||||||
),
|
),
|
||||||
borderRadius: radius ?? Corners.s6Border,
|
borderRadius: radius ?? Corners.s6Border,
|
||||||
),
|
),
|
||||||
|
@ -44,7 +44,7 @@ class PrimaryButton extends StatelessWidget {
|
|||||||
return BaseStyledButton(
|
return BaseStyledButton(
|
||||||
minWidth: mode.size.width,
|
minWidth: mode.size.width,
|
||||||
minHeight: mode.size.height,
|
minHeight: mode.size.height,
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
bgColor: backgroundColor ?? Theme.of(context).colorScheme.primary,
|
bgColor: backgroundColor ?? Theme.of(context).colorScheme.primary,
|
||||||
hoverColor: Theme.of(context).colorScheme.primaryContainer,
|
hoverColor: Theme.of(context).colorScheme.primaryContainer,
|
||||||
borderRadius: mode.borderRadius,
|
borderRadius: mode.borderRadius,
|
||||||
|
32
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
32
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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -826,7 +826,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"again",
|
"again",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -876,7 +876,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
@ -888,7 +888,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -1128,7 +1128,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -1153,7 +1153,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1417,7 +1417,7 @@ dependencies = [
|
|||||||
"cssparser-macros",
|
"cssparser-macros",
|
||||||
"dtoa-short",
|
"dtoa-short",
|
||||||
"itoa 1.0.6",
|
"itoa 1.0.6",
|
||||||
"phf 0.11.2",
|
"phf 0.8.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1528,7 +1528,7 @@ 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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -2103,6 +2103,7 @@ dependencies = [
|
|||||||
"client-api",
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
|
"flowy-error",
|
||||||
"lib-infra",
|
"lib-infra",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2474,12 +2475,16 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"flowy-codegen",
|
||||||
|
"flowy-derive",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
|
"flowy-notification",
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
"flowy-storage-pub",
|
"flowy-storage-pub",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"lib-infra",
|
"lib-infra",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
|
"protobuf",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -2512,6 +2517,7 @@ dependencies = [
|
|||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-database",
|
"collab-database",
|
||||||
"collab-document",
|
"collab-document",
|
||||||
@ -3028,7 +3034,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -3045,7 +3051,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -3477,7 +3483,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -6021,7 +6027,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
|
@ -53,7 +53,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 = "76a8993cac42ff89acb507cdb99942cac7c9bfd0" }
|
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c2a839ba8bf9ead44679eb08f3a9680467b767ca" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
32
frontend/appflowy_web_app/src-tauri/Cargo.lock
generated
32
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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -800,7 +800,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"again",
|
"again",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -850,7 +850,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
@ -862,7 +862,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -1111,7 +1111,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -1136,7 +1136,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1407,7 +1407,7 @@ dependencies = [
|
|||||||
"cssparser-macros",
|
"cssparser-macros",
|
||||||
"dtoa-short",
|
"dtoa-short",
|
||||||
"itoa 1.0.10",
|
"itoa 1.0.10",
|
||||||
"phf 0.11.2",
|
"phf 0.8.0",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1518,7 +1518,7 @@ 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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -2133,6 +2133,7 @@ dependencies = [
|
|||||||
"client-api",
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
|
"flowy-error",
|
||||||
"lib-infra",
|
"lib-infra",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2504,12 +2505,16 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"flowy-codegen",
|
||||||
|
"flowy-derive",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
|
"flowy-notification",
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
"flowy-storage-pub",
|
"flowy-storage-pub",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"lib-infra",
|
"lib-infra",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
|
"protobuf",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -2542,6 +2547,7 @@ dependencies = [
|
|||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-database",
|
"collab-database",
|
||||||
"collab-document",
|
"collab-document",
|
||||||
@ -3095,7 +3101,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -3112,7 +3118,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -3549,7 +3555,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -6085,7 +6091,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
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 = "76a8993cac42ff89acb507cdb99942cac7c9bfd0" }
|
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c2a839ba8bf9ead44679eb08f3a9680467b767ca" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M7.31665 11.5668L12.0166 6.86683L11.0833 5.9335L7.31665 9.70016L5.41665 7.80016L4.48331 8.7335L7.31665 11.5668ZM8.24998 15.1668C7.32776 15.1668 6.46109 14.9918 5.64998 14.6418C4.83887 14.2918 4.13331 13.8168 3.53331 13.2168C2.93331 12.6168 2.45831 11.9113 2.10831 11.1002C1.75831 10.2891 1.58331 9.42238 1.58331 8.50016C1.58331 7.57794 1.75831 6.71127 2.10831 5.90016C2.45831 5.08905 2.93331 4.3835 3.53331 3.7835C4.13331 3.1835 4.83887 2.7085 5.64998 2.3585C6.46109 2.0085 7.32776 1.8335 8.24998 1.8335C9.1722 1.8335 10.0389 2.0085 10.85 2.3585C11.6611 2.7085 12.3666 3.1835 12.9666 3.7835C13.5666 4.3835 14.0416 5.08905 14.3916 5.90016C14.7416 6.71127 14.9166 7.57794 14.9166 8.50016C14.9166 9.42238 14.7416 10.2891 14.3916 11.1002C14.0416 11.9113 13.5666 12.6168 12.9666 13.2168C12.3666 13.8168 11.6611 14.2918 10.85 14.6418C10.0389 14.9918 9.1722 15.1668 8.24998 15.1668ZM8.24998 13.8335C9.73887 13.8335 11 13.3168 12.0333 12.2835C13.0666 11.2502 13.5833 9.98905 13.5833 8.50016C13.5833 7.01127 13.0666 5.75016 12.0333 4.71683C11 3.6835 9.73887 3.16683 8.24998 3.16683C6.76109 3.16683 5.49998 3.6835 4.46665 4.71683C3.43331 5.75016 2.91665 7.01127 2.91665 8.50016C2.91665 9.98905 3.43331 11.2502 4.46665 12.2835C5.49998 13.3168 6.76109 13.8335 8.24998 13.8335Z" fill="#653E8C"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
5
frontend/resources/flowy_icons/16x/warning.svg
Normal file
5
frontend/resources/flowy_icons/16x/warning.svg
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="8.00033" cy="7.99984" r="7.33333" fill="#FF811A"/>
|
||||||
|
<path d="M7.99967 4.6665C7.63148 4.6665 7.33301 4.96498 7.33301 5.33317V8.6665C7.33301 9.03469 7.63148 9.33317 7.99967 9.33317C8.36786 9.33317 8.66634 9.03469 8.66634 8.6665V5.33317C8.66634 4.96498 8.36786 4.6665 7.99967 4.6665Z" fill="white"/>
|
||||||
|
<path d="M7.99967 11.3332C8.36786 11.3332 8.66634 11.0347 8.66634 10.6665C8.66634 10.2983 8.36786 9.99984 7.99967 9.99984C7.63148 9.99984 7.33301 10.2983 7.33301 10.6665C7.33301 11.0347 7.63148 11.3332 7.99967 11.3332Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 646 B |
@ -358,7 +358,7 @@
|
|||||||
"email": "ኢሜል",
|
"email": "ኢሜል",
|
||||||
"tooltipSelectIcon": "አዶን ይምረጡ",
|
"tooltipSelectIcon": "አዶን ይምረጡ",
|
||||||
"selectAnIcon": "አዶን ይምረጡ",
|
"selectAnIcon": "አዶን ይምረጡ",
|
||||||
"pleaseInputYourOpenAIKey": "እባክዎን OpenAI ቁልፍዎን ያስገቡ",
|
"pleaseInputYourOpenAIKey": "እባክዎን AI ቁልፍዎን ያስገቡ",
|
||||||
"pleaseInputYourStabilityAIKey": "እባክዎ Stability AI ቁልፍን ያስገቡ",
|
"pleaseInputYourStabilityAIKey": "እባክዎ Stability AI ቁልፍን ያስገቡ",
|
||||||
"clickToLogout": "የአሁኑን ተጠቃሚ ለመግባት ጠቅ ያድርጉ"
|
"clickToLogout": "የአሁኑን ተጠቃሚ ለመግባት ጠቅ ያድርጉ"
|
||||||
},
|
},
|
||||||
@ -556,12 +556,12 @@
|
|||||||
"referencedBoard": "ማጣቀሻ ቦርድ",
|
"referencedBoard": "ማጣቀሻ ቦርድ",
|
||||||
"referencedGrid": "ማጣቀሻ ፍርግርግ",
|
"referencedGrid": "ማጣቀሻ ፍርግርግ",
|
||||||
"referencedCalendar": "የቀን ቀን መቁጠሪያ",
|
"referencedCalendar": "የቀን ቀን መቁጠሪያ",
|
||||||
"autoGeneratorMenuItemName": "OpenAI ጸሐፊ",
|
"autoGeneratorMenuItemName": "AI ጸሐፊ",
|
||||||
"autoGeneratorTitleName": "OpenAI ማንኛውንም ነገር እንዲጽፉ ይጠይቁ ...",
|
"autoGeneratorTitleName": "AI ማንኛውንም ነገር እንዲጽፉ ይጠይቁ ...",
|
||||||
"autoGeneratorLearnMore": "ተጨማሪ እወቅ",
|
"autoGeneratorLearnMore": "ተጨማሪ እወቅ",
|
||||||
"autoGeneratorGenerate": "ማመንጨት",
|
"autoGeneratorGenerate": "ማመንጨት",
|
||||||
"autoGeneratorHintText": "OpenAI ይጠይቁ ...",
|
"autoGeneratorHintText": "AI ይጠይቁ ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "የ OpenAI ቁልፍ ማግኘት አልተቻለም",
|
"autoGeneratorCantGetOpenAIKey": "የ AI ቁልፍ ማግኘት አልተቻለም",
|
||||||
"autoGeneratorRewrite": "እንደገና ይፃፉ",
|
"autoGeneratorRewrite": "እንደገና ይፃፉ",
|
||||||
"smartEdit": "ረዳቶች",
|
"smartEdit": "ረዳቶች",
|
||||||
"openAI": "ኦፔና",
|
"openAI": "ኦፔና",
|
||||||
@ -572,7 +572,7 @@
|
|||||||
"smartEditMakeLonger": "ረዘም ላለ ጊዜ ያድርጉ",
|
"smartEditMakeLonger": "ረዘም ላለ ጊዜ ያድርጉ",
|
||||||
"smartEditCouldNotFetchResult": "ከOpenAI ውጤት ማምለጥ አልተቻለም",
|
"smartEditCouldNotFetchResult": "ከOpenAI ውጤት ማምለጥ አልተቻለም",
|
||||||
"smartEditCouldNotFetchKey": "ኦፕናይ ቁልፍን ማጣት አልተቻለም",
|
"smartEditCouldNotFetchKey": "ኦፕናይ ቁልፍን ማጣት አልተቻለም",
|
||||||
"smartEditDisabled": "በቅንብሮች ውስጥ OpenAI ያገናኙ",
|
"smartEditDisabled": "በቅንብሮች ውስጥ AI ያገናኙ",
|
||||||
"discardResponse": "የ AI ምላሾችን መጣል ይፈልጋሉ?",
|
"discardResponse": "የ AI ምላሾችን መጣል ይፈልጋሉ?",
|
||||||
"createInlineMathEquation": "እኩልነት ይፍጠሩ",
|
"createInlineMathEquation": "እኩልነት ይፍጠሩ",
|
||||||
"toggleList": "የተስተካከለ ዝርዝር",
|
"toggleList": "የተስተካከለ ዝርዝር",
|
||||||
@ -657,8 +657,8 @@
|
|||||||
"placeholder": "የምስል ዩአርኤል ያስገቡ"
|
"placeholder": "የምስል ዩአርኤል ያስገቡ"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "ምስል OpenAI ውስጥ ምስልን ማመንጨት",
|
"label": "ምስል AI ውስጥ ምስልን ማመንጨት",
|
||||||
"placeholder": "ምስልን ለማመንጨት እባክዎን ለ OpenAI ይጠይቁ"
|
"placeholder": "ምስልን ለማመንጨት እባክዎን ለ AI ይጠይቁ"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "ምስልን Stability AI ያመነጫል",
|
"label": "ምስልን Stability AI ያመነጫል",
|
||||||
|
@ -406,7 +406,7 @@
|
|||||||
"email": "بريد إلكتروني",
|
"email": "بريد إلكتروني",
|
||||||
"tooltipSelectIcon": "حدد أيقونة",
|
"tooltipSelectIcon": "حدد أيقونة",
|
||||||
"selectAnIcon": "حدد أيقونة",
|
"selectAnIcon": "حدد أيقونة",
|
||||||
"pleaseInputYourOpenAIKey": "الرجاء إدخال مفتاح OpenAI الخاص بك",
|
"pleaseInputYourOpenAIKey": "الرجاء إدخال مفتاح AI الخاص بك",
|
||||||
"pleaseInputYourStabilityAIKey": "يرجى إدخال رمز Stability AI الخاص بك",
|
"pleaseInputYourStabilityAIKey": "يرجى إدخال رمز Stability AI الخاص بك",
|
||||||
"clickToLogout": "انقر لتسجيل خروج المستخدم الحالي"
|
"clickToLogout": "انقر لتسجيل خروج المستخدم الحالي"
|
||||||
},
|
},
|
||||||
@ -659,23 +659,23 @@
|
|||||||
"referencedGrid": "الشبكة المشار إليها",
|
"referencedGrid": "الشبكة المشار إليها",
|
||||||
"referencedCalendar": "التقويم المشار إليه",
|
"referencedCalendar": "التقويم المشار إليه",
|
||||||
"referencedDocument": "الوثيقة المشار إليها",
|
"referencedDocument": "الوثيقة المشار إليها",
|
||||||
"autoGeneratorMenuItemName": "كاتب OpenAI",
|
"autoGeneratorMenuItemName": "كاتب AI",
|
||||||
"autoGeneratorTitleName": "OpenAI: اطلب من الذكاء الاصطناعي كتابة أي شيء ...",
|
"autoGeneratorTitleName": "AI: اطلب من الذكاء الاصطناعي كتابة أي شيء ...",
|
||||||
"autoGeneratorLearnMore": "يتعلم أكثر",
|
"autoGeneratorLearnMore": "يتعلم أكثر",
|
||||||
"autoGeneratorGenerate": "يولد",
|
"autoGeneratorGenerate": "يولد",
|
||||||
"autoGeneratorHintText": "اسأل OpenAI ...",
|
"autoGeneratorHintText": "اسأل AI ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "لا يمكن الحصول على مفتاح OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "لا يمكن الحصول على مفتاح AI",
|
||||||
"autoGeneratorRewrite": "اعادة كتابة",
|
"autoGeneratorRewrite": "اعادة كتابة",
|
||||||
"smartEdit": "مساعدي الذكاء الاصطناعي",
|
"smartEdit": "مساعدي الذكاء الاصطناعي",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "أصلح التهجئة",
|
"smartEditFixSpelling": "أصلح التهجئة",
|
||||||
"warning": "⚠️ يمكن أن تكون استجابات الذكاء الاصطناعي غير دقيقة أو مضللة.",
|
"warning": "⚠️ يمكن أن تكون استجابات الذكاء الاصطناعي غير دقيقة أو مضللة.",
|
||||||
"smartEditSummarize": "لخص",
|
"smartEditSummarize": "لخص",
|
||||||
"smartEditImproveWriting": "تحسين الكتابة",
|
"smartEditImproveWriting": "تحسين الكتابة",
|
||||||
"smartEditMakeLonger": "اجعله أطول",
|
"smartEditMakeLonger": "اجعله أطول",
|
||||||
"smartEditCouldNotFetchResult": "تعذر جلب النتيجة من OpenAI",
|
"smartEditCouldNotFetchResult": "تعذر جلب النتيجة من AI",
|
||||||
"smartEditCouldNotFetchKey": "تعذر جلب مفتاح OpenAI",
|
"smartEditCouldNotFetchKey": "تعذر جلب مفتاح AI",
|
||||||
"smartEditDisabled": "قم بتوصيل OpenAI في الإعدادات",
|
"smartEditDisabled": "قم بتوصيل AI في الإعدادات",
|
||||||
"discardResponse": "هل تريد تجاهل استجابات الذكاء الاصطناعي؟",
|
"discardResponse": "هل تريد تجاهل استجابات الذكاء الاصطناعي؟",
|
||||||
"createInlineMathEquation": "اصنع معادلة",
|
"createInlineMathEquation": "اصنع معادلة",
|
||||||
"fonts": "الخطوط",
|
"fonts": "الخطوط",
|
||||||
@ -770,8 +770,8 @@
|
|||||||
"placeholder": "أدخل عنوان URL للصورة"
|
"placeholder": "أدخل عنوان URL للصورة"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "إنشاء صورة من OpenAI",
|
"label": "إنشاء صورة من AI",
|
||||||
"placeholder": "يرجى إدخال الامر الواصف لـ OpenAI لإنشاء الصورة"
|
"placeholder": "يرجى إدخال الامر الواصف لـ AI لإنشاء الصورة"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "إنشاء صورة من Stability AI",
|
"label": "إنشاء صورة من Stability AI",
|
||||||
@ -792,7 +792,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "ابحث عن صورة",
|
"searchForAnImage": "ابحث عن صورة",
|
||||||
"pleaseInputYourOpenAIKey": "يرجى إدخال مفتاح OpenAI الخاص بك في صفحة الإعدادات",
|
"pleaseInputYourOpenAIKey": "يرجى إدخال مفتاح AI الخاص بك في صفحة الإعدادات",
|
||||||
"pleaseInputYourStabilityAIKey": "يرجى إدخال مفتاح Stability AI الخاص بك في صفحة الإعدادات",
|
"pleaseInputYourStabilityAIKey": "يرجى إدخال مفتاح Stability AI الخاص بك في صفحة الإعدادات",
|
||||||
"saveImageToGallery": "احفظ الصورة",
|
"saveImageToGallery": "احفظ الصورة",
|
||||||
"failedToAddImageToGallery": "فشلت إضافة الصورة إلى المعرض",
|
"failedToAddImageToGallery": "فشلت إضافة الصورة إلى المعرض",
|
||||||
|
@ -383,7 +383,7 @@
|
|||||||
"email": "Correu electrònic",
|
"email": "Correu electrònic",
|
||||||
"tooltipSelectIcon": "Seleccioneu la icona",
|
"tooltipSelectIcon": "Seleccioneu la icona",
|
||||||
"selectAnIcon": "Seleccioneu una icona",
|
"selectAnIcon": "Seleccioneu una icona",
|
||||||
"pleaseInputYourOpenAIKey": "si us plau, introduïu la vostra clau OpenAI"
|
"pleaseInputYourOpenAIKey": "si us plau, introduïu la vostra clau AI"
|
||||||
},
|
},
|
||||||
"mobile": {
|
"mobile": {
|
||||||
"personalInfo": "Informació personal",
|
"personalInfo": "Informació personal",
|
||||||
@ -602,23 +602,23 @@
|
|||||||
"referencedBoard": "Junta de referència",
|
"referencedBoard": "Junta de referència",
|
||||||
"referencedGrid": "Quadrícula de referència",
|
"referencedGrid": "Quadrícula de referència",
|
||||||
"referencedCalendar": "Calendari de referència",
|
"referencedCalendar": "Calendari de referència",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Writer",
|
"autoGeneratorMenuItemName": "AI Writer",
|
||||||
"autoGeneratorTitleName": "OpenAI: Demana a AI que escrigui qualsevol cosa...",
|
"autoGeneratorTitleName": "AI: Demana a AI que escrigui qualsevol cosa...",
|
||||||
"autoGeneratorLearnMore": "Aprèn més",
|
"autoGeneratorLearnMore": "Aprèn més",
|
||||||
"autoGeneratorGenerate": "Generar",
|
"autoGeneratorGenerate": "Generar",
|
||||||
"autoGeneratorHintText": "Pregunta a OpenAI...",
|
"autoGeneratorHintText": "Pregunta a AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "No es pot obtenir la clau OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "No es pot obtenir la clau AI",
|
||||||
"autoGeneratorRewrite": "Reescriure",
|
"autoGeneratorRewrite": "Reescriure",
|
||||||
"smartEdit": "Assistents d'IA",
|
"smartEdit": "Assistents d'IA",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Corregir l'ortografia",
|
"smartEditFixSpelling": "Corregir l'ortografia",
|
||||||
"warning": "⚠️ Les respostes de la IA poden ser inexactes o enganyoses.",
|
"warning": "⚠️ Les respostes de la IA poden ser inexactes o enganyoses.",
|
||||||
"smartEditSummarize": "Resumir",
|
"smartEditSummarize": "Resumir",
|
||||||
"smartEditImproveWriting": "Millorar l'escriptura",
|
"smartEditImproveWriting": "Millorar l'escriptura",
|
||||||
"smartEditMakeLonger": "Fer més llarg",
|
"smartEditMakeLonger": "Fer més llarg",
|
||||||
"smartEditCouldNotFetchResult": "No s'ha pogut obtenir el resultat d'OpenAI",
|
"smartEditCouldNotFetchResult": "No s'ha pogut obtenir el resultat d'AI",
|
||||||
"smartEditCouldNotFetchKey": "No s'ha pogut obtenir la clau OpenAI",
|
"smartEditCouldNotFetchKey": "No s'ha pogut obtenir la clau AI",
|
||||||
"smartEditDisabled": "Connecteu OpenAI a Configuració",
|
"smartEditDisabled": "Connecteu AI a Configuració",
|
||||||
"discardResponse": "Voleu descartar les respostes d'IA?",
|
"discardResponse": "Voleu descartar les respostes d'IA?",
|
||||||
"createInlineMathEquation": "Crea una equació",
|
"createInlineMathEquation": "Crea una equació",
|
||||||
"fonts": "Fonts",
|
"fonts": "Fonts",
|
||||||
@ -714,7 +714,7 @@
|
|||||||
"placeholder": "Introduïu l'URL de la imatge"
|
"placeholder": "Introduïu l'URL de la imatge"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Generar imatge des d'OpenAI"
|
"label": "Generar imatge des d'AI"
|
||||||
},
|
},
|
||||||
"support": "El límit de mida de la imatge és de 5 MB. Formats admesos: JPEG, PNG, GIF, SVG",
|
"support": "El límit de mida de la imatge és de 5 MB. Formats admesos: JPEG, PNG, GIF, SVG",
|
||||||
"error": {
|
"error": {
|
||||||
|
@ -480,7 +480,7 @@
|
|||||||
"email": "ئیمەیڵ",
|
"email": "ئیمەیڵ",
|
||||||
"tooltipSelectIcon": "هەڵبژاەدنی وێنۆچكه",
|
"tooltipSelectIcon": "هەڵبژاەدنی وێنۆچكه",
|
||||||
"selectAnIcon": "هەڵبژاردنی وێنۆچكه",
|
"selectAnIcon": "هەڵبژاردنی وێنۆچكه",
|
||||||
"pleaseInputYourOpenAIKey": "تکایە کلیلی OpenAI ـەکەت بنووسە",
|
"pleaseInputYourOpenAIKey": "تکایە کلیلی AI ـەکەت بنووسە",
|
||||||
"pleaseInputYourStabilityAIKey": "تکایە جێگیری کلیلی AI ـەکەت بنووسە",
|
"pleaseInputYourStabilityAIKey": "تکایە جێگیری کلیلی AI ـەکەت بنووسە",
|
||||||
"clickToLogout": "بۆ دەرچوون لە بەکارهێنەری ئێستا کلیک بکە"
|
"clickToLogout": "بۆ دەرچوون لە بەکارهێنەری ئێستا کلیک بکە"
|
||||||
},
|
},
|
||||||
@ -734,23 +734,23 @@
|
|||||||
"referencedGrid": "تۆڕی ئاماژەپێکراو",
|
"referencedGrid": "تۆڕی ئاماژەپێکراو",
|
||||||
"referencedCalendar": "ساڵنامەی ئاماژەپێکراو",
|
"referencedCalendar": "ساڵنامەی ئاماژەپێکراو",
|
||||||
"referencedDocument": "بەڵگەنامەی ئاماژەپێکراو",
|
"referencedDocument": "بەڵگەنامەی ئاماژەپێکراو",
|
||||||
"autoGeneratorMenuItemName": "OpenAI نووسەری",
|
"autoGeneratorMenuItemName": "AI نووسەری",
|
||||||
"autoGeneratorTitleName": "داوا لە AI بکە هەر شتێک بنووسێت...",
|
"autoGeneratorTitleName": "داوا لە AI بکە هەر شتێک بنووسێت...",
|
||||||
"autoGeneratorLearnMore": "زیاتر زانین",
|
"autoGeneratorLearnMore": "زیاتر زانین",
|
||||||
"autoGeneratorGenerate": "بنووسە",
|
"autoGeneratorGenerate": "بنووسە",
|
||||||
"autoGeneratorHintText": "لە OpenAI پرسیار بکە...",
|
"autoGeneratorHintText": "لە AI پرسیار بکە...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "نەتوانرا کلیلی OpenAI بەدەست بهێنرێت",
|
"autoGeneratorCantGetOpenAIKey": "نەتوانرا کلیلی AI بەدەست بهێنرێت",
|
||||||
"autoGeneratorRewrite": "دووبارە نووسینەوە",
|
"autoGeneratorRewrite": "دووبارە نووسینەوە",
|
||||||
"smartEdit": "یاریدەدەری زیرەک",
|
"smartEdit": "یاریدەدەری زیرەک",
|
||||||
"openAI": "OpenAI ژیری دەستکرد",
|
"openAI": "AI ژیری دەستکرد",
|
||||||
"smartEditFixSpelling": "ڕاستکردنەوەی نووسین",
|
"smartEditFixSpelling": "ڕاستکردنەوەی نووسین",
|
||||||
"warning": "⚠️ وەڵامەکانی AI دەتوانن هەڵە یان چەواشەکارانە بن",
|
"warning": "⚠️ وەڵامەکانی AI دەتوانن هەڵە یان چەواشەکارانە بن",
|
||||||
"smartEditSummarize": "کورتەنووسی",
|
"smartEditSummarize": "کورتەنووسی",
|
||||||
"smartEditImproveWriting": "پێشخستن نوووسین",
|
"smartEditImproveWriting": "پێشخستن نوووسین",
|
||||||
"smartEditMakeLonger": "درێژتری بکەرەوە",
|
"smartEditMakeLonger": "درێژتری بکەرەوە",
|
||||||
"smartEditCouldNotFetchResult": "هیچ ئەنجامێک لە OpenAI وەرنەگیرا",
|
"smartEditCouldNotFetchResult": "هیچ ئەنجامێک لە AI وەرنەگیرا",
|
||||||
"smartEditCouldNotFetchKey": "نەتوانرا کلیلی OpenAI بهێنێتە ئاراوە",
|
"smartEditCouldNotFetchKey": "نەتوانرا کلیلی AI بهێنێتە ئاراوە",
|
||||||
"smartEditDisabled": "لە ڕێکخستنەکاندا پەیوەندی بە OpenAI بکە",
|
"smartEditDisabled": "لە ڕێکخستنەکاندا پەیوەندی بە AI بکە",
|
||||||
"discardResponse": "ئایا دەتەوێت وەڵامەکانی AI بسڕیتەوە؟",
|
"discardResponse": "ئایا دەتەوێت وەڵامەکانی AI بسڕیتەوە؟",
|
||||||
"createInlineMathEquation": "درووست کردنی هاوکێشە",
|
"createInlineMathEquation": "درووست کردنی هاوکێشە",
|
||||||
"fonts": "فۆنتەکان",
|
"fonts": "فۆنتەکان",
|
||||||
|
@ -378,7 +378,7 @@
|
|||||||
"email": "E-mail",
|
"email": "E-mail",
|
||||||
"tooltipSelectIcon": "Vyberte ikonu",
|
"tooltipSelectIcon": "Vyberte ikonu",
|
||||||
"selectAnIcon": "Vyberte ikonu",
|
"selectAnIcon": "Vyberte ikonu",
|
||||||
"pleaseInputYourOpenAIKey": "Prosím vložte svůj OpenAI klíč",
|
"pleaseInputYourOpenAIKey": "Prosím vložte svůj AI klíč",
|
||||||
"pleaseInputYourStabilityAIKey": "Prosím vložte svůj Stability AI klíč",
|
"pleaseInputYourStabilityAIKey": "Prosím vložte svůj Stability AI klíč",
|
||||||
"clickToLogout": "Klin"
|
"clickToLogout": "Klin"
|
||||||
},
|
},
|
||||||
@ -606,23 +606,23 @@
|
|||||||
"referencedGrid": "Odkazovaná mřížka",
|
"referencedGrid": "Odkazovaná mřížka",
|
||||||
"referencedCalendar": "Odkazovaný kalendář",
|
"referencedCalendar": "Odkazovaný kalendář",
|
||||||
"referencedDocument": "Odkazovaný dokument",
|
"referencedDocument": "Odkazovaný dokument",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Writer",
|
"autoGeneratorMenuItemName": "AI Writer",
|
||||||
"autoGeneratorTitleName": "OpenAI: Zeptej se AI na cokoliv...",
|
"autoGeneratorTitleName": "AI: Zeptej se AI na cokoliv...",
|
||||||
"autoGeneratorLearnMore": "Zjistit více",
|
"autoGeneratorLearnMore": "Zjistit více",
|
||||||
"autoGeneratorGenerate": "Vygenerovat",
|
"autoGeneratorGenerate": "Vygenerovat",
|
||||||
"autoGeneratorHintText": "Zeptat se OpenAI...",
|
"autoGeneratorHintText": "Zeptat se AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Nepodařilo se získat klíč OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Nepodařilo se získat klíč AI",
|
||||||
"autoGeneratorRewrite": "Přepsat",
|
"autoGeneratorRewrite": "Přepsat",
|
||||||
"smartEdit": "AI asistenti",
|
"smartEdit": "AI asistenti",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Opravit pravopis",
|
"smartEditFixSpelling": "Opravit pravopis",
|
||||||
"warning": "⚠️ odpovědi AI mohou být nepřesné nebo zavádějící.",
|
"warning": "⚠️ odpovědi AI mohou být nepřesné nebo zavádějící.",
|
||||||
"smartEditSummarize": "Shrnout",
|
"smartEditSummarize": "Shrnout",
|
||||||
"smartEditImproveWriting": "Vylepšit styl psaní",
|
"smartEditImproveWriting": "Vylepšit styl psaní",
|
||||||
"smartEditMakeLonger": "Prodloužit",
|
"smartEditMakeLonger": "Prodloužit",
|
||||||
"smartEditCouldNotFetchResult": "Nepodařilo se stáhnout výsledek z OpenAI",
|
"smartEditCouldNotFetchResult": "Nepodařilo se stáhnout výsledek z AI",
|
||||||
"smartEditCouldNotFetchKey": "Nepodařilo se stáhnout klíč OpenAI",
|
"smartEditCouldNotFetchKey": "Nepodařilo se stáhnout klíč AI",
|
||||||
"smartEditDisabled": "Propojit s OpenAI v Nastavení",
|
"smartEditDisabled": "Propojit s AI v Nastavení",
|
||||||
"discardResponse": "Opravdu chcete zahodit odpovědi od AI?",
|
"discardResponse": "Opravdu chcete zahodit odpovědi od AI?",
|
||||||
"createInlineMathEquation": "Vytvořit rovnici",
|
"createInlineMathEquation": "Vytvořit rovnici",
|
||||||
"fonts": "Písma",
|
"fonts": "Písma",
|
||||||
@ -716,7 +716,7 @@
|
|||||||
"placeholder": "Vlože URL adresu obrázku"
|
"placeholder": "Vlože URL adresu obrázku"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Vygenerujte obrázek pomocí OpenAI",
|
"label": "Vygenerujte obrázek pomocí AI",
|
||||||
"placeholder": "Prosím vlo"
|
"placeholder": "Prosím vlo"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
@ -735,7 +735,7 @@
|
|||||||
"placeholder": "Vložte nebo napište odkaz na obrázek"
|
"placeholder": "Vložte nebo napište odkaz na obrázek"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Hledat obrázek",
|
"searchForAnImage": "Hledat obrázek",
|
||||||
"pleaseInputYourOpenAIKey": "zadejte prosím svůj OpenAI klíč v Nastavení",
|
"pleaseInputYourOpenAIKey": "zadejte prosím svůj AI klíč v Nastavení",
|
||||||
"pleaseInputYourStabilityAIKey": "prosím vložte svůjStability AI klíč v Nastavení",
|
"pleaseInputYourStabilityAIKey": "prosím vložte svůjStability AI klíč v Nastavení",
|
||||||
"saveImageToGallery": "Uložit obrázek",
|
"saveImageToGallery": "Uložit obrázek",
|
||||||
"failedToAddImageToGallery": "Nepodařilo se přidat obrázek do galerie",
|
"failedToAddImageToGallery": "Nepodařilo se přidat obrázek do galerie",
|
||||||
|
@ -389,15 +389,6 @@
|
|||||||
"loginLabel": "Anmeldung",
|
"loginLabel": "Anmeldung",
|
||||||
"logoutLabel": "Ausloggen"
|
"logoutLabel": "Ausloggen"
|
||||||
},
|
},
|
||||||
"keys": {
|
|
||||||
"title": "KI API-Schlüssel",
|
|
||||||
"openAILabel": "OpenAI API-Schlüssel",
|
|
||||||
"openAITooltip": "Der für die KI-Modelle zu verwendende OpenAI-API-Schlüssel",
|
|
||||||
"openAIHint": "OpenAI API-Schlüssel eingeben",
|
|
||||||
"stabilityAILabel": "Stability API-Schlüssel",
|
|
||||||
"stabilityAITooltip": "Der für die KI-Modelle zu verwendende Stability API-Schlüssel",
|
|
||||||
"stabilityAIHint": "Stability API-Schlüssel eingeben"
|
|
||||||
},
|
|
||||||
"description": "Passe dein Profil an, verwalte deine Sicherheitseinstellungen und KI API-Schlüssel oder melde dich bei deinem Konto an."
|
"description": "Passe dein Profil an, verwalte deine Sicherheitseinstellungen und KI API-Schlüssel oder melde dich bei deinem Konto an."
|
||||||
},
|
},
|
||||||
"workspacePage": {
|
"workspacePage": {
|
||||||
@ -642,13 +633,7 @@
|
|||||||
"aiSettingsDescription": "Wähle oder konfiguriere KI-Modelle, die in @:appName verwendet werden. Für eine optimale Leistung empfehlen wir die Verwendung der Standardmodelloptionen",
|
"aiSettingsDescription": "Wähle oder konfiguriere KI-Modelle, die in @:appName verwendet werden. Für eine optimale Leistung empfehlen wir die Verwendung der Standardmodelloptionen",
|
||||||
"loginToEnableAIFeature": "KI-Funktionen werden erst nach der Anmeldung bei @:appName Cloud aktiviert. Wenn du kein @:appName-Konto hast, gehe zu „Mein Konto“, um dich zu registrieren",
|
"loginToEnableAIFeature": "KI-Funktionen werden erst nach der Anmeldung bei @:appName Cloud aktiviert. Wenn du kein @:appName-Konto hast, gehe zu „Mein Konto“, um dich zu registrieren",
|
||||||
"llmModel": "Sprachmodell",
|
"llmModel": "Sprachmodell",
|
||||||
"title": "KI-API-Schlüssel",
|
"title": "KI-API-Schlüssel"
|
||||||
"openAILabel": "OpenAI API-Schlüssel",
|
|
||||||
"openAITooltip": "Du findest deinen geheimen API-Schlüssel auf der API-Schlüsselseite",
|
|
||||||
"openAIHint": "Gebe deinen OpenAI API-Schlüssel ein",
|
|
||||||
"stabilityAILabel": "Stability API-Schlüssel",
|
|
||||||
"stabilityAITooltip": "Dein Stability API-Schlüssel, der zur Authentifizierung deiner Anfragen verwendet wird",
|
|
||||||
"stabilityAIHint": "Gebe deinen Stability API-Schlüssel ein"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"planPage": {
|
"planPage": {
|
||||||
@ -1006,7 +991,7 @@
|
|||||||
"email": "E-Mail",
|
"email": "E-Mail",
|
||||||
"tooltipSelectIcon": "Symbol auswählen",
|
"tooltipSelectIcon": "Symbol auswählen",
|
||||||
"selectAnIcon": "Ein Symbol auswählen",
|
"selectAnIcon": "Ein Symbol auswählen",
|
||||||
"pleaseInputYourOpenAIKey": "Bitte gebe den OpenAI-Schlüssel ein",
|
"pleaseInputYourOpenAIKey": "Bitte gebe den AI-Schlüssel ein",
|
||||||
"pleaseInputYourStabilityAIKey": "Bitte gebe den Stability AI Schlüssel ein",
|
"pleaseInputYourStabilityAIKey": "Bitte gebe den Stability AI Schlüssel ein",
|
||||||
"clickToLogout": "Klicken, um den aktuellen Nutzer auszulogen"
|
"clickToLogout": "Klicken, um den aktuellen Nutzer auszulogen"
|
||||||
},
|
},
|
||||||
@ -1343,23 +1328,23 @@
|
|||||||
"referencedGrid": "Referenziertes Raster",
|
"referencedGrid": "Referenziertes Raster",
|
||||||
"referencedCalendar": "Referenzierter Kalender",
|
"referencedCalendar": "Referenzierter Kalender",
|
||||||
"referencedDocument": "Referenziertes Dokument",
|
"referencedDocument": "Referenziertes Dokument",
|
||||||
"autoGeneratorMenuItemName": "OpenAI-Autor",
|
"autoGeneratorMenuItemName": "AI-Autor",
|
||||||
"autoGeneratorTitleName": "OpenAI: Die KI bitten, etwas zu schreiben ...",
|
"autoGeneratorTitleName": "AI: Die KI bitten, etwas zu schreiben ...",
|
||||||
"autoGeneratorLearnMore": "Mehr erfahren",
|
"autoGeneratorLearnMore": "Mehr erfahren",
|
||||||
"autoGeneratorGenerate": "Erstellen",
|
"autoGeneratorGenerate": "Erstellen",
|
||||||
"autoGeneratorHintText": "OpenAI fragen ...",
|
"autoGeneratorHintText": "AI fragen ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Der OpenAI-Schlüssel kann nicht abgerufen werden",
|
"autoGeneratorCantGetOpenAIKey": "Der AI-Schlüssel kann nicht abgerufen werden",
|
||||||
"autoGeneratorRewrite": "Umschreiben",
|
"autoGeneratorRewrite": "Umschreiben",
|
||||||
"smartEdit": "KI-Assistenten",
|
"smartEdit": "KI-Assistenten",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Korrigiere Rechtschreibung",
|
"smartEditFixSpelling": "Korrigiere Rechtschreibung",
|
||||||
"warning": "⚠️ KI-Antworten können ungenau oder irreführend sein.",
|
"warning": "⚠️ KI-Antworten können ungenau oder irreführend sein.",
|
||||||
"smartEditSummarize": "Zusammenfassen",
|
"smartEditSummarize": "Zusammenfassen",
|
||||||
"smartEditImproveWriting": "Das Geschriebene verbessern",
|
"smartEditImproveWriting": "Das Geschriebene verbessern",
|
||||||
"smartEditMakeLonger": "Länger machen",
|
"smartEditMakeLonger": "Länger machen",
|
||||||
"smartEditCouldNotFetchResult": "Das Ergebnis konnte nicht von OpenAI abgerufen werden",
|
"smartEditCouldNotFetchResult": "Das Ergebnis konnte nicht von AI abgerufen werden",
|
||||||
"smartEditCouldNotFetchKey": "Der OpenAI-Schlüssel konnte nicht abgerufen werden",
|
"smartEditCouldNotFetchKey": "Der AI-Schlüssel konnte nicht abgerufen werden",
|
||||||
"smartEditDisabled": "OpenAI in den Einstellungen verbinden",
|
"smartEditDisabled": "AI in den Einstellungen verbinden",
|
||||||
"appflowyAIEditDisabled": "Melde dich an, um KI-Funktionen zu aktivieren",
|
"appflowyAIEditDisabled": "Melde dich an, um KI-Funktionen zu aktivieren",
|
||||||
"discardResponse": "Möchtest du die KI-Antworten verwerfen?",
|
"discardResponse": "Möchtest du die KI-Antworten verwerfen?",
|
||||||
"createInlineMathEquation": "Formel erstellen",
|
"createInlineMathEquation": "Formel erstellen",
|
||||||
@ -1489,8 +1474,8 @@
|
|||||||
"placeholder": "Bild-URL eingeben"
|
"placeholder": "Bild-URL eingeben"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Bild mit OpenAI erstellen",
|
"label": "Bild mit AI erstellen",
|
||||||
"placeholder": "Bitte den Prompt für OpenAI eingeben, um ein Bild zu erstellen"
|
"placeholder": "Bitte den Prompt für AI eingeben, um ein Bild zu erstellen"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Bild mit Stability AI erstellen",
|
"label": "Bild mit Stability AI erstellen",
|
||||||
@ -1512,7 +1497,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Nach einem Bild suchen",
|
"searchForAnImage": "Nach einem Bild suchen",
|
||||||
"pleaseInputYourOpenAIKey": "biitte den OpenAI Schlüssel in der Einstellungsseite eingeben",
|
"pleaseInputYourOpenAIKey": "biitte den AI Schlüssel in der Einstellungsseite eingeben",
|
||||||
"pleaseInputYourStabilityAIKey": "biitte den Stability AI Schlüssel in der Einstellungsseite eingeben",
|
"pleaseInputYourStabilityAIKey": "biitte den Stability AI Schlüssel in der Einstellungsseite eingeben",
|
||||||
"saveImageToGallery": "Bild speichern",
|
"saveImageToGallery": "Bild speichern",
|
||||||
"failedToAddImageToGallery": "Das Bild konnte nicht zur Galerie hinzugefügt werden",
|
"failedToAddImageToGallery": "Das Bild konnte nicht zur Galerie hinzugefügt werden",
|
||||||
|
@ -477,7 +477,7 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"tooltipSelectIcon": "Select icon",
|
"tooltipSelectIcon": "Select icon",
|
||||||
"selectAnIcon": "Select an icon",
|
"selectAnIcon": "Select an icon",
|
||||||
"pleaseInputYourOpenAIKey": "παρακαλώ εισάγετε το OpenAI κλειδί σας",
|
"pleaseInputYourOpenAIKey": "παρακαλώ εισάγετε το AI κλειδί σας",
|
||||||
"pleaseInputYourStabilityAIKey": "παρακαλώ εισάγετε το Stability AI κλειδί σας",
|
"pleaseInputYourStabilityAIKey": "παρακαλώ εισάγετε το Stability AI κλειδί σας",
|
||||||
"clickToLogout": "Κάντε κλικ για αποσύνδεση του τρέχοντος χρήστη"
|
"clickToLogout": "Κάντε κλικ για αποσύνδεση του τρέχοντος χρήστη"
|
||||||
},
|
},
|
||||||
@ -789,23 +789,23 @@
|
|||||||
"referencedGrid": "Referenced Grid",
|
"referencedGrid": "Referenced Grid",
|
||||||
"referencedCalendar": "Referenced Calendar",
|
"referencedCalendar": "Referenced Calendar",
|
||||||
"referencedDocument": "Referenced Document",
|
"referencedDocument": "Referenced Document",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Writer",
|
"autoGeneratorMenuItemName": "AI Writer",
|
||||||
"autoGeneratorTitleName": "OpenAI: Ask AI to write anything...",
|
"autoGeneratorTitleName": "AI: Ask AI to write anything...",
|
||||||
"autoGeneratorLearnMore": "Μάθετε περισσότερα",
|
"autoGeneratorLearnMore": "Μάθετε περισσότερα",
|
||||||
"autoGeneratorGenerate": "Generate",
|
"autoGeneratorGenerate": "Generate",
|
||||||
"autoGeneratorHintText": "Ρωτήστε Το OpenAI ...",
|
"autoGeneratorHintText": "Ρωτήστε Το AI ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Αδυναμία λήψης κλειδιού OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Αδυναμία λήψης κλειδιού AI",
|
||||||
"autoGeneratorRewrite": "Rewrite",
|
"autoGeneratorRewrite": "Rewrite",
|
||||||
"smartEdit": "AI Assistants",
|
"smartEdit": "AI Assistants",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Διόρθωση ορθογραφίας",
|
"smartEditFixSpelling": "Διόρθωση ορθογραφίας",
|
||||||
"warning": "⚠️ Οι απαντήσεις AI μπορεί να είναι ανακριβείς ή παραπλανητικές.",
|
"warning": "⚠️ Οι απαντήσεις AI μπορεί να είναι ανακριβείς ή παραπλανητικές.",
|
||||||
"smartEditSummarize": "Summarize",
|
"smartEditSummarize": "Summarize",
|
||||||
"smartEditImproveWriting": "Improve writing",
|
"smartEditImproveWriting": "Improve writing",
|
||||||
"smartEditMakeLonger": "Make longer",
|
"smartEditMakeLonger": "Make longer",
|
||||||
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
|
"smartEditCouldNotFetchResult": "Could not fetch result from AI",
|
||||||
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
"smartEditCouldNotFetchKey": "Could not fetch AI key",
|
||||||
"smartEditDisabled": "Connect OpenAI in Settings",
|
"smartEditDisabled": "Connect AI in Settings",
|
||||||
"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": "Γραμματοσειρές",
|
||||||
@ -919,8 +919,8 @@
|
|||||||
"placeholder": "Enter image URL"
|
"placeholder": "Enter image URL"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Generate image from OpenAI",
|
"label": "Generate image from AI",
|
||||||
"placeholder": "Please input the prompt for OpenAI to generate image"
|
"placeholder": "Please input the prompt for AI to generate image"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Generate image from Stability AI",
|
"label": "Generate image from Stability AI",
|
||||||
@ -942,7 +942,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Search for an image",
|
"searchForAnImage": "Search for an image",
|
||||||
"pleaseInputYourOpenAIKey": "please input your OpenAI key in Settings page",
|
"pleaseInputYourOpenAIKey": "please input your AI key in Settings page",
|
||||||
"pleaseInputYourStabilityAIKey": "please input your Stability AI key in Settings page",
|
"pleaseInputYourStabilityAIKey": "please input your Stability AI key in Settings page",
|
||||||
"saveImageToGallery": "Save image",
|
"saveImageToGallery": "Save image",
|
||||||
"failedToAddImageToGallery": "Failed to add image to gallery",
|
"failedToAddImageToGallery": "Failed to add image to gallery",
|
||||||
|
@ -282,7 +282,15 @@
|
|||||||
"removeSuccess": "Removed successfully",
|
"removeSuccess": "Removed successfully",
|
||||||
"favoriteSpace": "Favorites",
|
"favoriteSpace": "Favorites",
|
||||||
"RecentSpace": "Recent",
|
"RecentSpace": "Recent",
|
||||||
"Spaces": "Spaces"
|
"Spaces": "Spaces",
|
||||||
|
"upgradeToPro": "Upgrade to Pro Plan",
|
||||||
|
"upgradeToAIMax": "Unlock unlimited AI",
|
||||||
|
"storageLimitDialogTitle": "You are running out of storage space. Upgrade to Pro Plan to get more storage",
|
||||||
|
"aiResponseLitmitDialogTitle": "You are running out of AI responses. Upgrade to Pro Plan or AI Max to get more AI responses",
|
||||||
|
"aiResponseLitmit": "You are running out of AI responses. Go to Settings -> Plan -> Click AI Max or Pro Plan to get more AI responses",
|
||||||
|
"purchaseStorageSpace": "Purchase Storage Space",
|
||||||
|
"purchaseAIResponse": "Purchase ",
|
||||||
|
"upgradeToAILocal": "AI offline on your device"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"export": {
|
"export": {
|
||||||
@ -646,14 +654,7 @@
|
|||||||
"restartLocalAI": "Restart Local AI",
|
"restartLocalAI": "Restart Local AI",
|
||||||
"disableLocalAIDialog": "Do you want to disable local AI?",
|
"disableLocalAIDialog": "Do you want to disable local AI?",
|
||||||
"localAIToggleTitle": "Toggle to enable or disable local AI",
|
"localAIToggleTitle": "Toggle to enable or disable local AI",
|
||||||
"fetchLocalModel": "Fetch local model configuration",
|
"fetchLocalModel": "Fetch local model configuration"
|
||||||
"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": {
|
||||||
@ -663,14 +664,18 @@
|
|||||||
"title": "Plan usage summary",
|
"title": "Plan usage summary",
|
||||||
"storageLabel": "Storage",
|
"storageLabel": "Storage",
|
||||||
"storageUsage": "{} of {} GB",
|
"storageUsage": "{} of {} GB",
|
||||||
"collaboratorsLabel": "Collaborators",
|
"unlimitedStorageLabel": "Unlimited storage",
|
||||||
|
"collaboratorsLabel": "Members",
|
||||||
"collaboratorsUsage": "{} of {}",
|
"collaboratorsUsage": "{} of {}",
|
||||||
"aiResponseLabel": "AI Responses",
|
"aiResponseLabel": "AI Responses",
|
||||||
"aiResponseUsage": "{} of {}",
|
"aiResponseUsage": "{} of {}",
|
||||||
|
"unlimitedAILabel": "Unlimited responses",
|
||||||
"proBadge": "Pro",
|
"proBadge": "Pro",
|
||||||
"memberProToggle": "Unlimited members",
|
"aiMaxBadge": "AI Max",
|
||||||
"guestCollabToggle": "10 guest collaborators",
|
"aiOnDeviceBadge": "AI On-device",
|
||||||
"storageUnlimited": "Unlimited storage with your Pro Plan",
|
"memberProToggle": "More members & unlimited AI",
|
||||||
|
"aiMaxToggle": "Unlimited AI responses",
|
||||||
|
"aiOnDeviceToggle": "On-device AI for ultimate privacy",
|
||||||
"aiCredit": {
|
"aiCredit": {
|
||||||
"title": "Add @:appName AI Credit",
|
"title": "Add @:appName AI Credit",
|
||||||
"price": "{}",
|
"price": "{}",
|
||||||
@ -688,25 +693,28 @@
|
|||||||
"freeInfo": "Perfect for individuals or small teams up to 3 members.",
|
"freeInfo": "Perfect for individuals or small teams up to 3 members.",
|
||||||
"proInfo": "Perfect for small and medium teams up to 10 members.",
|
"proInfo": "Perfect for small and medium teams up to 10 members.",
|
||||||
"teamInfo": "Perfect for all productive and well-organized teams..",
|
"teamInfo": "Perfect for all productive and well-organized teams..",
|
||||||
"upgrade": "Compare &\n Upgrade",
|
"upgrade": "Change plan",
|
||||||
"freeProOne": "Collaborative workspace",
|
|
||||||
"freeProTwo": "Up to 3 members (incl. owner)",
|
|
||||||
"freeProThree": "Unlimited guests (view-only)",
|
|
||||||
"freeProFour": "Storage 5GB",
|
|
||||||
"freeProFive": "30 day revision history",
|
|
||||||
"freeConOne": "Guest collaborators (edit access)",
|
|
||||||
"freeConTwo": "Unlimited storage",
|
|
||||||
"freeConThree": "6 month revision history",
|
|
||||||
"professionalProOne": "Collaborative workspace",
|
|
||||||
"professionalProTwo": "Unlimited members",
|
|
||||||
"professionalProThree": "Unlimited guests (view-only)",
|
|
||||||
"professionalProFour": "Unlimited storage",
|
|
||||||
"professionalProFive": "6 month revision history",
|
|
||||||
"professionalConOne": "Unlimited guest collaborators (edit access)",
|
|
||||||
"professionalConTwo": "Unlimited AI responses",
|
|
||||||
"professionalConThree": "1 year revision history",
|
|
||||||
"canceledInfo": "Your plan is cancelled, you will be downgraded to the Free plan on {}."
|
"canceledInfo": "Your plan is cancelled, you will be downgraded to the Free plan on {}."
|
||||||
},
|
},
|
||||||
|
"addons": {
|
||||||
|
"title": "Add-ons",
|
||||||
|
"addLabel": "Add",
|
||||||
|
"activeLabel": "Added",
|
||||||
|
"aiMax": {
|
||||||
|
"title": "AI Max",
|
||||||
|
"description": "Unlock unlimited AI",
|
||||||
|
"price": "{}",
|
||||||
|
"priceInfo": "/user per month",
|
||||||
|
"billingInfo": "billed annually or {} billed monthly"
|
||||||
|
},
|
||||||
|
"aiOnDevice": {
|
||||||
|
"title": "AI On-device",
|
||||||
|
"description": "AI offline on your device",
|
||||||
|
"price": "{}",
|
||||||
|
"priceInfo": "/user per month",
|
||||||
|
"billingInfo": "billed annually or {} billed monthly"
|
||||||
|
}
|
||||||
|
},
|
||||||
"deal": {
|
"deal": {
|
||||||
"bannerLabel": "New year deal!",
|
"bannerLabel": "New year deal!",
|
||||||
"title": "Grow your team!",
|
"title": "Grow your team!",
|
||||||
@ -730,7 +738,36 @@
|
|||||||
"title": "Payment details",
|
"title": "Payment details",
|
||||||
"methodLabel": "Payment method",
|
"methodLabel": "Payment method",
|
||||||
"methodButtonLabel": "Edit method"
|
"methodButtonLabel": "Edit method"
|
||||||
}
|
},
|
||||||
|
"addons": {
|
||||||
|
"title": "Add-ons",
|
||||||
|
"addLabel": "Add",
|
||||||
|
"removeLabel": "Remove",
|
||||||
|
"renewLabel": "Renew",
|
||||||
|
"aiMax": {
|
||||||
|
"label": "AI Max",
|
||||||
|
"description": "Unlock unlimited AI and advanced models",
|
||||||
|
"activeDescription": "Next invoice due on {}",
|
||||||
|
"canceledDescription": "AI Max will be available until {}"
|
||||||
|
},
|
||||||
|
"aiOnDevice": {
|
||||||
|
"label": "AI On-device",
|
||||||
|
"description": "Unlock unlimited AI offline on your device",
|
||||||
|
"activeDescription": "Next invoice due on {}",
|
||||||
|
"canceledDescription": "AI On-device will be available until {}"
|
||||||
|
},
|
||||||
|
"removeDialog": {
|
||||||
|
"title": "Remove {}",
|
||||||
|
"description": "Are you sure you want to remove {plan}? You will lose access to the features and benefits of {plan} immediately."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"currentPeriodBadge": "CURRENT",
|
||||||
|
"changePeriod": "Change period",
|
||||||
|
"planPeriod": "{} period",
|
||||||
|
"monthlyInterval": "Monthly",
|
||||||
|
"monthlyPriceInfo": "per seat billed monthly",
|
||||||
|
"annualInterval": "Annually",
|
||||||
|
"annualPriceInfo": "per seat billed annually"
|
||||||
},
|
},
|
||||||
"comparePlanDialog": {
|
"comparePlanDialog": {
|
||||||
"title": "Compare & select plan",
|
"title": "Compare & select plan",
|
||||||
@ -744,48 +781,44 @@
|
|||||||
},
|
},
|
||||||
"freePlan": {
|
"freePlan": {
|
||||||
"title": "Free",
|
"title": "Free",
|
||||||
"description": "For organizing every corner of your work & life.",
|
"description": "For individuals and small groups to organize everything",
|
||||||
"price": "{}",
|
"price": "{}",
|
||||||
"priceInfo": "free forever"
|
"priceInfo": "free forever"
|
||||||
},
|
},
|
||||||
"proPlan": {
|
"proPlan": {
|
||||||
"title": "Professional",
|
"title": "Pro",
|
||||||
"description": "A place for small groups to plan & get organized.",
|
"description": "For small teams to manage projects and team knowledge",
|
||||||
"price": "{}/month",
|
"price": "{}",
|
||||||
"priceInfo": "billed annually"
|
"priceInfo": "/user per month billed annually\n\n{} billed monthly"
|
||||||
},
|
},
|
||||||
"planLabels": {
|
"planLabels": {
|
||||||
"itemOne": "Workspaces",
|
"itemOne": "Workspaces",
|
||||||
"itemTwo": "Members",
|
"itemTwo": "Members",
|
||||||
"itemThree": "Guests",
|
"itemThree": "Storage",
|
||||||
"tooltipThree": "Guests have read-only permission to the specifically shared content",
|
"itemFour": "Real-time collaboration",
|
||||||
"itemFour": "Guest collaborators",
|
"itemFive": "Mobile app",
|
||||||
"tooltipFour": "Guest collaborators are billed as one seat",
|
"itemSix": "AI Responses",
|
||||||
"itemFive": "Storage",
|
"tooltipSix": "Lifetime means the number of responses never reset",
|
||||||
"itemSix": "Real-time collaboration",
|
"itemSeven": "Custom namespace",
|
||||||
"itemSeven": "Mobile app",
|
"tooltipSeven": "Allows you to customize part of the URL for your workspace"
|
||||||
"itemEight": "AI Responses",
|
|
||||||
"tooltipEight": "Lifetime means the number of responses never reset"
|
|
||||||
},
|
},
|
||||||
"freeLabels": {
|
"freeLabels": {
|
||||||
"itemOne": "charged per workspace",
|
"itemOne": "charged per workspace",
|
||||||
"itemTwo": "3",
|
"itemTwo": "up to 3",
|
||||||
"itemThree": " ",
|
"itemThree": "5 GB",
|
||||||
"itemFour": "0",
|
"itemFour": "yes",
|
||||||
"itemFive": "5 GB",
|
"itemFive": "yes",
|
||||||
"itemSix": "yes",
|
"itemSix": "100 lifetime",
|
||||||
"itemSeven": "yes",
|
"itemSeven": ""
|
||||||
"itemEight": "1,000 lifetime"
|
|
||||||
},
|
},
|
||||||
"proLabels": {
|
"proLabels": {
|
||||||
"itemOne": "charged per workspace",
|
"itemOne": "charged per workspace",
|
||||||
"itemTwo": "up to 10",
|
"itemTwo": "up to 10",
|
||||||
"itemThree": " ",
|
"itemThree": "unlimited",
|
||||||
"itemFour": "10 guests billed as one seat",
|
"itemFour": "yes",
|
||||||
"itemFive": "unlimited",
|
"itemFive": "yes",
|
||||||
"itemSix": "yes",
|
"itemSix": "unlimited",
|
||||||
"itemSeven": "yes",
|
"itemSeven": ""
|
||||||
"itemEight": "10,000 monthly"
|
|
||||||
},
|
},
|
||||||
"paymentSuccess": {
|
"paymentSuccess": {
|
||||||
"title": "You are now on the {} plan!",
|
"title": "You are now on the {} plan!",
|
||||||
@ -793,7 +826,7 @@
|
|||||||
},
|
},
|
||||||
"downgradeDialog": {
|
"downgradeDialog": {
|
||||||
"title": "Are you sure you want to downgrade your plan?",
|
"title": "Are you sure you want to downgrade your plan?",
|
||||||
"description": "Downgrading your plan will revert you back to the Free plan. Members may lose access to workspaces and you may need to free up space to meet the storage limits of the Free plan.",
|
"description": "Downgrading your plan will revert you back to the Free plan. Members may lose access to this workspace and you may need to free up space to meet the storage limits of the Free plan.",
|
||||||
"downgradeLabel": "Downgrade plan"
|
"downgradeLabel": "Downgrade plan"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -808,7 +841,7 @@
|
|||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"open": "Open Settings",
|
"open": "Open Settings",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"logoutPrompt": "Are you sure to logout?",
|
"logoutPrompt": "Are you sure you want to logout?",
|
||||||
"selfEncryptionLogoutPrompt": "Are you sure you want to log out? Please ensure you have copied the encryption secret",
|
"selfEncryptionLogoutPrompt": "Are you sure you want to log out? Please ensure you have copied the encryption secret",
|
||||||
"syncSetting": "Sync Setting",
|
"syncSetting": "Sync Setting",
|
||||||
"cloudSettings": "Cloud Settings",
|
"cloudSettings": "Cloud Settings",
|
||||||
@ -958,7 +991,10 @@
|
|||||||
"one": "{} member",
|
"one": "{} member",
|
||||||
"other": "{} members"
|
"other": "{} members"
|
||||||
},
|
},
|
||||||
"memberLimitExceeded": "You've reached the maximum member limit allowed for your account. If you want to add more additional members to continue your work, please request on Github",
|
"memberLimitExceeded": "Member limit reached, to invite more members, please ",
|
||||||
|
"memberLimitExceededUpgrade": "upgrade",
|
||||||
|
"memberLimitExceededPro": "Member limit reached, if you require more members contact ",
|
||||||
|
"memberLimitExceededProContact": "support@appflowy.io",
|
||||||
"failedToAddMember": "Failed to add member",
|
"failedToAddMember": "Failed to add member",
|
||||||
"addMemberSuccess": "Member added successfully",
|
"addMemberSuccess": "Member added successfully",
|
||||||
"removeMember": "Remove Member",
|
"removeMember": "Remove Member",
|
||||||
@ -1012,7 +1048,7 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"tooltipSelectIcon": "Select icon",
|
"tooltipSelectIcon": "Select icon",
|
||||||
"selectAnIcon": "Select an icon",
|
"selectAnIcon": "Select an icon",
|
||||||
"pleaseInputYourOpenAIKey": "please input your OpenAI key",
|
"pleaseInputYourOpenAIKey": "please input your AI key",
|
||||||
"pleaseInputYourStabilityAIKey": "please input your Stability AI key",
|
"pleaseInputYourStabilityAIKey": "please input your Stability AI key",
|
||||||
"clickToLogout": "Click to logout the current user"
|
"clickToLogout": "Click to logout the current user"
|
||||||
},
|
},
|
||||||
@ -1327,23 +1363,23 @@
|
|||||||
"referencedGrid": "Referenced Grid",
|
"referencedGrid": "Referenced Grid",
|
||||||
"referencedCalendar": "Referenced Calendar",
|
"referencedCalendar": "Referenced Calendar",
|
||||||
"referencedDocument": "Referenced Document",
|
"referencedDocument": "Referenced Document",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Writer",
|
"autoGeneratorMenuItemName": "AI Writer",
|
||||||
"autoGeneratorTitleName": "OpenAI: Ask AI to write anything...",
|
"autoGeneratorTitleName": "AI: Ask AI to write anything...",
|
||||||
"autoGeneratorLearnMore": "Learn more",
|
"autoGeneratorLearnMore": "Learn more",
|
||||||
"autoGeneratorGenerate": "Generate",
|
"autoGeneratorGenerate": "Generate",
|
||||||
"autoGeneratorHintText": "Ask OpenAI ...",
|
"autoGeneratorHintText": "Ask AI ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Can't get OpenAI key",
|
"autoGeneratorCantGetOpenAIKey": "Can't get AI key",
|
||||||
"autoGeneratorRewrite": "Rewrite",
|
"autoGeneratorRewrite": "Rewrite",
|
||||||
"smartEdit": "AI Assistants",
|
"smartEdit": "AI Assistants",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Fix spelling",
|
"smartEditFixSpelling": "Fix spelling",
|
||||||
"warning": "⚠️ AI responses can be inaccurate or misleading.",
|
"warning": "⚠️ AI responses can be inaccurate or misleading.",
|
||||||
"smartEditSummarize": "Summarize",
|
"smartEditSummarize": "Summarize",
|
||||||
"smartEditImproveWriting": "Improve writing",
|
"smartEditImproveWriting": "Improve writing",
|
||||||
"smartEditMakeLonger": "Make longer",
|
"smartEditMakeLonger": "Make longer",
|
||||||
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
|
"smartEditCouldNotFetchResult": "Could not fetch result from AI",
|
||||||
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
"smartEditCouldNotFetchKey": "Could not fetch AI key",
|
||||||
"smartEditDisabled": "Connect OpenAI in Settings",
|
"smartEditDisabled": "Connect AI in Settings",
|
||||||
"appflowyAIEditDisabled": "Sign in to enable AI features",
|
"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",
|
||||||
@ -1473,8 +1509,8 @@
|
|||||||
"placeholder": "Enter image URL"
|
"placeholder": "Enter image URL"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Generate image from OpenAI",
|
"label": "Generate image from AI",
|
||||||
"placeholder": "Please input the prompt for OpenAI to generate image"
|
"placeholder": "Please input the prompt for AI to generate image"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Generate image from Stability AI",
|
"label": "Generate image from Stability AI",
|
||||||
@ -1496,7 +1532,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Search for an image",
|
"searchForAnImage": "Search for an image",
|
||||||
"pleaseInputYourOpenAIKey": "please input your OpenAI key in Settings page",
|
"pleaseInputYourOpenAIKey": "please input your AI key in Settings page",
|
||||||
"pleaseInputYourStabilityAIKey": "please input your Stability AI key in Settings page",
|
"pleaseInputYourStabilityAIKey": "please input your Stability AI key in Settings page",
|
||||||
"saveImageToGallery": "Save image",
|
"saveImageToGallery": "Save image",
|
||||||
"failedToAddImageToGallery": "Failed to add image to gallery",
|
"failedToAddImageToGallery": "Failed to add image to gallery",
|
||||||
|
@ -361,15 +361,6 @@
|
|||||||
"loginLabel": "Inicio de sesión",
|
"loginLabel": "Inicio de sesión",
|
||||||
"logoutLabel": "Cerrar sesión"
|
"logoutLabel": "Cerrar sesión"
|
||||||
},
|
},
|
||||||
"keys": {
|
|
||||||
"title": "Claves API de IA",
|
|
||||||
"openAILabel": "Clave API de OpenAI",
|
|
||||||
"openAITooltip": "La clave API de OpenAI para usar en los modelos de IA",
|
|
||||||
"openAIHint": "Ingresa tu clave API de OpenAI",
|
|
||||||
"stabilityAILabel": "Clave API de Stability",
|
|
||||||
"stabilityAITooltip": "La clave API de Stability que se utilizará en los modelos de IA",
|
|
||||||
"stabilityAIHint": "Ingresa tu clave API de Stability"
|
|
||||||
},
|
|
||||||
"description": "Personaliza tu perfil, administra la seguridad de la cuenta y las claves API de IA, o inicia sesión en tu cuenta."
|
"description": "Personaliza tu perfil, administra la seguridad de la cuenta y las claves API de IA, o inicia sesión en tu cuenta."
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
@ -577,7 +568,7 @@
|
|||||||
"email": "Correo electrónico",
|
"email": "Correo electrónico",
|
||||||
"tooltipSelectIcon": "Seleccionar icono",
|
"tooltipSelectIcon": "Seleccionar icono",
|
||||||
"selectAnIcon": "Seleccione un icono",
|
"selectAnIcon": "Seleccione un icono",
|
||||||
"pleaseInputYourOpenAIKey": "por favor ingrese su clave OpenAI",
|
"pleaseInputYourOpenAIKey": "por favor ingrese su clave AI",
|
||||||
"pleaseInputYourStabilityAIKey": "por favor ingrese su clave de estabilidad AI",
|
"pleaseInputYourStabilityAIKey": "por favor ingrese su clave de estabilidad AI",
|
||||||
"clickToLogout": "Haga clic para cerrar la sesión del usuario actual."
|
"clickToLogout": "Haga clic para cerrar la sesión del usuario actual."
|
||||||
},
|
},
|
||||||
@ -902,12 +893,12 @@
|
|||||||
"referencedGrid": "Cuadrícula referenciada",
|
"referencedGrid": "Cuadrícula referenciada",
|
||||||
"referencedCalendar": "Calendario referenciado",
|
"referencedCalendar": "Calendario referenciado",
|
||||||
"referencedDocument": "Documento referenciado",
|
"referencedDocument": "Documento referenciado",
|
||||||
"autoGeneratorMenuItemName": "Escritor de OpenAI",
|
"autoGeneratorMenuItemName": "Escritor de AI",
|
||||||
"autoGeneratorTitleName": "OpenAI: Pídele a AI que escriba cualquier cosa...",
|
"autoGeneratorTitleName": "AI: Pídele a AI que escriba cualquier cosa...",
|
||||||
"autoGeneratorLearnMore": "Aprende más",
|
"autoGeneratorLearnMore": "Aprende más",
|
||||||
"autoGeneratorGenerate": "Generar",
|
"autoGeneratorGenerate": "Generar",
|
||||||
"autoGeneratorHintText": "Pregúntale a OpenAI...",
|
"autoGeneratorHintText": "Pregúntale a AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "No puedo obtener la clave de OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "No puedo obtener la clave de AI",
|
||||||
"autoGeneratorRewrite": "Volver a escribir",
|
"autoGeneratorRewrite": "Volver a escribir",
|
||||||
"smartEdit": "Asistentes de IA",
|
"smartEdit": "Asistentes de IA",
|
||||||
"openAI": "IA abierta",
|
"openAI": "IA abierta",
|
||||||
@ -916,9 +907,9 @@
|
|||||||
"smartEditSummarize": "Resumir",
|
"smartEditSummarize": "Resumir",
|
||||||
"smartEditImproveWriting": "Mejorar la escritura",
|
"smartEditImproveWriting": "Mejorar la escritura",
|
||||||
"smartEditMakeLonger": "hacer más largo",
|
"smartEditMakeLonger": "hacer más largo",
|
||||||
"smartEditCouldNotFetchResult": "No se pudo obtener el resultado de OpenAI",
|
"smartEditCouldNotFetchResult": "No se pudo obtener el resultado de AI",
|
||||||
"smartEditCouldNotFetchKey": "No se pudo obtener la clave de OpenAI",
|
"smartEditCouldNotFetchKey": "No se pudo obtener la clave de AI",
|
||||||
"smartEditDisabled": "Conectar OpenAI en Configuración",
|
"smartEditDisabled": "Conectar AI en Configuración",
|
||||||
"discardResponse": "¿Quieres descartar las respuestas de IA?",
|
"discardResponse": "¿Quieres descartar las respuestas de IA?",
|
||||||
"createInlineMathEquation": "Crear ecuación",
|
"createInlineMathEquation": "Crear ecuación",
|
||||||
"fonts": "Tipo de letra",
|
"fonts": "Tipo de letra",
|
||||||
@ -1034,8 +1025,8 @@
|
|||||||
"placeholder": "Introduce la URL de la imagen"
|
"placeholder": "Introduce la URL de la imagen"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Generar imagen desde OpenAI",
|
"label": "Generar imagen desde AI",
|
||||||
"placeholder": "Ingrese el prompt para que OpenAI genere una imagen"
|
"placeholder": "Ingrese el prompt para que AI genere una imagen"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Generar imagen desde Stability AI",
|
"label": "Generar imagen desde Stability AI",
|
||||||
@ -1057,7 +1048,7 @@
|
|||||||
"label": "Desempaquetar"
|
"label": "Desempaquetar"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Buscar una imagen",
|
"searchForAnImage": "Buscar una imagen",
|
||||||
"pleaseInputYourOpenAIKey": "ingresa tu clave OpenAI en la página de Configuración",
|
"pleaseInputYourOpenAIKey": "ingresa tu clave AI en la página de Configuración",
|
||||||
"pleaseInputYourStabilityAIKey": "ingresa tu clave de Stability AI en la página de configuración",
|
"pleaseInputYourStabilityAIKey": "ingresa tu clave de Stability AI en la página de configuración",
|
||||||
"saveImageToGallery": "Guardar imagen",
|
"saveImageToGallery": "Guardar imagen",
|
||||||
"failedToAddImageToGallery": "No se pudo agregar la imagen a la galería",
|
"failedToAddImageToGallery": "No se pudo agregar la imagen a la galería",
|
||||||
|
@ -273,7 +273,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"name": "Izena",
|
"name": "Izena",
|
||||||
"selectAnIcon": "Hautatu ikono bat",
|
"selectAnIcon": "Hautatu ikono bat",
|
||||||
"pleaseInputYourOpenAIKey": "mesedez sartu zure OpenAI gakoa"
|
"pleaseInputYourOpenAIKey": "mesedez sartu zure AI gakoa"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"grid": {
|
"grid": {
|
||||||
@ -430,23 +430,23 @@
|
|||||||
"referencedBoard": "Erreferentziazko Batzordea",
|
"referencedBoard": "Erreferentziazko Batzordea",
|
||||||
"referencedGrid": "Erreferentziazko Sarea",
|
"referencedGrid": "Erreferentziazko Sarea",
|
||||||
"referencedCalendar": "Erreferentziazko Egutegia",
|
"referencedCalendar": "Erreferentziazko Egutegia",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Writer",
|
"autoGeneratorMenuItemName": "AI Writer",
|
||||||
"autoGeneratorTitleName": "OpenAI: Eskatu AIri edozer idazteko...",
|
"autoGeneratorTitleName": "AI: Eskatu AIri edozer idazteko...",
|
||||||
"autoGeneratorLearnMore": "Gehiago ikasi",
|
"autoGeneratorLearnMore": "Gehiago ikasi",
|
||||||
"autoGeneratorGenerate": "Sortu",
|
"autoGeneratorGenerate": "Sortu",
|
||||||
"autoGeneratorHintText": "Galdetu OpenAI...",
|
"autoGeneratorHintText": "Galdetu AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Ezin da lortu OpenAI gakoa",
|
"autoGeneratorCantGetOpenAIKey": "Ezin da lortu AI gakoa",
|
||||||
"autoGeneratorRewrite": "Berridatzi",
|
"autoGeneratorRewrite": "Berridatzi",
|
||||||
"smartEdit": "AI Laguntzaileak",
|
"smartEdit": "AI Laguntzaileak",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Ortografia konpondu",
|
"smartEditFixSpelling": "Ortografia konpondu",
|
||||||
"warning": "⚠️ AI erantzunak okerrak edo engainagarriak izan daitezke.",
|
"warning": "⚠️ AI erantzunak okerrak edo engainagarriak izan daitezke.",
|
||||||
"smartEditSummarize": "Laburtu",
|
"smartEditSummarize": "Laburtu",
|
||||||
"smartEditImproveWriting": "Hobetu idazkera",
|
"smartEditImproveWriting": "Hobetu idazkera",
|
||||||
"smartEditMakeLonger": "Luzatu",
|
"smartEditMakeLonger": "Luzatu",
|
||||||
"smartEditCouldNotFetchResult": "Ezin izan da emaitzarik eskuratu OpenAI-tik",
|
"smartEditCouldNotFetchResult": "Ezin izan da emaitzarik eskuratu AI-tik",
|
||||||
"smartEditCouldNotFetchKey": "Ezin izan da OpenAI gakoa eskuratu",
|
"smartEditCouldNotFetchKey": "Ezin izan da AI gakoa eskuratu",
|
||||||
"smartEditDisabled": "Konektatu OpenAI Ezarpenetan",
|
"smartEditDisabled": "Konektatu AI Ezarpenetan",
|
||||||
"discardResponse": "AI erantzunak baztertu nahi dituzu?",
|
"discardResponse": "AI erantzunak baztertu nahi dituzu?",
|
||||||
"createInlineMathEquation": "Sortu ekuazioa",
|
"createInlineMathEquation": "Sortu ekuazioa",
|
||||||
"toggleList": "Aldatu zerrenda",
|
"toggleList": "Aldatu zerrenda",
|
||||||
|
@ -296,7 +296,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"name": "نام",
|
"name": "نام",
|
||||||
"selectAnIcon": "انتخاب یک آیکون",
|
"selectAnIcon": "انتخاب یک آیکون",
|
||||||
"pleaseInputYourOpenAIKey": "لطفا کلید OpenAI خود را وارد کنید",
|
"pleaseInputYourOpenAIKey": "لطفا کلید AI خود را وارد کنید",
|
||||||
"clickToLogout": "برای خروج از کاربر فعلی کلیک کنید"
|
"clickToLogout": "برای خروج از کاربر فعلی کلیک کنید"
|
||||||
},
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
@ -465,23 +465,23 @@
|
|||||||
"referencedBoard": "بورد مرجع",
|
"referencedBoard": "بورد مرجع",
|
||||||
"referencedGrid": "شبکهنمایش مرجع",
|
"referencedGrid": "شبکهنمایش مرجع",
|
||||||
"referencedCalendar": "تقویم مرجع",
|
"referencedCalendar": "تقویم مرجع",
|
||||||
"autoGeneratorMenuItemName": "OpenAI نویسنده",
|
"autoGeneratorMenuItemName": "AI نویسنده",
|
||||||
"autoGeneratorTitleName": "از هوش مصنوعی بخواهید هر چیزی بنویسد...",
|
"autoGeneratorTitleName": "از هوش مصنوعی بخواهید هر چیزی بنویسد...",
|
||||||
"autoGeneratorLearnMore": "بیشتر بدانید",
|
"autoGeneratorLearnMore": "بیشتر بدانید",
|
||||||
"autoGeneratorGenerate": "بنویس",
|
"autoGeneratorGenerate": "بنویس",
|
||||||
"autoGeneratorHintText": "از OpenAI بپرسید ...",
|
"autoGeneratorHintText": "از AI بپرسید ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "کلید OpenAI را نمی توان دریافت کرد",
|
"autoGeneratorCantGetOpenAIKey": "کلید AI را نمی توان دریافت کرد",
|
||||||
"autoGeneratorRewrite": "بازنویس",
|
"autoGeneratorRewrite": "بازنویس",
|
||||||
"smartEdit": "دستیاران هوشمند",
|
"smartEdit": "دستیاران هوشمند",
|
||||||
"openAI": "OpenAI",
|
"openAI": "AI",
|
||||||
"smartEditFixSpelling": "اصلاح نگارش",
|
"smartEditFixSpelling": "اصلاح نگارش",
|
||||||
"warning": "⚠️ پاسخهای هوش مصنوعی میتوانند نادرست یا گمراهکننده باشند",
|
"warning": "⚠️ پاسخهای هوش مصنوعی میتوانند نادرست یا گمراهکننده باشند",
|
||||||
"smartEditSummarize": "خلاصهنویسی",
|
"smartEditSummarize": "خلاصهنویسی",
|
||||||
"smartEditImproveWriting": "بهبود نگارش",
|
"smartEditImproveWriting": "بهبود نگارش",
|
||||||
"smartEditMakeLonger": "به نوشته اضافه کن",
|
"smartEditMakeLonger": "به نوشته اضافه کن",
|
||||||
"smartEditCouldNotFetchResult": "نتیجهای از OpenAI گرفته نشد",
|
"smartEditCouldNotFetchResult": "نتیجهای از AI گرفته نشد",
|
||||||
"smartEditCouldNotFetchKey": "کلید OpenAI واکشی نشد",
|
"smartEditCouldNotFetchKey": "کلید AI واکشی نشد",
|
||||||
"smartEditDisabled": "به OpenAI در تنظیمات وصل شوید",
|
"smartEditDisabled": "به AI در تنظیمات وصل شوید",
|
||||||
"discardResponse": "آیا می خواهید پاسخ های هوش مصنوعی را حذف کنید؟",
|
"discardResponse": "آیا می خواهید پاسخ های هوش مصنوعی را حذف کنید؟",
|
||||||
"createInlineMathEquation": "ایجاد معادله",
|
"createInlineMathEquation": "ایجاد معادله",
|
||||||
"toggleList": "Toggle لیست",
|
"toggleList": "Toggle لیست",
|
||||||
|
@ -428,7 +428,7 @@
|
|||||||
"email": "Courriel",
|
"email": "Courriel",
|
||||||
"tooltipSelectIcon": "Sélectionner l'icône",
|
"tooltipSelectIcon": "Sélectionner l'icône",
|
||||||
"selectAnIcon": "Sélectionnez une icône",
|
"selectAnIcon": "Sélectionnez une icône",
|
||||||
"pleaseInputYourOpenAIKey": "Veuillez entrer votre clé OpenAI",
|
"pleaseInputYourOpenAIKey": "Veuillez entrer votre clé AI",
|
||||||
"pleaseInputYourStabilityAIKey": "Veuillez saisir votre clé de Stability AI",
|
"pleaseInputYourStabilityAIKey": "Veuillez saisir votre clé de Stability AI",
|
||||||
"clickToLogout": "Cliquez pour déconnecter l'utilisateur actuel"
|
"clickToLogout": "Cliquez pour déconnecter l'utilisateur actuel"
|
||||||
},
|
},
|
||||||
@ -701,23 +701,23 @@
|
|||||||
"referencedGrid": "Grille référencée",
|
"referencedGrid": "Grille référencée",
|
||||||
"referencedCalendar": "Calendrier référencé",
|
"referencedCalendar": "Calendrier référencé",
|
||||||
"referencedDocument": "Document référencé",
|
"referencedDocument": "Document référencé",
|
||||||
"autoGeneratorMenuItemName": "Rédacteur OpenAI",
|
"autoGeneratorMenuItemName": "Rédacteur AI",
|
||||||
"autoGeneratorTitleName": "OpenAI : Demandez à l'IA d'écrire quelque chose...",
|
"autoGeneratorTitleName": "AI : Demandez à l'IA d'écrire quelque chose...",
|
||||||
"autoGeneratorLearnMore": "Apprendre encore plus",
|
"autoGeneratorLearnMore": "Apprendre encore plus",
|
||||||
"autoGeneratorGenerate": "Générer",
|
"autoGeneratorGenerate": "Générer",
|
||||||
"autoGeneratorHintText": "Demandez à OpenAI...",
|
"autoGeneratorHintText": "Demandez à AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Impossible d'obtenir la clé OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Impossible d'obtenir la clé AI",
|
||||||
"autoGeneratorRewrite": "Réécrire",
|
"autoGeneratorRewrite": "Réécrire",
|
||||||
"smartEdit": "Assistants IA",
|
"smartEdit": "Assistants IA",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Corriger l'orthographe",
|
"smartEditFixSpelling": "Corriger l'orthographe",
|
||||||
"warning": "⚠️ Les réponses de l'IA peuvent être inexactes ou trompeuses.",
|
"warning": "⚠️ Les réponses de l'IA peuvent être inexactes ou trompeuses.",
|
||||||
"smartEditSummarize": "Résumer",
|
"smartEditSummarize": "Résumer",
|
||||||
"smartEditImproveWriting": "Améliorer l'écriture",
|
"smartEditImproveWriting": "Améliorer l'écriture",
|
||||||
"smartEditMakeLonger": "Rallonger",
|
"smartEditMakeLonger": "Rallonger",
|
||||||
"smartEditCouldNotFetchResult": "Impossible de récupérer le résultat d'OpenAI",
|
"smartEditCouldNotFetchResult": "Impossible de récupérer le résultat d'AI",
|
||||||
"smartEditCouldNotFetchKey": "Impossible de récupérer la clé OpenAI",
|
"smartEditCouldNotFetchKey": "Impossible de récupérer la clé AI",
|
||||||
"smartEditDisabled": "Connectez OpenAI dans les paramètres",
|
"smartEditDisabled": "Connectez AI dans les paramètres",
|
||||||
"discardResponse": "Voulez-vous supprimer les réponses de l'IA ?",
|
"discardResponse": "Voulez-vous supprimer les réponses de l'IA ?",
|
||||||
"createInlineMathEquation": "Créer une équation",
|
"createInlineMathEquation": "Créer une équation",
|
||||||
"fonts": "Polices",
|
"fonts": "Polices",
|
||||||
@ -824,8 +824,8 @@
|
|||||||
"placeholder": "Entrez l'URL de l'image"
|
"placeholder": "Entrez l'URL de l'image"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Générer une image à partir d'OpenAI",
|
"label": "Générer une image à partir d'AI",
|
||||||
"placeholder": "Veuillez saisir l'invite pour qu'OpenAI génère l'image"
|
"placeholder": "Veuillez saisir l'invite pour qu'AI génère l'image"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Générer une image à partir de Stability AI",
|
"label": "Générer une image à partir de Stability AI",
|
||||||
@ -846,7 +846,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Rechercher une image",
|
"searchForAnImage": "Rechercher une image",
|
||||||
"pleaseInputYourOpenAIKey": "veuillez saisir votre clé OpenAI dans la page Paramètres",
|
"pleaseInputYourOpenAIKey": "veuillez saisir votre clé AI dans la page Paramètres",
|
||||||
"pleaseInputYourStabilityAIKey": "veuillez saisir votre clé Stability AI dans la page Paramètres",
|
"pleaseInputYourStabilityAIKey": "veuillez saisir votre clé Stability AI dans la page Paramètres",
|
||||||
"saveImageToGallery": "Enregistrer l'image",
|
"saveImageToGallery": "Enregistrer l'image",
|
||||||
"failedToAddImageToGallery": "Échec de l'ajout d'une image à la galerie",
|
"failedToAddImageToGallery": "Échec de l'ajout d'une image à la galerie",
|
||||||
|
@ -618,7 +618,7 @@
|
|||||||
"email": "Courriel",
|
"email": "Courriel",
|
||||||
"tooltipSelectIcon": "Sélectionner l'icône",
|
"tooltipSelectIcon": "Sélectionner l'icône",
|
||||||
"selectAnIcon": "Sélectionnez une icône",
|
"selectAnIcon": "Sélectionnez une icône",
|
||||||
"pleaseInputYourOpenAIKey": "Veuillez entrer votre clé OpenAI",
|
"pleaseInputYourOpenAIKey": "Veuillez entrer votre clé AI",
|
||||||
"pleaseInputYourStabilityAIKey": "Veuillez saisir votre clé de Stability AI",
|
"pleaseInputYourStabilityAIKey": "Veuillez saisir votre clé de Stability AI",
|
||||||
"clickToLogout": "Cliquez pour déconnecter l'utilisateur actuel"
|
"clickToLogout": "Cliquez pour déconnecter l'utilisateur actuel"
|
||||||
},
|
},
|
||||||
@ -942,23 +942,23 @@
|
|||||||
"referencedGrid": "Grille référencée",
|
"referencedGrid": "Grille référencée",
|
||||||
"referencedCalendar": "Calendrier référencé",
|
"referencedCalendar": "Calendrier référencé",
|
||||||
"referencedDocument": "Document référencé",
|
"referencedDocument": "Document référencé",
|
||||||
"autoGeneratorMenuItemName": "Rédacteur OpenAI",
|
"autoGeneratorMenuItemName": "Rédacteur AI",
|
||||||
"autoGeneratorTitleName": "OpenAI : Demandez à l'IA d'écrire quelque chose...",
|
"autoGeneratorTitleName": "AI : Demandez à l'IA d'écrire quelque chose...",
|
||||||
"autoGeneratorLearnMore": "Apprendre encore plus",
|
"autoGeneratorLearnMore": "Apprendre encore plus",
|
||||||
"autoGeneratorGenerate": "Générer",
|
"autoGeneratorGenerate": "Générer",
|
||||||
"autoGeneratorHintText": "Demandez à OpenAI...",
|
"autoGeneratorHintText": "Demandez à AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Impossible d'obtenir la clé OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Impossible d'obtenir la clé AI",
|
||||||
"autoGeneratorRewrite": "Réécrire",
|
"autoGeneratorRewrite": "Réécrire",
|
||||||
"smartEdit": "Assistants IA",
|
"smartEdit": "Assistants IA",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Corriger l'orthographe",
|
"smartEditFixSpelling": "Corriger l'orthographe",
|
||||||
"warning": "⚠️ Les réponses de l'IA peuvent être inexactes ou trompeuses.",
|
"warning": "⚠️ Les réponses de l'IA peuvent être inexactes ou trompeuses.",
|
||||||
"smartEditSummarize": "Résumer",
|
"smartEditSummarize": "Résumer",
|
||||||
"smartEditImproveWriting": "Améliorer l'écriture",
|
"smartEditImproveWriting": "Améliorer l'écriture",
|
||||||
"smartEditMakeLonger": "Rallonger",
|
"smartEditMakeLonger": "Rallonger",
|
||||||
"smartEditCouldNotFetchResult": "Impossible de récupérer le résultat d'OpenAI",
|
"smartEditCouldNotFetchResult": "Impossible de récupérer le résultat d'AI",
|
||||||
"smartEditCouldNotFetchKey": "Impossible de récupérer la clé OpenAI",
|
"smartEditCouldNotFetchKey": "Impossible de récupérer la clé AI",
|
||||||
"smartEditDisabled": "Connectez OpenAI dans les paramètres",
|
"smartEditDisabled": "Connectez AI dans les paramètres",
|
||||||
"discardResponse": "Voulez-vous supprimer les réponses de l'IA ?",
|
"discardResponse": "Voulez-vous supprimer les réponses de l'IA ?",
|
||||||
"createInlineMathEquation": "Créer une équation",
|
"createInlineMathEquation": "Créer une équation",
|
||||||
"fonts": "Polices",
|
"fonts": "Polices",
|
||||||
@ -1073,8 +1073,8 @@
|
|||||||
"placeholder": "Entrez l'URL de l'image"
|
"placeholder": "Entrez l'URL de l'image"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Générer une image à partir d'OpenAI",
|
"label": "Générer une image à partir d'AI",
|
||||||
"placeholder": "Veuillez saisir l'invite pour qu'OpenAI génère l'image"
|
"placeholder": "Veuillez saisir l'invite pour qu'AI génère l'image"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Générer une image à partir de Stability AI",
|
"label": "Générer une image à partir de Stability AI",
|
||||||
@ -1096,7 +1096,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Rechercher une image",
|
"searchForAnImage": "Rechercher une image",
|
||||||
"pleaseInputYourOpenAIKey": "veuillez saisir votre clé OpenAI dans la page Paramètres",
|
"pleaseInputYourOpenAIKey": "veuillez saisir votre clé AI dans la page Paramètres",
|
||||||
"pleaseInputYourStabilityAIKey": "veuillez saisir votre clé Stability AI dans la page Paramètres",
|
"pleaseInputYourStabilityAIKey": "veuillez saisir votre clé Stability AI dans la page Paramètres",
|
||||||
"saveImageToGallery": "Enregistrer l'image",
|
"saveImageToGallery": "Enregistrer l'image",
|
||||||
"failedToAddImageToGallery": "Échec de l'ajout d'une image à la galerie",
|
"failedToAddImageToGallery": "Échec de l'ajout d'une image à la galerie",
|
||||||
|
@ -333,7 +333,7 @@
|
|||||||
"email": "ईमेल",
|
"email": "ईमेल",
|
||||||
"tooltipSelectIcon": "आइकन चुनें",
|
"tooltipSelectIcon": "आइकन चुनें",
|
||||||
"selectAnIcon": "एक आइकन चुनें",
|
"selectAnIcon": "एक आइकन चुनें",
|
||||||
"pleaseInputYourOpenAIKey": "कृपया अपनी OpenAI key इनपुट करें",
|
"pleaseInputYourOpenAIKey": "कृपया अपनी AI key इनपुट करें",
|
||||||
"clickToLogout": "वर्तमान उपयोगकर्ता को लॉगआउट करने के लिए क्लिक करें"
|
"clickToLogout": "वर्तमान उपयोगकर्ता को लॉगआउट करने के लिए क्लिक करें"
|
||||||
},
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
@ -515,23 +515,23 @@
|
|||||||
"referencedBoard": "रेफेरेंस बोर्ड",
|
"referencedBoard": "रेफेरेंस बोर्ड",
|
||||||
"referencedGrid": "रेफेरेंस ग्रिड",
|
"referencedGrid": "रेफेरेंस ग्रिड",
|
||||||
"referencedCalendar": "रेफेरेंस कैलेंडर",
|
"referencedCalendar": "रेफेरेंस कैलेंडर",
|
||||||
"autoGeneratorMenuItemName": "OpenAI लेखक",
|
"autoGeneratorMenuItemName": "AI लेखक",
|
||||||
"autoGeneratorTitleName": "OpenAI: AI को कुछ भी लिखने के लिए कहें...",
|
"autoGeneratorTitleName": "AI: AI को कुछ भी लिखने के लिए कहें...",
|
||||||
"autoGeneratorLearnMore": "और जानें",
|
"autoGeneratorLearnMore": "और जानें",
|
||||||
"autoGeneratorGenerate": "उत्पन्न करें",
|
"autoGeneratorGenerate": "उत्पन्न करें",
|
||||||
"autoGeneratorHintText": "OpenAI से पूछें...",
|
"autoGeneratorHintText": "AI से पूछें...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "OpenAI key नहीं मिल सकी",
|
"autoGeneratorCantGetOpenAIKey": "AI key नहीं मिल सकी",
|
||||||
"autoGeneratorRewrite": "पुनः लिखें",
|
"autoGeneratorRewrite": "पुनः लिखें",
|
||||||
"smartEdit": "AI सहायक",
|
"smartEdit": "AI सहायक",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "वर्तनी ठीक करें",
|
"smartEditFixSpelling": "वर्तनी ठीक करें",
|
||||||
"warning": "⚠️ AI प्रतिक्रियाएँ गलत या भ्रामक हो सकती हैं।",
|
"warning": "⚠️ AI प्रतिक्रियाएँ गलत या भ्रामक हो सकती हैं।",
|
||||||
"smartEditSummarize": "सारांश",
|
"smartEditSummarize": "सारांश",
|
||||||
"smartEditImproveWriting": "लेख में सुधार करें",
|
"smartEditImproveWriting": "लेख में सुधार करें",
|
||||||
"smartEditMakeLonger": "लंबा बनाएं",
|
"smartEditMakeLonger": "लंबा बनाएं",
|
||||||
"smartEditCouldNotFetchResult": "OpenAI से परिणाम प्राप्त नहीं किया जा सका",
|
"smartEditCouldNotFetchResult": "AI से परिणाम प्राप्त नहीं किया जा सका",
|
||||||
"smartEditCouldNotFetchKey": "OpenAI key नहीं लायी जा सकी",
|
"smartEditCouldNotFetchKey": "AI key नहीं लायी जा सकी",
|
||||||
"smartEditDisabled": "सेटिंग्स में OpenAI कनेक्ट करें",
|
"smartEditDisabled": "सेटिंग्स में AI कनेक्ट करें",
|
||||||
"discardResponse": "क्या आप AI प्रतिक्रियाओं को छोड़ना चाहते हैं?",
|
"discardResponse": "क्या आप AI प्रतिक्रियाओं को छोड़ना चाहते हैं?",
|
||||||
"createInlineMathEquation": "समीकरण बनाएं",
|
"createInlineMathEquation": "समीकरण बनाएं",
|
||||||
"toggleList": "सूची टॉगल करें",
|
"toggleList": "सूची टॉगल करें",
|
||||||
|
@ -277,7 +277,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"name": "Név",
|
"name": "Név",
|
||||||
"selectAnIcon": "Válasszon ki egy ikont",
|
"selectAnIcon": "Válasszon ki egy ikont",
|
||||||
"pleaseInputYourOpenAIKey": "kérjük, adja meg OpenAI kulcsát"
|
"pleaseInputYourOpenAIKey": "kérjük, adja meg AI kulcsát"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"grid": {
|
"grid": {
|
||||||
@ -432,23 +432,23 @@
|
|||||||
"referencedBoard": "Hivatkozott feladat tábla",
|
"referencedBoard": "Hivatkozott feladat tábla",
|
||||||
"referencedGrid": "Hivatkozott táblázat",
|
"referencedGrid": "Hivatkozott táblázat",
|
||||||
"referencedCalendar": "Hivatkozott naptár",
|
"referencedCalendar": "Hivatkozott naptár",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Writer",
|
"autoGeneratorMenuItemName": "AI Writer",
|
||||||
"autoGeneratorTitleName": "OpenAI: Kérd meg az AI-t, hogy írjon bármit...",
|
"autoGeneratorTitleName": "AI: Kérd meg az AI-t, hogy írjon bármit...",
|
||||||
"autoGeneratorLearnMore": "Tudj meg többet",
|
"autoGeneratorLearnMore": "Tudj meg többet",
|
||||||
"autoGeneratorGenerate": "generál",
|
"autoGeneratorGenerate": "generál",
|
||||||
"autoGeneratorHintText": "Kérdezd meg az OpenAI-t...",
|
"autoGeneratorHintText": "Kérdezd meg az AI-t...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Nem lehet beszerezni az OpenAI kulcsot",
|
"autoGeneratorCantGetOpenAIKey": "Nem lehet beszerezni az AI kulcsot",
|
||||||
"autoGeneratorRewrite": "Újraírni",
|
"autoGeneratorRewrite": "Újraírni",
|
||||||
"smartEdit": "AI asszisztensek",
|
"smartEdit": "AI asszisztensek",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Helyesírás javítása",
|
"smartEditFixSpelling": "Helyesírás javítása",
|
||||||
"warning": "⚠️ Az AI-válaszok pontatlanok vagy félrevezetőek lehetnek.",
|
"warning": "⚠️ Az AI-válaszok pontatlanok vagy félrevezetőek lehetnek.",
|
||||||
"smartEditSummarize": "Összesít",
|
"smartEditSummarize": "Összesít",
|
||||||
"smartEditImproveWriting": "Az írás javítása",
|
"smartEditImproveWriting": "Az írás javítása",
|
||||||
"smartEditMakeLonger": "Hosszabb legyen",
|
"smartEditMakeLonger": "Hosszabb legyen",
|
||||||
"smartEditCouldNotFetchResult": "Nem sikerült lekérni az eredményt az OpenAI-ból",
|
"smartEditCouldNotFetchResult": "Nem sikerült lekérni az eredményt az AI-ból",
|
||||||
"smartEditCouldNotFetchKey": "Nem sikerült lekérni az OpenAI kulcsot",
|
"smartEditCouldNotFetchKey": "Nem sikerült lekérni az AI kulcsot",
|
||||||
"smartEditDisabled": "Csatlakoztassa az OpenAI-t a Beállításokban",
|
"smartEditDisabled": "Csatlakoztassa az AI-t a Beállításokban",
|
||||||
"discardResponse": "El szeretné vetni az AI-válaszokat?",
|
"discardResponse": "El szeretné vetni az AI-válaszokat?",
|
||||||
"createInlineMathEquation": "Hozzon létre egyenletet",
|
"createInlineMathEquation": "Hozzon létre egyenletet",
|
||||||
"toggleList": "Lista váltása",
|
"toggleList": "Lista váltása",
|
||||||
|
@ -376,7 +376,7 @@
|
|||||||
"email": "Surel",
|
"email": "Surel",
|
||||||
"tooltipSelectIcon": "Pilih ikon",
|
"tooltipSelectIcon": "Pilih ikon",
|
||||||
"selectAnIcon": "Pilih ikon",
|
"selectAnIcon": "Pilih ikon",
|
||||||
"pleaseInputYourOpenAIKey": "silakan masukkan kunci OpenAI Anda",
|
"pleaseInputYourOpenAIKey": "silakan masukkan kunci AI Anda",
|
||||||
"pleaseInputYourStabilityAIKey": "Masukkan kunci Stability AI anda",
|
"pleaseInputYourStabilityAIKey": "Masukkan kunci Stability AI anda",
|
||||||
"clickToLogout": "Klik untuk keluar dari pengguna saat ini"
|
"clickToLogout": "Klik untuk keluar dari pengguna saat ini"
|
||||||
},
|
},
|
||||||
@ -589,23 +589,23 @@
|
|||||||
"referencedBoard": "Papan Referensi",
|
"referencedBoard": "Papan Referensi",
|
||||||
"referencedGrid": "Kisi yang Direferensikan",
|
"referencedGrid": "Kisi yang Direferensikan",
|
||||||
"referencedCalendar": "Kalender Referensi",
|
"referencedCalendar": "Kalender Referensi",
|
||||||
"autoGeneratorMenuItemName": "Penulis OpenAI",
|
"autoGeneratorMenuItemName": "Penulis AI",
|
||||||
"autoGeneratorTitleName": "OpenAI: Minta AI untuk menulis apa saja...",
|
"autoGeneratorTitleName": "AI: Minta AI untuk menulis apa saja...",
|
||||||
"autoGeneratorLearnMore": "Belajarlah lagi",
|
"autoGeneratorLearnMore": "Belajarlah lagi",
|
||||||
"autoGeneratorGenerate": "Menghasilkan",
|
"autoGeneratorGenerate": "Menghasilkan",
|
||||||
"autoGeneratorHintText": "Tanya OpenAI...",
|
"autoGeneratorHintText": "Tanya AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Tidak bisa mendapatkan kunci OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Tidak bisa mendapatkan kunci AI",
|
||||||
"autoGeneratorRewrite": "Menulis kembali",
|
"autoGeneratorRewrite": "Menulis kembali",
|
||||||
"smartEdit": "Asisten AI",
|
"smartEdit": "Asisten AI",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Perbaiki ejaan",
|
"smartEditFixSpelling": "Perbaiki ejaan",
|
||||||
"warning": "⚠️ Respons AI bisa jadi tidak akurat atau menyesatkan.",
|
"warning": "⚠️ Respons AI bisa jadi tidak akurat atau menyesatkan.",
|
||||||
"smartEditSummarize": "Meringkaskan",
|
"smartEditSummarize": "Meringkaskan",
|
||||||
"smartEditImproveWriting": "Perbaiki tulisan",
|
"smartEditImproveWriting": "Perbaiki tulisan",
|
||||||
"smartEditMakeLonger": "Buat lebih lama",
|
"smartEditMakeLonger": "Buat lebih lama",
|
||||||
"smartEditCouldNotFetchResult": "Tidak dapat mengambil hasil dari OpenAI",
|
"smartEditCouldNotFetchResult": "Tidak dapat mengambil hasil dari AI",
|
||||||
"smartEditCouldNotFetchKey": "Tidak dapat mengambil kunci OpenAI",
|
"smartEditCouldNotFetchKey": "Tidak dapat mengambil kunci AI",
|
||||||
"smartEditDisabled": "Hubungkan OpenAI di Pengaturan",
|
"smartEditDisabled": "Hubungkan AI di Pengaturan",
|
||||||
"discardResponse": "Apakah Anda ingin membuang respons AI?",
|
"discardResponse": "Apakah Anda ingin membuang respons AI?",
|
||||||
"createInlineMathEquation": "Buat persamaan",
|
"createInlineMathEquation": "Buat persamaan",
|
||||||
"toggleList": "Beralih Daftar",
|
"toggleList": "Beralih Daftar",
|
||||||
@ -690,8 +690,8 @@
|
|||||||
"placeholder": "Masukkan URL gambar"
|
"placeholder": "Masukkan URL gambar"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Buat gambar dari OpenAI",
|
"label": "Buat gambar dari AI",
|
||||||
"placeholder": "Masukkan perintah agar OpenAI menghasilkan gambar"
|
"placeholder": "Masukkan perintah agar AI menghasilkan gambar"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Buat gambar dari Stability AI",
|
"label": "Buat gambar dari Stability AI",
|
||||||
@ -709,7 +709,7 @@
|
|||||||
"placeholder": "Tempel atau ketik tautan gambar"
|
"placeholder": "Tempel atau ketik tautan gambar"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Mencari gambar",
|
"searchForAnImage": "Mencari gambar",
|
||||||
"pleaseInputYourOpenAIKey": "masukkan kunci OpenAI Anda di halaman Pengaturan",
|
"pleaseInputYourOpenAIKey": "masukkan kunci AI Anda di halaman Pengaturan",
|
||||||
"pleaseInputYourStabilityAIKey": "masukkan kunci AI Stabilitas Anda di halaman Pengaturan"
|
"pleaseInputYourStabilityAIKey": "masukkan kunci AI Stabilitas Anda di halaman Pengaturan"
|
||||||
},
|
},
|
||||||
"codeBlock": {
|
"codeBlock": {
|
||||||
|
@ -427,7 +427,7 @@
|
|||||||
"email": "E-mail",
|
"email": "E-mail",
|
||||||
"tooltipSelectIcon": "Seleziona l'icona",
|
"tooltipSelectIcon": "Seleziona l'icona",
|
||||||
"selectAnIcon": "Seleziona un'icona",
|
"selectAnIcon": "Seleziona un'icona",
|
||||||
"pleaseInputYourOpenAIKey": "inserisci la tua chiave OpenAI",
|
"pleaseInputYourOpenAIKey": "inserisci la tua chiave AI",
|
||||||
"pleaseInputYourStabilityAIKey": "per favore inserisci la tua chiave Stability AI",
|
"pleaseInputYourStabilityAIKey": "per favore inserisci la tua chiave Stability AI",
|
||||||
"clickToLogout": "Fare clic per disconnettere l'utente corrente"
|
"clickToLogout": "Fare clic per disconnettere l'utente corrente"
|
||||||
},
|
},
|
||||||
@ -706,23 +706,23 @@
|
|||||||
"referencedGrid": "Griglia di riferimento",
|
"referencedGrid": "Griglia di riferimento",
|
||||||
"referencedCalendar": "Calendario referenziato",
|
"referencedCalendar": "Calendario referenziato",
|
||||||
"referencedDocument": "Documento riferito",
|
"referencedDocument": "Documento riferito",
|
||||||
"autoGeneratorMenuItemName": "Scrittore OpenAI",
|
"autoGeneratorMenuItemName": "Scrittore AI",
|
||||||
"autoGeneratorTitleName": "OpenAI: chiedi all'AI di scrivere qualsiasi cosa...",
|
"autoGeneratorTitleName": "AI: chiedi all'AI di scrivere qualsiasi cosa...",
|
||||||
"autoGeneratorLearnMore": "Saperne di più",
|
"autoGeneratorLearnMore": "Saperne di più",
|
||||||
"autoGeneratorGenerate": "creare",
|
"autoGeneratorGenerate": "creare",
|
||||||
"autoGeneratorHintText": "Chiedi a OpenAI...",
|
"autoGeneratorHintText": "Chiedi a AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Impossibile ottenere la chiave OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Impossibile ottenere la chiave AI",
|
||||||
"autoGeneratorRewrite": "Riscrivere",
|
"autoGeneratorRewrite": "Riscrivere",
|
||||||
"smartEdit": "Assistenti AI",
|
"smartEdit": "Assistenti AI",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Correggi l'ortografia",
|
"smartEditFixSpelling": "Correggi l'ortografia",
|
||||||
"warning": "⚠️ Le risposte AI possono essere imprecise o fuorvianti.",
|
"warning": "⚠️ Le risposte AI possono essere imprecise o fuorvianti.",
|
||||||
"smartEditSummarize": "Riassumere",
|
"smartEditSummarize": "Riassumere",
|
||||||
"smartEditImproveWriting": "Migliora la scrittura",
|
"smartEditImproveWriting": "Migliora la scrittura",
|
||||||
"smartEditMakeLonger": "Rendi più lungo",
|
"smartEditMakeLonger": "Rendi più lungo",
|
||||||
"smartEditCouldNotFetchResult": "Impossibile recuperare il risultato da OpenAI",
|
"smartEditCouldNotFetchResult": "Impossibile recuperare il risultato da AI",
|
||||||
"smartEditCouldNotFetchKey": "Impossibile recuperare la chiave OpenAI",
|
"smartEditCouldNotFetchKey": "Impossibile recuperare la chiave AI",
|
||||||
"smartEditDisabled": "Connetti OpenAI in Impostazioni",
|
"smartEditDisabled": "Connetti AI in Impostazioni",
|
||||||
"discardResponse": "Vuoi scartare le risposte AI?",
|
"discardResponse": "Vuoi scartare le risposte AI?",
|
||||||
"createInlineMathEquation": "Crea un'equazione",
|
"createInlineMathEquation": "Crea un'equazione",
|
||||||
"fonts": "Caratteri",
|
"fonts": "Caratteri",
|
||||||
@ -831,8 +831,8 @@
|
|||||||
"placeholder": "Inserisci l'URL dell'immagine"
|
"placeholder": "Inserisci l'URL dell'immagine"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Genera immagine da OpenAI",
|
"label": "Genera immagine da AI",
|
||||||
"placeholder": "Inserisci la richiesta affinché OpenAI generi l'immagine"
|
"placeholder": "Inserisci la richiesta affinché AI generi l'immagine"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Genera immagine da Stability AI",
|
"label": "Genera immagine da Stability AI",
|
||||||
@ -853,7 +853,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Cerca un'immagine",
|
"searchForAnImage": "Cerca un'immagine",
|
||||||
"pleaseInputYourOpenAIKey": "inserisci la tua chiave OpenAI nella pagina Impostazioni",
|
"pleaseInputYourOpenAIKey": "inserisci la tua chiave AI nella pagina Impostazioni",
|
||||||
"pleaseInputYourStabilityAIKey": "inserisci la chiave Stability AI nella pagina Impostazioni",
|
"pleaseInputYourStabilityAIKey": "inserisci la chiave Stability AI nella pagina Impostazioni",
|
||||||
"saveImageToGallery": "Salva immagine",
|
"saveImageToGallery": "Salva immagine",
|
||||||
"failedToAddImageToGallery": "Impossibile aggiungere l'immagine alla galleria",
|
"failedToAddImageToGallery": "Impossibile aggiungere l'immagine alla galleria",
|
||||||
|
@ -343,7 +343,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"name": "名前",
|
"name": "名前",
|
||||||
"selectAnIcon": "アイコンを選択してください",
|
"selectAnIcon": "アイコンを選択してください",
|
||||||
"pleaseInputYourOpenAIKey": "OpenAI キーを入力してください"
|
"pleaseInputYourOpenAIKey": "AI キーを入力してください"
|
||||||
},
|
},
|
||||||
"mobile": {
|
"mobile": {
|
||||||
"username": "ユーザー名",
|
"username": "ユーザー名",
|
||||||
@ -519,23 +519,23 @@
|
|||||||
"referencedBoard": "参照ボード",
|
"referencedBoard": "参照ボード",
|
||||||
"referencedGrid": "参照されるグリッド",
|
"referencedGrid": "参照されるグリッド",
|
||||||
"referencedCalendar": "参照カレンダー",
|
"referencedCalendar": "参照カレンダー",
|
||||||
"autoGeneratorMenuItemName": "OpenAI ライター",
|
"autoGeneratorMenuItemName": "AI ライター",
|
||||||
"autoGeneratorTitleName": "OpenAI: AI に何でも書いてもらいます...",
|
"autoGeneratorTitleName": "AI: AI に何でも書いてもらいます...",
|
||||||
"autoGeneratorLearnMore": "もっと詳しく知る",
|
"autoGeneratorLearnMore": "もっと詳しく知る",
|
||||||
"autoGeneratorGenerate": "生成",
|
"autoGeneratorGenerate": "生成",
|
||||||
"autoGeneratorHintText": "OpenAIに質問してください...",
|
"autoGeneratorHintText": "OpenAIに質問してください...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "OpenAI キーを取得できません",
|
"autoGeneratorCantGetOpenAIKey": "AI キーを取得できません",
|
||||||
"autoGeneratorRewrite": "リライト",
|
"autoGeneratorRewrite": "リライト",
|
||||||
"smartEdit": "AIアシスタント",
|
"smartEdit": "AIアシスタント",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "スペルを修正",
|
"smartEditFixSpelling": "スペルを修正",
|
||||||
"warning": "⚠️ AI の応答は不正確または誤解を招く可能性があります。",
|
"warning": "⚠️ AI の応答は不正確または誤解を招く可能性があります。",
|
||||||
"smartEditSummarize": "要約する",
|
"smartEditSummarize": "要約する",
|
||||||
"smartEditImproveWriting": "ライティングを改善する",
|
"smartEditImproveWriting": "ライティングを改善する",
|
||||||
"smartEditMakeLonger": "もっと長くする",
|
"smartEditMakeLonger": "もっと長くする",
|
||||||
"smartEditCouldNotFetchResult": "OpenAIから結果を取得できませんでした",
|
"smartEditCouldNotFetchResult": "OpenAIから結果を取得できませんでした",
|
||||||
"smartEditCouldNotFetchKey": "OpenAI キーを取得できませんでした",
|
"smartEditCouldNotFetchKey": "AI キーを取得できませんでした",
|
||||||
"smartEditDisabled": "設定で OpenAI に接続する",
|
"smartEditDisabled": "設定で AI に接続する",
|
||||||
"discardResponse": "AI の応答を破棄しますか?",
|
"discardResponse": "AI の応答を破棄しますか?",
|
||||||
"createInlineMathEquation": "方程式の作成",
|
"createInlineMathEquation": "方程式の作成",
|
||||||
"toggleList": "リストの切り替え",
|
"toggleList": "リストの切り替え",
|
||||||
|
@ -275,7 +275,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"name": "이름",
|
"name": "이름",
|
||||||
"selectAnIcon": "아이콘을 선택하세요",
|
"selectAnIcon": "아이콘을 선택하세요",
|
||||||
"pleaseInputYourOpenAIKey": "OpenAI 키를 입력하십시오"
|
"pleaseInputYourOpenAIKey": "AI 키를 입력하십시오"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"grid": {
|
"grid": {
|
||||||
@ -431,23 +431,23 @@
|
|||||||
"referencedBoard": "참조 보드",
|
"referencedBoard": "참조 보드",
|
||||||
"referencedGrid": "참조된 그리드",
|
"referencedGrid": "참조된 그리드",
|
||||||
"referencedCalendar": "참조된 달력",
|
"referencedCalendar": "참조된 달력",
|
||||||
"autoGeneratorMenuItemName": "OpenAI 작성자",
|
"autoGeneratorMenuItemName": "AI 작성자",
|
||||||
"autoGeneratorTitleName": "OpenAI: AI에게 무엇이든 쓰라고 요청하세요...",
|
"autoGeneratorTitleName": "AI: AI에게 무엇이든 쓰라고 요청하세요...",
|
||||||
"autoGeneratorLearnMore": "더 알아보기",
|
"autoGeneratorLearnMore": "더 알아보기",
|
||||||
"autoGeneratorGenerate": "생성하다",
|
"autoGeneratorGenerate": "생성하다",
|
||||||
"autoGeneratorHintText": "OpenAI에게 물어보세요 ...",
|
"autoGeneratorHintText": "OpenAI에게 물어보세요 ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "OpenAI 키를 가져올 수 없습니다.",
|
"autoGeneratorCantGetOpenAIKey": "AI 키를 가져올 수 없습니다.",
|
||||||
"autoGeneratorRewrite": "고쳐 쓰기",
|
"autoGeneratorRewrite": "고쳐 쓰기",
|
||||||
"smartEdit": "AI 어시스턴트",
|
"smartEdit": "AI 어시스턴트",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "맞춤법 수정",
|
"smartEditFixSpelling": "맞춤법 수정",
|
||||||
"warning": "⚠️ AI 응답은 부정확하거나 오해의 소지가 있을 수 있습니다.",
|
"warning": "⚠️ AI 응답은 부정확하거나 오해의 소지가 있을 수 있습니다.",
|
||||||
"smartEditSummarize": "요약하다",
|
"smartEditSummarize": "요약하다",
|
||||||
"smartEditImproveWriting": "쓰기 향상",
|
"smartEditImproveWriting": "쓰기 향상",
|
||||||
"smartEditMakeLonger": "더 길게",
|
"smartEditMakeLonger": "더 길게",
|
||||||
"smartEditCouldNotFetchResult": "OpenAI에서 결과를 가져올 수 없습니다.",
|
"smartEditCouldNotFetchResult": "OpenAI에서 결과를 가져올 수 없습니다.",
|
||||||
"smartEditCouldNotFetchKey": "OpenAI 키를 가져올 수 없습니다.",
|
"smartEditCouldNotFetchKey": "AI 키를 가져올 수 없습니다.",
|
||||||
"smartEditDisabled": "설정에서 OpenAI 연결",
|
"smartEditDisabled": "설정에서 AI 연결",
|
||||||
"discardResponse": "AI 응답을 삭제하시겠습니까?",
|
"discardResponse": "AI 응답을 삭제하시겠습니까?",
|
||||||
"createInlineMathEquation": "방정식 만들기",
|
"createInlineMathEquation": "방정식 만들기",
|
||||||
"toggleList": "토글 목록",
|
"toggleList": "토글 목록",
|
||||||
|
@ -446,7 +446,7 @@
|
|||||||
"email": "E-mail",
|
"email": "E-mail",
|
||||||
"tooltipSelectIcon": "Wybierz ikonę",
|
"tooltipSelectIcon": "Wybierz ikonę",
|
||||||
"selectAnIcon": "Wybierz ikonę",
|
"selectAnIcon": "Wybierz ikonę",
|
||||||
"pleaseInputYourOpenAIKey": "wprowadź swój klucz OpenAI",
|
"pleaseInputYourOpenAIKey": "wprowadź swój klucz AI",
|
||||||
"pleaseInputYourStabilityAIKey": "wprowadź swój klucz Stability AI",
|
"pleaseInputYourStabilityAIKey": "wprowadź swój klucz Stability AI",
|
||||||
"clickToLogout": "Kliknij, aby wylogować bieżącego użytkownika"
|
"clickToLogout": "Kliknij, aby wylogować bieżącego użytkownika"
|
||||||
},
|
},
|
||||||
@ -667,23 +667,23 @@
|
|||||||
"referencedGrid": "Siatka referencyjna",
|
"referencedGrid": "Siatka referencyjna",
|
||||||
"referencedCalendar": "Kalendarz referencyjny",
|
"referencedCalendar": "Kalendarz referencyjny",
|
||||||
"referencedDocument": "Dokument referencyjny",
|
"referencedDocument": "Dokument referencyjny",
|
||||||
"autoGeneratorMenuItemName": "Pisarz OpenAI",
|
"autoGeneratorMenuItemName": "Pisarz AI",
|
||||||
"autoGeneratorTitleName": "OpenAI: Poproś AI o napisanie czegokolwiek...",
|
"autoGeneratorTitleName": "AI: Poproś AI o napisanie czegokolwiek...",
|
||||||
"autoGeneratorLearnMore": "Dowiedz się więcej",
|
"autoGeneratorLearnMore": "Dowiedz się więcej",
|
||||||
"autoGeneratorGenerate": "Generuj",
|
"autoGeneratorGenerate": "Generuj",
|
||||||
"autoGeneratorHintText": "Zapytaj OpenAI...",
|
"autoGeneratorHintText": "Zapytaj AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Nie można uzyskać klucza OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Nie można uzyskać klucza AI",
|
||||||
"autoGeneratorRewrite": "Przepisz",
|
"autoGeneratorRewrite": "Przepisz",
|
||||||
"smartEdit": "Asystenci AI",
|
"smartEdit": "Asystenci AI",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Popraw pisownię",
|
"smartEditFixSpelling": "Popraw pisownię",
|
||||||
"warning": "⚠️ Odpowiedzi AI mogą być niedokładne lub mylące.",
|
"warning": "⚠️ Odpowiedzi AI mogą być niedokładne lub mylące.",
|
||||||
"smartEditSummarize": "Podsumuj",
|
"smartEditSummarize": "Podsumuj",
|
||||||
"smartEditImproveWriting": "Popraw pisanie",
|
"smartEditImproveWriting": "Popraw pisanie",
|
||||||
"smartEditMakeLonger": "Dłużej",
|
"smartEditMakeLonger": "Dłużej",
|
||||||
"smartEditCouldNotFetchResult": "Nie można pobrać wyniku z OpenAI",
|
"smartEditCouldNotFetchResult": "Nie można pobrać wyniku z AI",
|
||||||
"smartEditCouldNotFetchKey": "Nie można pobrać klucza OpenAI",
|
"smartEditCouldNotFetchKey": "Nie można pobrać klucza AI",
|
||||||
"smartEditDisabled": "Połącz OpenAI w Ustawieniach",
|
"smartEditDisabled": "Połącz AI w Ustawieniach",
|
||||||
"discardResponse": "Czy chcesz odrzucić odpowiedzi AI?",
|
"discardResponse": "Czy chcesz odrzucić odpowiedzi AI?",
|
||||||
"createInlineMathEquation": "Utwórz równanie",
|
"createInlineMathEquation": "Utwórz równanie",
|
||||||
"toggleList": "Przełącz listę",
|
"toggleList": "Przełącz listę",
|
||||||
@ -776,8 +776,8 @@
|
|||||||
"placeholder": "Wprowadź adres URL obrazu"
|
"placeholder": "Wprowadź adres URL obrazu"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Wygeneruj obraz z OpenAI",
|
"label": "Wygeneruj obraz z AI",
|
||||||
"placeholder": "Wpisz treść podpowiedzi dla OpenAI, aby wygenerować obraz"
|
"placeholder": "Wpisz treść podpowiedzi dla AI, aby wygenerować obraz"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Wygeneruj obraz z Stability AI",
|
"label": "Wygeneruj obraz z Stability AI",
|
||||||
@ -795,7 +795,7 @@
|
|||||||
"placeholder": "Wklej lub wpisz link obrazu"
|
"placeholder": "Wklej lub wpisz link obrazu"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Szukaj obrazu",
|
"searchForAnImage": "Szukaj obrazu",
|
||||||
"pleaseInputYourOpenAIKey": "wpisz swój klucz OpenAI w ustawieniach",
|
"pleaseInputYourOpenAIKey": "wpisz swój klucz AI w ustawieniach",
|
||||||
"pleaseInputYourStabilityAIKey": "wpisz swój klucz Stability AI w ustawieniach",
|
"pleaseInputYourStabilityAIKey": "wpisz swój klucz Stability AI w ustawieniach",
|
||||||
"saveImageToGallery": "Zapisz obraz",
|
"saveImageToGallery": "Zapisz obraz",
|
||||||
"failedToAddImageToGallery": "Nie udało się dodać obrazu do galerii",
|
"failedToAddImageToGallery": "Nie udało się dodać obrazu do galerii",
|
||||||
|
@ -421,7 +421,7 @@
|
|||||||
"email": "E-mail",
|
"email": "E-mail",
|
||||||
"tooltipSelectIcon": "Selecionar ícone",
|
"tooltipSelectIcon": "Selecionar ícone",
|
||||||
"selectAnIcon": "Escolha um ícone",
|
"selectAnIcon": "Escolha um ícone",
|
||||||
"pleaseInputYourOpenAIKey": "por favor insira sua chave OpenAI",
|
"pleaseInputYourOpenAIKey": "por favor insira sua chave AI",
|
||||||
"pleaseInputYourStabilityAIKey": "insira sua chave Stability AI",
|
"pleaseInputYourStabilityAIKey": "insira sua chave Stability AI",
|
||||||
"clickToLogout": "Clique para sair do usuário atual"
|
"clickToLogout": "Clique para sair do usuário atual"
|
||||||
},
|
},
|
||||||
@ -696,18 +696,18 @@
|
|||||||
"autoGeneratorLearnMore": "Saiba mais",
|
"autoGeneratorLearnMore": "Saiba mais",
|
||||||
"autoGeneratorGenerate": "Gerar",
|
"autoGeneratorGenerate": "Gerar",
|
||||||
"autoGeneratorHintText": "Diga-nos o que você deseja gerar por IA ...",
|
"autoGeneratorHintText": "Diga-nos o que você deseja gerar por IA ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Não foi possível obter a chave da OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Não foi possível obter a chave da AI",
|
||||||
"autoGeneratorRewrite": "Reescrever",
|
"autoGeneratorRewrite": "Reescrever",
|
||||||
"smartEdit": "Assistentes de IA",
|
"smartEdit": "Assistentes de IA",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Corrigir ortografia",
|
"smartEditFixSpelling": "Corrigir ortografia",
|
||||||
"warning": "⚠️ As respostas da IA podem ser imprecisas ou enganosas.",
|
"warning": "⚠️ As respostas da IA podem ser imprecisas ou enganosas.",
|
||||||
"smartEditSummarize": "Resumir",
|
"smartEditSummarize": "Resumir",
|
||||||
"smartEditImproveWriting": "melhorar a escrita",
|
"smartEditImproveWriting": "melhorar a escrita",
|
||||||
"smartEditMakeLonger": "Faça mais",
|
"smartEditMakeLonger": "Faça mais",
|
||||||
"smartEditCouldNotFetchResult": "Não foi possível obter o resultado do OpenAI",
|
"smartEditCouldNotFetchResult": "Não foi possível obter o resultado do AI",
|
||||||
"smartEditCouldNotFetchKey": "Não foi possível obter a chave OpenAI",
|
"smartEditCouldNotFetchKey": "Não foi possível obter a chave AI",
|
||||||
"smartEditDisabled": "Conecte OpenAI em Configurações",
|
"smartEditDisabled": "Conecte AI em Configurações",
|
||||||
"discardResponse": "Deseja descartar as respostas de IA?",
|
"discardResponse": "Deseja descartar as respostas de IA?",
|
||||||
"createInlineMathEquation": "Criar equação",
|
"createInlineMathEquation": "Criar equação",
|
||||||
"fonts": "Fontes",
|
"fonts": "Fontes",
|
||||||
@ -815,8 +815,8 @@
|
|||||||
"placeholder": "Insira o URL da imagem"
|
"placeholder": "Insira o URL da imagem"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Gerar imagem da OpenAI",
|
"label": "Gerar imagem da AI",
|
||||||
"placeholder": "Insira o prompt para OpenAI gerar imagem"
|
"placeholder": "Insira o prompt para AI gerar imagem"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Gerar imagem da Stability AI",
|
"label": "Gerar imagem da Stability AI",
|
||||||
@ -837,7 +837,7 @@
|
|||||||
"label": "Remover respingo"
|
"label": "Remover respingo"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Procurar uma imagem",
|
"searchForAnImage": "Procurar uma imagem",
|
||||||
"pleaseInputYourOpenAIKey": "insira sua chave OpenAI na página configurações",
|
"pleaseInputYourOpenAIKey": "insira sua chave AI na página configurações",
|
||||||
"pleaseInputYourStabilityAIKey": "insira sua chave Stability AI na página Configurações",
|
"pleaseInputYourStabilityAIKey": "insira sua chave Stability AI na página Configurações",
|
||||||
"saveImageToGallery": "Salvar imagem",
|
"saveImageToGallery": "Salvar imagem",
|
||||||
"failedToAddImageToGallery": "Falha ao adicionar imagem à galeria",
|
"failedToAddImageToGallery": "Falha ao adicionar imagem à galeria",
|
||||||
|
@ -362,7 +362,7 @@
|
|||||||
"email": "E-mail",
|
"email": "E-mail",
|
||||||
"tooltipSelectIcon": "Selecione o ícone",
|
"tooltipSelectIcon": "Selecione o ícone",
|
||||||
"selectAnIcon": "Selecione um ícone",
|
"selectAnIcon": "Selecione um ícone",
|
||||||
"pleaseInputYourOpenAIKey": "por favor insira sua chave OpenAI",
|
"pleaseInputYourOpenAIKey": "por favor insira sua chave AI",
|
||||||
"pleaseInputYourStabilityAIKey": "por favor, insira a sua chave Stability AI",
|
"pleaseInputYourStabilityAIKey": "por favor, insira a sua chave Stability AI",
|
||||||
"clickToLogout": "Clique para fazer logout"
|
"clickToLogout": "Clique para fazer logout"
|
||||||
},
|
},
|
||||||
@ -561,23 +561,23 @@
|
|||||||
"referencedBoard": "Conselho Referenciado",
|
"referencedBoard": "Conselho Referenciado",
|
||||||
"referencedGrid": "grade referenciada",
|
"referencedGrid": "grade referenciada",
|
||||||
"referencedCalendar": "calendário referenciado",
|
"referencedCalendar": "calendário referenciado",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Writer",
|
"autoGeneratorMenuItemName": "AI Writer",
|
||||||
"autoGeneratorTitleName": "OpenAI: Peça à IA para escrever qualquer coisa...",
|
"autoGeneratorTitleName": "AI: Peça à IA para escrever qualquer coisa...",
|
||||||
"autoGeneratorLearnMore": "Saber mais",
|
"autoGeneratorLearnMore": "Saber mais",
|
||||||
"autoGeneratorGenerate": "Gerar",
|
"autoGeneratorGenerate": "Gerar",
|
||||||
"autoGeneratorHintText": "Pergunte ao OpenAI...",
|
"autoGeneratorHintText": "Pergunte ao AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Não é possível obter a chave OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Não é possível obter a chave AI",
|
||||||
"autoGeneratorRewrite": "Reescrever",
|
"autoGeneratorRewrite": "Reescrever",
|
||||||
"smartEdit": "Assistentes de IA",
|
"smartEdit": "Assistentes de IA",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "corrigir ortografia",
|
"smartEditFixSpelling": "corrigir ortografia",
|
||||||
"warning": "⚠️ As respostas da IA podem ser imprecisas ou enganosas.",
|
"warning": "⚠️ As respostas da IA podem ser imprecisas ou enganosas.",
|
||||||
"smartEditSummarize": "Resumir",
|
"smartEditSummarize": "Resumir",
|
||||||
"smartEditImproveWriting": "melhorar a escrita",
|
"smartEditImproveWriting": "melhorar a escrita",
|
||||||
"smartEditMakeLonger": "Faça mais",
|
"smartEditMakeLonger": "Faça mais",
|
||||||
"smartEditCouldNotFetchResult": "Não foi possível obter o resultado do OpenAI",
|
"smartEditCouldNotFetchResult": "Não foi possível obter o resultado do AI",
|
||||||
"smartEditCouldNotFetchKey": "Não foi possível obter a chave OpenAI",
|
"smartEditCouldNotFetchKey": "Não foi possível obter a chave AI",
|
||||||
"smartEditDisabled": "Conecte OpenAI em Configurações",
|
"smartEditDisabled": "Conecte AI em Configurações",
|
||||||
"discardResponse": "Deseja descartar as respostas de IA?",
|
"discardResponse": "Deseja descartar as respostas de IA?",
|
||||||
"createInlineMathEquation": "Criar equação",
|
"createInlineMathEquation": "Criar equação",
|
||||||
"toggleList": "Alternar lista",
|
"toggleList": "Alternar lista",
|
||||||
@ -662,8 +662,8 @@
|
|||||||
"placeholder": "Insira o URL da imagem"
|
"placeholder": "Insira o URL da imagem"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Gerar imagem da OpenAI",
|
"label": "Gerar imagem da AI",
|
||||||
"placeholder": "Por favor, insira o comando para a OpenAI gerar a imagem"
|
"placeholder": "Por favor, insira o comando para a AI gerar a imagem"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Gerar imagem da Stability AI",
|
"label": "Gerar imagem da Stability AI",
|
||||||
@ -681,7 +681,7 @@
|
|||||||
"placeholder": "Cole ou digite uma hiperligação de imagem"
|
"placeholder": "Cole ou digite uma hiperligação de imagem"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Procure uma imagem",
|
"searchForAnImage": "Procure uma imagem",
|
||||||
"pleaseInputYourOpenAIKey": "por favor, insira a sua chave OpenAI na página Configurações",
|
"pleaseInputYourOpenAIKey": "por favor, insira a sua chave AI na página Configurações",
|
||||||
"pleaseInputYourStabilityAIKey": "por favor, insira a sua chave Stability AI na página Configurações"
|
"pleaseInputYourStabilityAIKey": "por favor, insira a sua chave Stability AI na página Configurações"
|
||||||
},
|
},
|
||||||
"codeBlock": {
|
"codeBlock": {
|
||||||
|
@ -999,7 +999,7 @@
|
|||||||
"email": "Электронная почта",
|
"email": "Электронная почта",
|
||||||
"tooltipSelectIcon": "Выберите иконку",
|
"tooltipSelectIcon": "Выберите иконку",
|
||||||
"selectAnIcon": "Выбрать иконку",
|
"selectAnIcon": "Выбрать иконку",
|
||||||
"pleaseInputYourOpenAIKey": "Пожалуйста, введите токен OpenAI",
|
"pleaseInputYourOpenAIKey": "Пожалуйста, введите токен AI",
|
||||||
"pleaseInputYourStabilityAIKey": "Пожалуйста, введите свой токен Stability AI",
|
"pleaseInputYourStabilityAIKey": "Пожалуйста, введите свой токен Stability AI",
|
||||||
"clickToLogout": "Нажмите, чтобы выйти из текущего аккаунта"
|
"clickToLogout": "Нажмите, чтобы выйти из текущего аккаунта"
|
||||||
},
|
},
|
||||||
@ -1326,15 +1326,15 @@
|
|||||||
"referencedGrid": "Связанные сетки",
|
"referencedGrid": "Связанные сетки",
|
||||||
"referencedCalendar": "Связанные календари",
|
"referencedCalendar": "Связанные календари",
|
||||||
"referencedDocument": "Связанные документы",
|
"referencedDocument": "Связанные документы",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Генератор",
|
"autoGeneratorMenuItemName": "AI Генератор",
|
||||||
"autoGeneratorTitleName": "OpenAI: попросить ИИ написать что угодно...",
|
"autoGeneratorTitleName": "AI: попросить ИИ написать что угодно...",
|
||||||
"autoGeneratorLearnMore": "Узнать больше",
|
"autoGeneratorLearnMore": "Узнать больше",
|
||||||
"autoGeneratorGenerate": "Генерировать",
|
"autoGeneratorGenerate": "Генерировать",
|
||||||
"autoGeneratorHintText": "Спросить OpenAI ...",
|
"autoGeneratorHintText": "Спросить AI ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Не могу получить токен OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Не могу получить токен AI",
|
||||||
"autoGeneratorRewrite": "Переписать",
|
"autoGeneratorRewrite": "Переписать",
|
||||||
"smartEdit": "ИИ-ассистенты",
|
"smartEdit": "ИИ-ассистенты",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Исправить правописание",
|
"smartEditFixSpelling": "Исправить правописание",
|
||||||
"warning": "⚠️ Ответы ИИ могут быть неправильными или неточными.",
|
"warning": "⚠️ Ответы ИИ могут быть неправильными или неточными.",
|
||||||
"smartEditSummarize": "Обобщить",
|
"smartEditSummarize": "Обобщить",
|
||||||
@ -1472,8 +1472,8 @@
|
|||||||
"placeholder": "Введите URL-адрес изображения"
|
"placeholder": "Введите URL-адрес изображения"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "Сгенерировать изображение через OpenAI",
|
"label": "Сгенерировать изображение через AI",
|
||||||
"placeholder": "Пожалуйста, введите запрос для OpenAI чтобы сгенерировать изображение"
|
"placeholder": "Пожалуйста, введите запрос для AI чтобы сгенерировать изображение"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Сгенерировать изображение через Stability AI",
|
"label": "Сгенерировать изображение через Stability AI",
|
||||||
@ -1495,7 +1495,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Поиск изображения",
|
"searchForAnImage": "Поиск изображения",
|
||||||
"pleaseInputYourOpenAIKey": "пожалуйста, введите свой токен OpenAI на странице настроек",
|
"pleaseInputYourOpenAIKey": "пожалуйста, введите свой токен AI на странице настроек",
|
||||||
"pleaseInputYourStabilityAIKey": "пожалуйста, введите свой токен Stability AI на странице настроек",
|
"pleaseInputYourStabilityAIKey": "пожалуйста, введите свой токен Stability AI на странице настроек",
|
||||||
"saveImageToGallery": "Сохранить изображение",
|
"saveImageToGallery": "Сохранить изображение",
|
||||||
"failedToAddImageToGallery": "Ошибка добавления изображения в галерею",
|
"failedToAddImageToGallery": "Ошибка добавления изображения в галерею",
|
||||||
@ -2103,4 +2103,4 @@
|
|||||||
"signInError": "Ошибка входа",
|
"signInError": "Ошибка входа",
|
||||||
"login": "Зарегистрироваться или войти"
|
"login": "Зарегистрироваться или войти"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -345,7 +345,7 @@
|
|||||||
"user": {
|
"user": {
|
||||||
"name": "namn",
|
"name": "namn",
|
||||||
"selectAnIcon": "Välj en ikon",
|
"selectAnIcon": "Välj en ikon",
|
||||||
"pleaseInputYourOpenAIKey": "vänligen ange din OpenAI-nyckel"
|
"pleaseInputYourOpenAIKey": "vänligen ange din AI-nyckel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"grid": {
|
"grid": {
|
||||||
@ -501,23 +501,23 @@
|
|||||||
"referencedBoard": "Refererad tavla",
|
"referencedBoard": "Refererad tavla",
|
||||||
"referencedGrid": "Refererade tabell",
|
"referencedGrid": "Refererade tabell",
|
||||||
"referencedCalendar": "Refererad kalender",
|
"referencedCalendar": "Refererad kalender",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Writer",
|
"autoGeneratorMenuItemName": "AI Writer",
|
||||||
"autoGeneratorTitleName": "OpenAI: Be AI skriva vad som helst...",
|
"autoGeneratorTitleName": "AI: Be AI skriva vad som helst...",
|
||||||
"autoGeneratorLearnMore": "Läs mer",
|
"autoGeneratorLearnMore": "Läs mer",
|
||||||
"autoGeneratorGenerate": "Generera",
|
"autoGeneratorGenerate": "Generera",
|
||||||
"autoGeneratorHintText": "Fråga OpenAI...",
|
"autoGeneratorHintText": "Fråga AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Kan inte hämta OpenAI-nyckeln",
|
"autoGeneratorCantGetOpenAIKey": "Kan inte hämta AI-nyckeln",
|
||||||
"autoGeneratorRewrite": "Skriva om",
|
"autoGeneratorRewrite": "Skriva om",
|
||||||
"smartEdit": "AI-assistenter",
|
"smartEdit": "AI-assistenter",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Fixa stavningen",
|
"smartEditFixSpelling": "Fixa stavningen",
|
||||||
"warning": "⚠️ AI-svar kan vara felaktiga eller vilseledande.",
|
"warning": "⚠️ AI-svar kan vara felaktiga eller vilseledande.",
|
||||||
"smartEditSummarize": "Sammanfatta",
|
"smartEditSummarize": "Sammanfatta",
|
||||||
"smartEditImproveWriting": "Förbättra skrivandet",
|
"smartEditImproveWriting": "Förbättra skrivandet",
|
||||||
"smartEditMakeLonger": "Gör längre",
|
"smartEditMakeLonger": "Gör längre",
|
||||||
"smartEditCouldNotFetchResult": "Det gick inte att hämta resultatet från OpenAI",
|
"smartEditCouldNotFetchResult": "Det gick inte att hämta resultatet från AI",
|
||||||
"smartEditCouldNotFetchKey": "Det gick inte att hämta OpenAI-nyckeln",
|
"smartEditCouldNotFetchKey": "Det gick inte att hämta AI-nyckeln",
|
||||||
"smartEditDisabled": "Anslut OpenAI i Inställningar",
|
"smartEditDisabled": "Anslut AI i Inställningar",
|
||||||
"discardResponse": "Vill du kassera AI-svaren?",
|
"discardResponse": "Vill du kassera AI-svaren?",
|
||||||
"createInlineMathEquation": "Skapa ekvation",
|
"createInlineMathEquation": "Skapa ekvation",
|
||||||
"toggleList": "Växla lista",
|
"toggleList": "Växla lista",
|
||||||
|
@ -399,7 +399,7 @@
|
|||||||
"email": "อีเมล",
|
"email": "อีเมล",
|
||||||
"tooltipSelectIcon": "เลือกไอคอน",
|
"tooltipSelectIcon": "เลือกไอคอน",
|
||||||
"selectAnIcon": "เลือกไอคอน",
|
"selectAnIcon": "เลือกไอคอน",
|
||||||
"pleaseInputYourOpenAIKey": "โปรดระบุคีย์ OpenAI ของคุณ",
|
"pleaseInputYourOpenAIKey": "โปรดระบุคีย์ AI ของคุณ",
|
||||||
"pleaseInputYourStabilityAIKey": "โปรดระบุคีย์ Stability AI ของคุณ",
|
"pleaseInputYourStabilityAIKey": "โปรดระบุคีย์ Stability AI ของคุณ",
|
||||||
"clickToLogout": "คลิกเพื่อออกจากระบบผู้ใช้ปัจจุบัน"
|
"clickToLogout": "คลิกเพื่อออกจากระบบผู้ใช้ปัจจุบัน"
|
||||||
},
|
},
|
||||||
@ -647,23 +647,23 @@
|
|||||||
"referencedGrid": "ตารางอ้างอิง",
|
"referencedGrid": "ตารางอ้างอิง",
|
||||||
"referencedCalendar": "ปฏิทินที่อ้างอิง",
|
"referencedCalendar": "ปฏิทินที่อ้างอิง",
|
||||||
"referencedDocument": "เอกสารอ้างอิง",
|
"referencedDocument": "เอกสารอ้างอิง",
|
||||||
"autoGeneratorMenuItemName": "นักเขียน OpenAI",
|
"autoGeneratorMenuItemName": "นักเขียน AI",
|
||||||
"autoGeneratorTitleName": "OpenAI: สอบถาม AI เพื่อให้เขียนอะไรก็ได้...",
|
"autoGeneratorTitleName": "AI: สอบถาม AI เพื่อให้เขียนอะไรก็ได้...",
|
||||||
"autoGeneratorLearnMore": "เรียนรู้เพิ่มเติม",
|
"autoGeneratorLearnMore": "เรียนรู้เพิ่มเติม",
|
||||||
"autoGeneratorGenerate": "สร้าง",
|
"autoGeneratorGenerate": "สร้าง",
|
||||||
"autoGeneratorHintText": "ถาม OpenAI ...",
|
"autoGeneratorHintText": "ถาม AI ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "ไม่สามารถรับคีย์ OpenAI ได้",
|
"autoGeneratorCantGetOpenAIKey": "ไม่สามารถรับคีย์ AI ได้",
|
||||||
"autoGeneratorRewrite": "เขียนใหม่",
|
"autoGeneratorRewrite": "เขียนใหม่",
|
||||||
"smartEdit": "ผู้ช่วย AI",
|
"smartEdit": "ผู้ช่วย AI",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "แก้ไขการสะกด",
|
"smartEditFixSpelling": "แก้ไขการสะกด",
|
||||||
"warning": "⚠️ คำตอบของ AI อาจจะไม่ถูกต้องหรืออาจจะเข้าใจผิดได้",
|
"warning": "⚠️ คำตอบของ AI อาจจะไม่ถูกต้องหรืออาจจะเข้าใจผิดได้",
|
||||||
"smartEditSummarize": "สรุป",
|
"smartEditSummarize": "สรุป",
|
||||||
"smartEditImproveWriting": "ปรับปรุงการเขียน",
|
"smartEditImproveWriting": "ปรับปรุงการเขียน",
|
||||||
"smartEditMakeLonger": "ทำให้ยาวขึ้น",
|
"smartEditMakeLonger": "ทำให้ยาวขึ้น",
|
||||||
"smartEditCouldNotFetchResult": "ไม่สามารถดึงผลลัพธ์จาก OpenAI ได้",
|
"smartEditCouldNotFetchResult": "ไม่สามารถดึงผลลัพธ์จาก AI ได้",
|
||||||
"smartEditCouldNotFetchKey": "ไม่สามารถดึงคีย์ OpenAI ได้",
|
"smartEditCouldNotFetchKey": "ไม่สามารถดึงคีย์ AI ได้",
|
||||||
"smartEditDisabled": "เชื่อมต่อ OpenAI ในการตั้งค่า",
|
"smartEditDisabled": "เชื่อมต่อ AI ในการตั้งค่า",
|
||||||
"discardResponse": "คุณต้องการทิ้งการตอบกลับของ AI หรือไม่",
|
"discardResponse": "คุณต้องการทิ้งการตอบกลับของ AI หรือไม่",
|
||||||
"createInlineMathEquation": "สร้างสมการ",
|
"createInlineMathEquation": "สร้างสมการ",
|
||||||
"fonts": "แบบอักษร",
|
"fonts": "แบบอักษร",
|
||||||
@ -757,8 +757,8 @@
|
|||||||
"placeholder": "ป้อน URL รูปภาพ"
|
"placeholder": "ป้อน URL รูปภาพ"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "สร้างรูปภาพจาก OpenAI",
|
"label": "สร้างรูปภาพจาก AI",
|
||||||
"placeholder": "โปรดระบุคำขอให้ OpenAI สร้างรูปภาพ"
|
"placeholder": "โปรดระบุคำขอให้ AI สร้างรูปภาพ"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "สร้างรูปภาพจาก Stability AI",
|
"label": "สร้างรูปภาพจาก Stability AI",
|
||||||
@ -779,7 +779,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "ค้นหารูปภาพ",
|
"searchForAnImage": "ค้นหารูปภาพ",
|
||||||
"pleaseInputYourOpenAIKey": "โปรดระบุคีย์ OpenAI ของคุณในหน้าการตั้งค่า",
|
"pleaseInputYourOpenAIKey": "โปรดระบุคีย์ AI ของคุณในหน้าการตั้งค่า",
|
||||||
"pleaseInputYourStabilityAIKey": "โปรดระบุคีย์ Stability AI ของคุณในหน้าการตั้งค่า",
|
"pleaseInputYourStabilityAIKey": "โปรดระบุคีย์ Stability AI ของคุณในหน้าการตั้งค่า",
|
||||||
"saveImageToGallery": "บันทึกภาพ",
|
"saveImageToGallery": "บันทึกภาพ",
|
||||||
"failedToAddImageToGallery": "ไม่สามารถเพิ่มรูปภาพลงในแกลเลอรี่ได้",
|
"failedToAddImageToGallery": "ไม่สามารถเพิ่มรูปภาพลงในแกลเลอรี่ได้",
|
||||||
|
@ -337,7 +337,7 @@
|
|||||||
"logoutLabel": "Çıkış Yap"
|
"logoutLabel": "Çıkış Yap"
|
||||||
},
|
},
|
||||||
"keys": {
|
"keys": {
|
||||||
"openAIHint": "OpenAI API Anahtarını Gir"
|
"openAIHint": "AI API Anahtarını Gir"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"workspacePage": {
|
"workspacePage": {
|
||||||
@ -553,7 +553,7 @@
|
|||||||
"email": "E-posta",
|
"email": "E-posta",
|
||||||
"tooltipSelectIcon": "Simge seç",
|
"tooltipSelectIcon": "Simge seç",
|
||||||
"selectAnIcon": "Bir simge seçin",
|
"selectAnIcon": "Bir simge seçin",
|
||||||
"pleaseInputYourOpenAIKey": "Lütfen OpenAI anahtarınızı girin",
|
"pleaseInputYourOpenAIKey": "Lütfen AI anahtarınızı girin",
|
||||||
"pleaseInputYourStabilityAIKey": "Lütfen Stability AI anahtarınızı girin",
|
"pleaseInputYourStabilityAIKey": "Lütfen Stability AI anahtarınızı girin",
|
||||||
"clickToLogout": "Geçerli kullanıcıdan çıkış yapmak için tıklayın"
|
"clickToLogout": "Geçerli kullanıcıdan çıkış yapmak için tıklayın"
|
||||||
},
|
},
|
||||||
@ -882,23 +882,23 @@
|
|||||||
"referencedGrid": "Referans Gösterilen Tablo",
|
"referencedGrid": "Referans Gösterilen Tablo",
|
||||||
"referencedCalendar": "Referans Gösterilen Takvim",
|
"referencedCalendar": "Referans Gösterilen Takvim",
|
||||||
"referencedDocument": "Referans Gösterilen Belge",
|
"referencedDocument": "Referans Gösterilen Belge",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Yazar",
|
"autoGeneratorMenuItemName": "AI Yazar",
|
||||||
"autoGeneratorTitleName": "OpenAI: AI'dan istediğinizi yazmasını isteyin...",
|
"autoGeneratorTitleName": "AI: AI'dan istediğinizi yazmasını isteyin...",
|
||||||
"autoGeneratorLearnMore": "Daha fazla bilgi edinin",
|
"autoGeneratorLearnMore": "Daha fazla bilgi edinin",
|
||||||
"autoGeneratorGenerate": "Oluştur",
|
"autoGeneratorGenerate": "Oluştur",
|
||||||
"autoGeneratorHintText": "OpenAI'ya sorun ...",
|
"autoGeneratorHintText": "AI'ya sorun ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "OpenAI anahtarı alınamıyor",
|
"autoGeneratorCantGetOpenAIKey": "AI anahtarı alınamıyor",
|
||||||
"autoGeneratorRewrite": "Yeniden yaz",
|
"autoGeneratorRewrite": "Yeniden yaz",
|
||||||
"smartEdit": "AI Asistanları",
|
"smartEdit": "AI Asistanları",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Yazımı düzelt",
|
"smartEditFixSpelling": "Yazımı düzelt",
|
||||||
"warning": "⚠️ AI yanıtları yanlış veya yanıltıcı olabilir.",
|
"warning": "⚠️ AI yanıtları yanlış veya yanıltıcı olabilir.",
|
||||||
"smartEditSummarize": "Özetle",
|
"smartEditSummarize": "Özetle",
|
||||||
"smartEditImproveWriting": "Yazımı geliştir",
|
"smartEditImproveWriting": "Yazımı geliştir",
|
||||||
"smartEditMakeLonger": "Daha uzun yap",
|
"smartEditMakeLonger": "Daha uzun yap",
|
||||||
"smartEditCouldNotFetchResult": "OpenAI'dan sonuç alınamadı",
|
"smartEditCouldNotFetchResult": "AI'dan sonuç alınamadı",
|
||||||
"smartEditCouldNotFetchKey": "OpenAI anahtarı alınamadı",
|
"smartEditCouldNotFetchKey": "AI anahtarı alınamadı",
|
||||||
"smartEditDisabled": "Ayarlar'da OpenAI'yı bağlayın",
|
"smartEditDisabled": "Ayarlar'da AI'yı bağlayın",
|
||||||
"discardResponse": "AI yanıtlarını silmek ister misiniz?",
|
"discardResponse": "AI yanıtlarını silmek ister misiniz?",
|
||||||
"createInlineMathEquation": "Denklem oluştur",
|
"createInlineMathEquation": "Denklem oluştur",
|
||||||
"fonts": "Yazı Tipleri",
|
"fonts": "Yazı Tipleri",
|
||||||
@ -1013,8 +1013,8 @@
|
|||||||
"placeholder": "Resim URL'sini girin"
|
"placeholder": "Resim URL'sini girin"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "OpenAI ile resim oluştur",
|
"label": "AI ile resim oluştur",
|
||||||
"placeholder": "Lütfen OpenAI'nin resim oluşturması için komutu girin"
|
"placeholder": "Lütfen AI'nin resim oluşturması için komutu girin"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "Stability AI ile resim oluştur",
|
"label": "Stability AI ile resim oluştur",
|
||||||
@ -1036,7 +1036,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "Bir resim arayın",
|
"searchForAnImage": "Bir resim arayın",
|
||||||
"pleaseInputYourOpenAIKey": "Lütfen Ayarlar sayfasında OpenAI anahtarınızı girin",
|
"pleaseInputYourOpenAIKey": "Lütfen Ayarlar sayfasında AI anahtarınızı girin",
|
||||||
"pleaseInputYourStabilityAIKey": "Lütfen Ayarlar sayfasında Stability AI anahtarınızı girin",
|
"pleaseInputYourStabilityAIKey": "Lütfen Ayarlar sayfasında Stability AI anahtarınızı girin",
|
||||||
"saveImageToGallery": "Resmi kaydet",
|
"saveImageToGallery": "Resmi kaydet",
|
||||||
"failedToAddImageToGallery": "Resim galeriye eklenemedi",
|
"failedToAddImageToGallery": "Resim galeriye eklenemedi",
|
||||||
|
@ -351,7 +351,7 @@
|
|||||||
"email": "Електронна пошта",
|
"email": "Електронна пошта",
|
||||||
"tooltipSelectIcon": "Обрати значок",
|
"tooltipSelectIcon": "Обрати значок",
|
||||||
"selectAnIcon": "Обрати значок",
|
"selectAnIcon": "Обрати значок",
|
||||||
"pleaseInputYourOpenAIKey": "Будь ласка, введіть ваш ключ OpenAI",
|
"pleaseInputYourOpenAIKey": "Будь ласка, введіть ваш ключ AI",
|
||||||
"clickToLogout": "Натисніть, щоб вийти з поточного облікового запису"
|
"clickToLogout": "Натисніть, щоб вийти з поточного облікового запису"
|
||||||
},
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
@ -547,23 +547,23 @@
|
|||||||
"referencedBoard": "Пов'язані дошки",
|
"referencedBoard": "Пов'язані дошки",
|
||||||
"referencedGrid": "Пов'язані сітки",
|
"referencedGrid": "Пов'язані сітки",
|
||||||
"referencedCalendar": "Календар посилань",
|
"referencedCalendar": "Календар посилань",
|
||||||
"autoGeneratorMenuItemName": "OpenAI Writer",
|
"autoGeneratorMenuItemName": "AI Writer",
|
||||||
"autoGeneratorTitleName": "OpenAI: Запитайте штучний інтелект написати будь-що...",
|
"autoGeneratorTitleName": "AI: Запитайте штучний інтелект написати будь-що...",
|
||||||
"autoGeneratorLearnMore": "Дізнатися більше",
|
"autoGeneratorLearnMore": "Дізнатися більше",
|
||||||
"autoGeneratorGenerate": "Генерувати",
|
"autoGeneratorGenerate": "Генерувати",
|
||||||
"autoGeneratorHintText": "Запитайте OpenAI...",
|
"autoGeneratorHintText": "Запитайте AI...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "Не вдається отримати ключ OpenAI",
|
"autoGeneratorCantGetOpenAIKey": "Не вдається отримати ключ AI",
|
||||||
"autoGeneratorRewrite": "Переписати",
|
"autoGeneratorRewrite": "Переписати",
|
||||||
"smartEdit": "AI Асистенти",
|
"smartEdit": "AI Асистенти",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "Виправити правопис",
|
"smartEditFixSpelling": "Виправити правопис",
|
||||||
"warning": "⚠️ Відповіді AI можуть бути неточними або вводити в оману.",
|
"warning": "⚠️ Відповіді AI можуть бути неточними або вводити в оману.",
|
||||||
"smartEditSummarize": "Підсумувати",
|
"smartEditSummarize": "Підсумувати",
|
||||||
"smartEditImproveWriting": "Покращити написання",
|
"smartEditImproveWriting": "Покращити написання",
|
||||||
"smartEditMakeLonger": "Зробити довше",
|
"smartEditMakeLonger": "Зробити довше",
|
||||||
"smartEditCouldNotFetchResult": "Не вдалося отримати результат від OpenAI",
|
"smartEditCouldNotFetchResult": "Не вдалося отримати результат від AI",
|
||||||
"smartEditCouldNotFetchKey": "Не вдалося отримати ключ OpenAI",
|
"smartEditCouldNotFetchKey": "Не вдалося отримати ключ AI",
|
||||||
"smartEditDisabled": "Підключіть OpenAI в Налаштуваннях",
|
"smartEditDisabled": "Підключіть AI в Налаштуваннях",
|
||||||
"discardResponse": "Ви хочете відкинути відповіді AI?",
|
"discardResponse": "Ви хочете відкинути відповіді AI?",
|
||||||
"createInlineMathEquation": "Створити рівняння",
|
"createInlineMathEquation": "Створити рівняння",
|
||||||
"toggleList": "Перемкнути список",
|
"toggleList": "Перемкнути список",
|
||||||
|
@ -331,7 +331,7 @@
|
|||||||
"name": "نام",
|
"name": "نام",
|
||||||
"email": "ای میل",
|
"email": "ای میل",
|
||||||
"selectAnIcon": "آئیکن منتخب کریں",
|
"selectAnIcon": "آئیکن منتخب کریں",
|
||||||
"pleaseInputYourOpenAIKey": "براہ کرم اپنی OpenAI کی درج کریں",
|
"pleaseInputYourOpenAIKey": "براہ کرم اپنی AI کی درج کریں",
|
||||||
"clickToLogout": "موجودہ صارف سے لاگ آؤٹ کرنے کے لیے کلک کریں"
|
"clickToLogout": "موجودہ صارف سے لاگ آؤٹ کرنے کے لیے کلک کریں"
|
||||||
},
|
},
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
@ -511,23 +511,23 @@
|
|||||||
"referencedBoard": "حوالہ شدہ بورڈ",
|
"referencedBoard": "حوالہ شدہ بورڈ",
|
||||||
"referencedGrid": "حوالہ شدہ گرِڈ",
|
"referencedGrid": "حوالہ شدہ گرِڈ",
|
||||||
"referencedCalendar": "حوالہ شدہ کیلنڈر",
|
"referencedCalendar": "حوالہ شدہ کیلنڈر",
|
||||||
"autoGeneratorMenuItemName": "OpenAI رائٹر",
|
"autoGeneratorMenuItemName": "AI رائٹر",
|
||||||
"autoGeneratorTitleName": "OpenAI: AI سے کچھ بھی لکھنے کے لیے کہیں...",
|
"autoGeneratorTitleName": "AI: AI سے کچھ بھی لکھنے کے لیے کہیں...",
|
||||||
"autoGeneratorLearnMore": "مزید جانئے",
|
"autoGeneratorLearnMore": "مزید جانئے",
|
||||||
"autoGeneratorGenerate": "جنریٹ کریں",
|
"autoGeneratorGenerate": "جنریٹ کریں",
|
||||||
"autoGeneratorHintText": "OpenAI سے پوچھیں...",
|
"autoGeneratorHintText": "AI سے پوچھیں...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "OpenAI کی حاصل نہیں کر سکتا",
|
"autoGeneratorCantGetOpenAIKey": "AI کی حاصل نہیں کر سکتا",
|
||||||
"autoGeneratorRewrite": "دوبارہ لکھیں",
|
"autoGeneratorRewrite": "دوبارہ لکھیں",
|
||||||
"smartEdit": "AI اسسٹنٹ",
|
"smartEdit": "AI اسسٹنٹ",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "املا درست کریں",
|
"smartEditFixSpelling": "املا درست کریں",
|
||||||
"warning": "⚠️ AI کی پاسخیں غلط یا گمراہ کن ہو سکتی ہیں۔",
|
"warning": "⚠️ AI کی پاسخیں غلط یا گمراہ کن ہو سکتی ہیں۔",
|
||||||
"smartEditSummarize": "سارے لکھیں",
|
"smartEditSummarize": "سارے لکھیں",
|
||||||
"smartEditImproveWriting": "تحریر بہتر بنائیں",
|
"smartEditImproveWriting": "تحریر بہتر بنائیں",
|
||||||
"smartEditMakeLonger": "طویل تر بنائیں",
|
"smartEditMakeLonger": "طویل تر بنائیں",
|
||||||
"smartEditCouldNotFetchResult": "OpenAI سے نتیجہ حاصل نہیں کر سکا",
|
"smartEditCouldNotFetchResult": "AI سے نتیجہ حاصل نہیں کر سکا",
|
||||||
"smartEditCouldNotFetchKey": "OpenAI کی حاصل نہیں کر سکا",
|
"smartEditCouldNotFetchKey": "AI کی حاصل نہیں کر سکا",
|
||||||
"smartEditDisabled": "Settings میں OpenAI سے منسلک کریں",
|
"smartEditDisabled": "Settings میں AI سے منسلک کریں",
|
||||||
"discardResponse": "کیا آپ AI کی پاسخیں حذف کرنا چاہتے ہیں؟",
|
"discardResponse": "کیا آپ AI کی پاسخیں حذف کرنا چاہتے ہیں؟",
|
||||||
"createInlineMathEquation": "مساوات بنائیں",
|
"createInlineMathEquation": "مساوات بنائیں",
|
||||||
"toggleList": "فہرست ٹوگل کریں",
|
"toggleList": "فہرست ٹوگل کریں",
|
||||||
|
@ -465,7 +465,7 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"tooltipSelectIcon": "Chọn biểu tượng",
|
"tooltipSelectIcon": "Chọn biểu tượng",
|
||||||
"selectAnIcon": "Chọn một biểu tượng",
|
"selectAnIcon": "Chọn một biểu tượng",
|
||||||
"pleaseInputYourOpenAIKey": "vui lòng nhập khóa OpenAI của bạn",
|
"pleaseInputYourOpenAIKey": "vui lòng nhập khóa AI của bạn",
|
||||||
"pleaseInputYourStabilityAIKey": "vui lòng nhập khóa Stability AI của bạn",
|
"pleaseInputYourStabilityAIKey": "vui lòng nhập khóa Stability AI của bạn",
|
||||||
"clickToLogout": "Nhấn để đăng xuất"
|
"clickToLogout": "Nhấn để đăng xuất"
|
||||||
},
|
},
|
||||||
@ -675,7 +675,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"optionAction": {
|
"optionAction": {
|
||||||
"delete": "Xóa",
|
"delete": "Xóa",
|
||||||
"color": "Màu",
|
"color": "Màu",
|
||||||
|
@ -376,13 +376,6 @@
|
|||||||
"loginLabel": "登录",
|
"loginLabel": "登录",
|
||||||
"logoutLabel": "退出登录"
|
"logoutLabel": "退出登录"
|
||||||
},
|
},
|
||||||
"keys": {
|
|
||||||
"title": "AI API 密钥",
|
|
||||||
"openAILabel": "OpenAI API 密钥",
|
|
||||||
"openAIHint": "输入你的 OpenAI API Key",
|
|
||||||
"stabilityAILabel": "Stability API key",
|
|
||||||
"stabilityAIHint": "输入你的 Stability API Key"
|
|
||||||
},
|
|
||||||
"description": "自定义您的简介,管理账户安全信息和 AI API keys,或登陆您的账户"
|
"description": "自定义您的简介,管理账户安全信息和 AI API keys,或登陆您的账户"
|
||||||
},
|
},
|
||||||
"workspacePage": {
|
"workspacePage": {
|
||||||
@ -694,7 +687,7 @@
|
|||||||
"email": "电子邮件",
|
"email": "电子邮件",
|
||||||
"tooltipSelectIcon": "选择图标",
|
"tooltipSelectIcon": "选择图标",
|
||||||
"selectAnIcon": "选择一个图标",
|
"selectAnIcon": "选择一个图标",
|
||||||
"pleaseInputYourOpenAIKey": "请输入您的 OpenAI 密钥",
|
"pleaseInputYourOpenAIKey": "请输入您的 AI 密钥",
|
||||||
"pleaseInputYourStabilityAIKey": "请输入您的 Stability AI 密钥",
|
"pleaseInputYourStabilityAIKey": "请输入您的 Stability AI 密钥",
|
||||||
"clickToLogout": "点击退出当前用户"
|
"clickToLogout": "点击退出当前用户"
|
||||||
},
|
},
|
||||||
@ -1022,23 +1015,23 @@
|
|||||||
"referencedGrid": "引用的网格",
|
"referencedGrid": "引用的网格",
|
||||||
"referencedCalendar": "引用的日历",
|
"referencedCalendar": "引用的日历",
|
||||||
"referencedDocument": "参考文档",
|
"referencedDocument": "参考文档",
|
||||||
"autoGeneratorMenuItemName": "OpenAI 创作",
|
"autoGeneratorMenuItemName": "AI 创作",
|
||||||
"autoGeneratorTitleName": "OpenAI: 让 AI 写些什么...",
|
"autoGeneratorTitleName": "AI: 让 AI 写些什么...",
|
||||||
"autoGeneratorLearnMore": "学习更多",
|
"autoGeneratorLearnMore": "学习更多",
|
||||||
"autoGeneratorGenerate": "生成",
|
"autoGeneratorGenerate": "生成",
|
||||||
"autoGeneratorHintText": "让 OpenAI ...",
|
"autoGeneratorHintText": "让 AI ...",
|
||||||
"autoGeneratorCantGetOpenAIKey": "无法获得 OpenAI 密钥",
|
"autoGeneratorCantGetOpenAIKey": "无法获得 AI 密钥",
|
||||||
"autoGeneratorRewrite": "重写",
|
"autoGeneratorRewrite": "重写",
|
||||||
"smartEdit": "AI 助手",
|
"smartEdit": "AI 助手",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "修正拼写",
|
"smartEditFixSpelling": "修正拼写",
|
||||||
"warning": "⚠️ AI 可能不准确或具有误导性.",
|
"warning": "⚠️ AI 可能不准确或具有误导性.",
|
||||||
"smartEditSummarize": "总结",
|
"smartEditSummarize": "总结",
|
||||||
"smartEditImproveWriting": "提高写作水平",
|
"smartEditImproveWriting": "提高写作水平",
|
||||||
"smartEditMakeLonger": "丰富内容",
|
"smartEditMakeLonger": "丰富内容",
|
||||||
"smartEditCouldNotFetchResult": "无法从 OpenAI 获取到结果",
|
"smartEditCouldNotFetchResult": "无法从 AI 获取到结果",
|
||||||
"smartEditCouldNotFetchKey": "无法获取到 OpenAI 密钥",
|
"smartEditCouldNotFetchKey": "无法获取到 AI 密钥",
|
||||||
"smartEditDisabled": "在设置中连接 OpenAI",
|
"smartEditDisabled": "在设置中连接 AI",
|
||||||
"discardResponse": "您是否要放弃 AI 继续写作?",
|
"discardResponse": "您是否要放弃 AI 继续写作?",
|
||||||
"createInlineMathEquation": "创建方程",
|
"createInlineMathEquation": "创建方程",
|
||||||
"fonts": "字体",
|
"fonts": "字体",
|
||||||
@ -1156,8 +1149,8 @@
|
|||||||
"placeholder": "输入图片网址"
|
"placeholder": "输入图片网址"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "从 OpenAI 生成图像",
|
"label": "从 AI 生成图像",
|
||||||
"placeholder": "请输入 OpenAI 生成图像的提示"
|
"placeholder": "请输入 AI 生成图像的提示"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "从 Stability AI 生成图像",
|
"label": "从 Stability AI 生成图像",
|
||||||
@ -1179,7 +1172,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "搜索图像",
|
"searchForAnImage": "搜索图像",
|
||||||
"pleaseInputYourOpenAIKey": "请在设置页面输入您的 OpenAI 密钥",
|
"pleaseInputYourOpenAIKey": "请在设置页面输入您的 AI 密钥",
|
||||||
"pleaseInputYourStabilityAIKey": "请在设置页面输入您的 Stability AI 密钥",
|
"pleaseInputYourStabilityAIKey": "请在设置页面输入您的 Stability AI 密钥",
|
||||||
"saveImageToGallery": "保存图片",
|
"saveImageToGallery": "保存图片",
|
||||||
"failedToAddImageToGallery": "无法将图像添加到图库",
|
"failedToAddImageToGallery": "无法将图像添加到图库",
|
||||||
|
@ -333,15 +333,6 @@
|
|||||||
"loginLabel": "登入",
|
"loginLabel": "登入",
|
||||||
"logoutLabel": "登出"
|
"logoutLabel": "登出"
|
||||||
},
|
},
|
||||||
"keys": {
|
|
||||||
"title": "AI API 金鑰",
|
|
||||||
"openAILabel": "Open AI API 金鑰",
|
|
||||||
"openAITooltip": "以OpenAI API 金鑰使用AI 模型",
|
|
||||||
"openAIHint": "輸入您的 OpenAI API 金鑰",
|
|
||||||
"stabilityAILabel": "Stability API 金鑰",
|
|
||||||
"stabilityAITooltip": "以Stability API 金鑰使用AI 模型",
|
|
||||||
"stabilityAIHint": "輸入您的Stability API 金鑰"
|
|
||||||
},
|
|
||||||
"description": "自訂您的個人資料、管理帳戶安全性和 AI API 金鑰,或登入您的帳號"
|
"description": "自訂您的個人資料、管理帳戶安全性和 AI API 金鑰,或登入您的帳號"
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
@ -542,7 +533,7 @@
|
|||||||
"email": "電子郵件",
|
"email": "電子郵件",
|
||||||
"tooltipSelectIcon": "選擇圖示",
|
"tooltipSelectIcon": "選擇圖示",
|
||||||
"selectAnIcon": "選擇圖示",
|
"selectAnIcon": "選擇圖示",
|
||||||
"pleaseInputYourOpenAIKey": "請輸入您的 OpenAI 金鑰",
|
"pleaseInputYourOpenAIKey": "請輸入您的 AI 金鑰",
|
||||||
"pleaseInputYourStabilityAIKey": "請輸入您的 Stability AI 金鑰",
|
"pleaseInputYourStabilityAIKey": "請輸入您的 Stability AI 金鑰",
|
||||||
"clickToLogout": "點選以登出目前使用者"
|
"clickToLogout": "點選以登出目前使用者"
|
||||||
},
|
},
|
||||||
@ -820,23 +811,23 @@
|
|||||||
"referencedGrid": "已連結的網格",
|
"referencedGrid": "已連結的網格",
|
||||||
"referencedCalendar": "已連結的日曆",
|
"referencedCalendar": "已連結的日曆",
|
||||||
"referencedDocument": "已連結的文件",
|
"referencedDocument": "已連結的文件",
|
||||||
"autoGeneratorMenuItemName": "OpenAI 寫手",
|
"autoGeneratorMenuItemName": "AI 寫手",
|
||||||
"autoGeneratorTitleName": "OpenAI:讓 AI 撰寫任何內容……",
|
"autoGeneratorTitleName": "AI:讓 AI 撰寫任何內容……",
|
||||||
"autoGeneratorLearnMore": "瞭解更多",
|
"autoGeneratorLearnMore": "瞭解更多",
|
||||||
"autoGeneratorGenerate": "產生",
|
"autoGeneratorGenerate": "產生",
|
||||||
"autoGeneratorHintText": "問 OpenAI……",
|
"autoGeneratorHintText": "問 AI……",
|
||||||
"autoGeneratorCantGetOpenAIKey": "無法取得 OpenAI 金鑰",
|
"autoGeneratorCantGetOpenAIKey": "無法取得 AI 金鑰",
|
||||||
"autoGeneratorRewrite": "改寫",
|
"autoGeneratorRewrite": "改寫",
|
||||||
"smartEdit": "AI 助理",
|
"smartEdit": "AI 助理",
|
||||||
"openAI": "OpenAI",
|
"aI": "AI",
|
||||||
"smartEditFixSpelling": "修正拼寫",
|
"smartEditFixSpelling": "修正拼寫",
|
||||||
"warning": "⚠️ AI 的回覆可能不準確或具有誤導性。",
|
"warning": "⚠️ AI 的回覆可能不準確或具有誤導性。",
|
||||||
"smartEditSummarize": "總結",
|
"smartEditSummarize": "總結",
|
||||||
"smartEditImproveWriting": "提高寫作水準",
|
"smartEditImproveWriting": "提高寫作水準",
|
||||||
"smartEditMakeLonger": "做得更長",
|
"smartEditMakeLonger": "做得更長",
|
||||||
"smartEditCouldNotFetchResult": "無法取得 OpenAI 的結果",
|
"smartEditCouldNotFetchResult": "無法取得 AI 的結果",
|
||||||
"smartEditCouldNotFetchKey": "無法取得 OpenAI 金鑰",
|
"smartEditCouldNotFetchKey": "無法取得 AI 金鑰",
|
||||||
"smartEditDisabled": "在設定連結 OpenAI ",
|
"smartEditDisabled": "在設定連結 AI ",
|
||||||
"discardResponse": "確定捨棄 AI 的回覆?",
|
"discardResponse": "確定捨棄 AI 的回覆?",
|
||||||
"createInlineMathEquation": "建立公式",
|
"createInlineMathEquation": "建立公式",
|
||||||
"fonts": "字型",
|
"fonts": "字型",
|
||||||
@ -951,8 +942,8 @@
|
|||||||
"placeholder": "輸入圖片網址"
|
"placeholder": "輸入圖片網址"
|
||||||
},
|
},
|
||||||
"ai": {
|
"ai": {
|
||||||
"label": "由 OpenAI 生成圖片",
|
"label": "由 AI 生成圖片",
|
||||||
"placeholder": "請輸入提示讓 OpenAI 生成圖片"
|
"placeholder": "請輸入提示讓 AI 生成圖片"
|
||||||
},
|
},
|
||||||
"stability_ai": {
|
"stability_ai": {
|
||||||
"label": "由 Stability AI 生成圖片",
|
"label": "由 Stability AI 生成圖片",
|
||||||
@ -974,7 +965,7 @@
|
|||||||
"label": "Unsplash"
|
"label": "Unsplash"
|
||||||
},
|
},
|
||||||
"searchForAnImage": "搜尋圖片",
|
"searchForAnImage": "搜尋圖片",
|
||||||
"pleaseInputYourOpenAIKey": "請在設定頁面輸入您的 OpenAI 金鑰",
|
"pleaseInputYourOpenAIKey": "請在設定頁面輸入您的 AI 金鑰",
|
||||||
"pleaseInputYourStabilityAIKey": "請在設定頁面輸入您的 Stability AI 金鑰",
|
"pleaseInputYourStabilityAIKey": "請在設定頁面輸入您的 Stability AI 金鑰",
|
||||||
"saveImageToGallery": "儲存圖片",
|
"saveImageToGallery": "儲存圖片",
|
||||||
"failedToAddImageToGallery": "無法將圖片新增到相簿",
|
"failedToAddImageToGallery": "無法將圖片新增到相簿",
|
||||||
|
44
frontend/rust-lib/Cargo.lock
generated
44
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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -718,7 +718,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"again",
|
"again",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -768,7 +768,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
"collab-rt-entity",
|
"collab-rt-entity",
|
||||||
@ -780,7 +780,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -823,7 +823,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -847,7 +847,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-database"
|
name = "collab-database"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -877,7 +877,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-document"
|
name = "collab-document"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -897,7 +897,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-entity"
|
name = "collab-entity"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -912,7 +912,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-folder"
|
name = "collab-folder"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -950,7 +950,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-plugins"
|
name = "collab-plugins"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -989,7 +989,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -1014,7 +1014,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1031,7 +1031,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-user"
|
name = "collab-user"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=20f7814#20f7814beb265ea76e85ea7a9d392b9fe18a2a63"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=43b1c98435d63c225229c9def79f2f5213d6eaf1#43b1c98435d63c225229c9def79f2f5213d6eaf1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -1352,7 +1352,7 @@ 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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -1918,6 +1918,7 @@ dependencies = [
|
|||||||
"client-api",
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-entity",
|
"collab-entity",
|
||||||
|
"flowy-error",
|
||||||
"lib-infra",
|
"lib-infra",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2299,12 +2300,16 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"flowy-codegen",
|
||||||
|
"flowy-derive",
|
||||||
"flowy-error",
|
"flowy-error",
|
||||||
|
"flowy-notification",
|
||||||
"flowy-sqlite",
|
"flowy-sqlite",
|
||||||
"flowy-storage-pub",
|
"flowy-storage-pub",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"lib-infra",
|
"lib-infra",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
|
"protobuf",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -2339,6 +2344,7 @@ dependencies = [
|
|||||||
"base64 0.21.5",
|
"base64 0.21.5",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"client-api",
|
||||||
"collab",
|
"collab",
|
||||||
"collab-database",
|
"collab-database",
|
||||||
"collab-document",
|
"collab-document",
|
||||||
@ -2695,7 +2701,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -2712,7 +2718,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -3077,7 +3083,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -5223,7 +5229,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=76a8993cac42ff89acb507cdb99942cac7c9bfd0#76a8993cac42ff89acb507cdb99942cac7c9bfd0"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=c2a839ba8bf9ead44679eb08f3a9680467b767ca#c2a839ba8bf9ead44679eb08f3a9680467b767ca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
|
@ -89,7 +89,7 @@ collab-document = { version = "0.2" }
|
|||||||
collab-database = { version = "0.2" }
|
collab-database = { version = "0.2" }
|
||||||
collab-plugins = { version = "0.2" }
|
collab-plugins = { version = "0.2" }
|
||||||
collab-user = { version = "0.2" }
|
collab-user = { version = "0.2" }
|
||||||
yrs = "0.19.1"
|
yrs = "0.19.2"
|
||||||
validator = { version = "0.16.1", features = ["derive"] }
|
validator = { version = "0.16.1", features = ["derive"] }
|
||||||
tokio-util = "0.7.11"
|
tokio-util = "0.7.11"
|
||||||
zip = "2.1.3"
|
zip = "2.1.3"
|
||||||
@ -99,8 +99,8 @@ zip = "2.1.3"
|
|||||||
# Run the script.add_workspace_members:
|
# Run the script.add_workspace_members:
|
||||||
# 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 = "76a8993cac42ff89acb507cdb99942cac7c9bfd0" }
|
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c2a839ba8bf9ead44679eb08f3a9680467b767ca" }
|
||||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "76a8993cac42ff89acb507cdb99942cac7c9bfd0" }
|
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "c2a839ba8bf9ead44679eb08f3a9680467b767ca" }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 0
|
opt-level = 0
|
||||||
@ -135,13 +135,13 @@ rocksdb = { git = "https://github.com/LucasXu0/rust-rocksdb", rev = "21cf4a23ec1
|
|||||||
# To switch to the local path, run:
|
# To switch to the local path, run:
|
||||||
# scripts/tool/update_collab_source.sh
|
# scripts/tool/update_collab_source.sh
|
||||||
# ⚠️⚠️⚠️️
|
# ⚠️⚠️⚠️️
|
||||||
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||||
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||||
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||||
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||||
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||||
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||||
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "20f7814" }
|
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "43b1c98435d63c225229c9def79f2f5213d6eaf1" }
|
||||||
|
|
||||||
# Working directory: frontend
|
# Working directory: frontend
|
||||||
# To update the commit ID, run:
|
# To update the commit ID, run:
|
||||||
|
@ -226,6 +226,10 @@ impl ChatManager {
|
|||||||
chat.index_file(file_path).await?;
|
chat.index_file(file_path).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn local_ai_purchased(&self) {
|
||||||
|
// TODO(nathan): enable local ai
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_chat(conn: DBConnection, chat_id: &str) -> FlowyResult<()> {
|
fn save_chat(conn: DBConnection, chat_id: &str) -> FlowyResult<()> {
|
||||||
|
@ -11,7 +11,7 @@ use lib_infra::isolate_stream::IsolateSink;
|
|||||||
|
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tracing::{error, trace};
|
use tracing::trace;
|
||||||
|
|
||||||
pub struct AITools {
|
pub struct AITools {
|
||||||
tasks: Arc<DashMap<String, tokio::sync::mpsc::Sender<()>>>,
|
tasks: Arc<DashMap<String, tokio::sync::mpsc::Sender<()>>>,
|
||||||
@ -83,54 +83,59 @@ impl ToolTask {
|
|||||||
pub async fn start(mut self) {
|
pub async fn start(mut self) {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut sink = IsolateSink::new(Isolate::new(self.context.stream_port));
|
let mut sink = IsolateSink::new(Isolate::new(self.context.stream_port));
|
||||||
match self.cloud_service.upgrade() {
|
|
||||||
None => {},
|
if let Some(cloud_service) = self.cloud_service.upgrade() {
|
||||||
Some(cloud_service) => {
|
let complete_type = match self.context.completion_type {
|
||||||
let complete_type = match self.context.completion_type {
|
CompletionTypePB::UnknownCompletionType | CompletionTypePB::ImproveWriting => {
|
||||||
CompletionTypePB::UnknownCompletionType => CompletionType::ImproveWriting,
|
CompletionType::ImproveWriting
|
||||||
CompletionTypePB::ImproveWriting => CompletionType::ImproveWriting,
|
},
|
||||||
CompletionTypePB::SpellingAndGrammar => CompletionType::SpellingAndGrammar,
|
CompletionTypePB::SpellingAndGrammar => CompletionType::SpellingAndGrammar,
|
||||||
CompletionTypePB::MakeShorter => CompletionType::MakeShorter,
|
CompletionTypePB::MakeShorter => CompletionType::MakeShorter,
|
||||||
CompletionTypePB::MakeLonger => CompletionType::MakeLonger,
|
CompletionTypePB::MakeLonger => CompletionType::MakeLonger,
|
||||||
CompletionTypePB::ContinueWriting => CompletionType::ContinueWriting,
|
CompletionTypePB::ContinueWriting => CompletionType::ContinueWriting,
|
||||||
};
|
};
|
||||||
let _ = sink.send("start:".to_string()).await;
|
|
||||||
match cloud_service
|
let _ = sink.send("start:".to_string()).await;
|
||||||
.stream_complete(&self.workspace_id, &self.context.text, complete_type)
|
match cloud_service
|
||||||
.await
|
.stream_complete(&self.workspace_id, &self.context.text, complete_type)
|
||||||
{
|
.await
|
||||||
Ok(mut stream) => loop {
|
{
|
||||||
select! {
|
Ok(mut stream) => loop {
|
||||||
|
select! {
|
||||||
_ = self.stop_rx.recv() => {
|
_ = self.stop_rx.recv() => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
result = stream.next() => {
|
result = stream.next() => {
|
||||||
match result {
|
match result {
|
||||||
Some(Ok(data)) => {
|
Some(Ok(data)) => {
|
||||||
let s = String::from_utf8(data.to_vec()).unwrap_or_default();
|
let s = String::from_utf8(data.to_vec()).unwrap_or_default();
|
||||||
trace!("stream completion data: {}", s);
|
trace!("stream completion data: {}", s);
|
||||||
let _ = sink.send(format!("data:{}", s)).await;
|
let _ = sink.send(format!("data:{}", s)).await;
|
||||||
},
|
},
|
||||||
Some(Err(error)) => {
|
Some(Err(error)) => {
|
||||||
error!("stream error: {}", error);
|
handle_error(&mut sink, FlowyError::from(error)).await;
|
||||||
let _ = sink.send(format!("error:{}", error)).await;
|
return;
|
||||||
return;
|
},
|
||||||
},
|
None => {
|
||||||
None => {
|
let _ = sink.send(format!("finish:{}", self.task_id)).await;
|
||||||
let _ = sink.send(format!("finish:{}", self.task_id)).await;
|
return;
|
||||||
return;
|
},
|
||||||
},
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!("stream complete error: {}", error);
|
handle_error(&mut sink, error).await;
|
||||||
let _ = sink.send(format!("error:{}", error)).await;
|
},
|
||||||
},
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async fn handle_error(sink: &mut IsolateSink, error: FlowyError) {
|
||||||
|
if error.is_ai_response_limit_exceeded() {
|
||||||
|
let _ = sink.send("AI_RESPONSE_LIMIT".to_string()).await;
|
||||||
|
} else {
|
||||||
|
let _ = sink.send(format!("error:{}", error)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -66,6 +66,7 @@ dart = [
|
|||||||
"flowy-folder/dart",
|
"flowy-folder/dart",
|
||||||
"flowy-database2/dart",
|
"flowy-database2/dart",
|
||||||
"flowy-chat/dart",
|
"flowy-chat/dart",
|
||||||
|
"flowy-storage/dart",
|
||||||
]
|
]
|
||||||
ts = [
|
ts = [
|
||||||
"flowy-user/tauri_ts",
|
"flowy-user/tauri_ts",
|
||||||
@ -74,6 +75,7 @@ ts = [
|
|||||||
"flowy-database2/ts",
|
"flowy-database2/ts",
|
||||||
"flowy-config/tauri_ts",
|
"flowy-config/tauri_ts",
|
||||||
"flowy-chat/tauri_ts",
|
"flowy-chat/tauri_ts",
|
||||||
|
"flowy-storage/tauri_ts",
|
||||||
]
|
]
|
||||||
openssl_vendored = ["flowy-sqlite/openssl_vendored"]
|
openssl_vendored = ["flowy-sqlite/openssl_vendored"]
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ pub fn create_log_filter(
|
|||||||
filters.push(format!("appflowy_local_ai={}", level));
|
filters.push(format!("appflowy_local_ai={}", level));
|
||||||
filters.push(format!("appflowy_plugin={}", level));
|
filters.push(format!("appflowy_plugin={}", level));
|
||||||
filters.push(format!("flowy_ai={}", level));
|
filters.push(format!("flowy_ai={}", level));
|
||||||
|
filters.push(format!("flowy_storage={}", 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));
|
||||||
|
@ -418,7 +418,7 @@ impl DatabaseCloudService for ServerProvider {
|
|||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
object_id: &str,
|
object_id: &str,
|
||||||
summary_row: SummaryRowContent,
|
summary_row: SummaryRowContent,
|
||||||
) -> FutureResult<String, Error> {
|
) -> FutureResult<String, FlowyError> {
|
||||||
let workspace_id = workspace_id.to_string();
|
let workspace_id = workspace_id.to_string();
|
||||||
let server = self.get_server();
|
let server = self.get_server();
|
||||||
let object_id = object_id.to_string();
|
let object_id = object_id.to_string();
|
||||||
@ -435,7 +435,7 @@ impl DatabaseCloudService for ServerProvider {
|
|||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
translate_row: TranslateRowContent,
|
translate_row: TranslateRowContent,
|
||||||
language: &str,
|
language: &str,
|
||||||
) -> FutureResult<TranslateRowResponse, Error> {
|
) -> FutureResult<TranslateRowResponse, FlowyError> {
|
||||||
let workspace_id = workspace_id.to_string();
|
let workspace_id = workspace_id.to_string();
|
||||||
let server = self.get_server();
|
let server = self.get_server();
|
||||||
let language = language.to_string();
|
let language = language.to_string();
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
use client_api::entity::billing_dto::SubscriptionPlan;
|
||||||
use tracing::{event, trace};
|
use tracing::{event, trace};
|
||||||
|
|
||||||
use collab_entity::CollabType;
|
use collab_entity::CollabType;
|
||||||
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
use collab_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||||
|
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};
|
||||||
@ -24,6 +26,7 @@ pub(crate) struct UserStatusCallbackImpl {
|
|||||||
pub(crate) document_manager: Arc<DocumentManager>,
|
pub(crate) document_manager: Arc<DocumentManager>,
|
||||||
pub(crate) server_provider: Arc<ServerProvider>,
|
pub(crate) server_provider: Arc<ServerProvider>,
|
||||||
pub(crate) storage_manager: Arc<StorageManager>,
|
pub(crate) storage_manager: Arc<StorageManager>,
|
||||||
|
pub(crate) chat_manager: Arc<ChatManager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserStatusCallback for UserStatusCallbackImpl {
|
impl UserStatusCallback for UserStatusCallbackImpl {
|
||||||
@ -216,4 +219,31 @@ impl UserStatusCallback for UserStatusCallbackImpl {
|
|||||||
self.collab_builder.update_network(reachable);
|
self.collab_builder.update_network(reachable);
|
||||||
self.storage_manager.update_network_reachable(reachable);
|
self.storage_manager.update_network_reachable(reachable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn did_update_plans(&self, plans: Vec<SubscriptionPlan>) {
|
||||||
|
let mut storage_plan_changed = false;
|
||||||
|
let mut local_ai_enabled = false;
|
||||||
|
for plan in &plans {
|
||||||
|
match plan {
|
||||||
|
SubscriptionPlan::Pro | SubscriptionPlan::Team => storage_plan_changed = true,
|
||||||
|
SubscriptionPlan::AiLocal => local_ai_enabled = true,
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if storage_plan_changed {
|
||||||
|
self.storage_manager.enable_storage_write_access();
|
||||||
|
}
|
||||||
|
|
||||||
|
if local_ai_enabled {
|
||||||
|
self.chat_manager.local_ai_purchased();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn did_update_storage_limitation(&self, can_write: bool) {
|
||||||
|
if can_write {
|
||||||
|
self.storage_manager.enable_storage_write_access();
|
||||||
|
} else {
|
||||||
|
self.storage_manager.disable_storage_write_access();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,6 +241,7 @@ impl AppFlowyCore {
|
|||||||
document_manager: document_manager.clone(),
|
document_manager: document_manager.clone(),
|
||||||
server_provider: server_provider.clone(),
|
server_provider: server_provider.clone(),
|
||||||
storage_manager: storage_manager.clone(),
|
storage_manager: storage_manager.clone(),
|
||||||
|
chat_manager: chat_manager.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let collab_interact_impl = CollabInteractImpl {
|
let collab_interact_impl = CollabInteractImpl {
|
||||||
|
@ -11,3 +11,4 @@ collab-entity = { workspace = true }
|
|||||||
collab = { workspace = true }
|
collab = { workspace = true }
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
client-api = { workspace = true }
|
client-api = { workspace = true }
|
||||||
|
flowy-error = { workspace = true }
|
@ -2,6 +2,7 @@ use anyhow::Error;
|
|||||||
pub use client_api::entity::ai_dto::{TranslateItem, TranslateRowResponse};
|
pub use client_api::entity::ai_dto::{TranslateItem, TranslateRowResponse};
|
||||||
use collab::core::collab::DataSource;
|
use collab::core::collab::DataSource;
|
||||||
use collab_entity::CollabType;
|
use collab_entity::CollabType;
|
||||||
|
use flowy_error::FlowyError;
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@ -40,14 +41,14 @@ pub trait DatabaseCloudService: Send + Sync {
|
|||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
object_id: &str,
|
object_id: &str,
|
||||||
summary_row: SummaryRowContent,
|
summary_row: SummaryRowContent,
|
||||||
) -> FutureResult<String, Error>;
|
) -> FutureResult<String, FlowyError>;
|
||||||
|
|
||||||
fn translate_database_row(
|
fn translate_database_row(
|
||||||
&self,
|
&self,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
translate_row: TranslateRowContent,
|
translate_row: TranslateRowContent,
|
||||||
language: &str,
|
language: &str,
|
||||||
) -> FutureResult<TranslateRowResponse, Error>;
|
) -> FutureResult<TranslateRowResponse, FlowyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DatabaseSnapshot {
|
pub struct DatabaseSnapshot {
|
||||||
|
@ -17,10 +17,11 @@ flowy-derive.workspace = true
|
|||||||
flowy-notification = { workspace = true }
|
flowy-notification = { workspace = true }
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
protobuf.workspace = true
|
protobuf.workspace = true
|
||||||
flowy-error = { workspace = true, features = [
|
flowy-error = { path = "../flowy-error", features = [
|
||||||
"impl_from_dispatch_error",
|
"impl_from_dispatch_error",
|
||||||
"impl_from_collab_database",
|
"impl_from_collab_database",
|
||||||
] }
|
]}
|
||||||
|
|
||||||
lib-dispatch = { workspace = true }
|
lib-dispatch = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["sync"] }
|
tokio = { workspace = true, features = ["sync"] }
|
||||||
bytes.workspace = true
|
bytes.workspace = true
|
||||||
|
@ -104,6 +104,17 @@ pub struct UploadedFilePB {
|
|||||||
pub local_file_path: String,
|
pub local_file_path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, ProtoBuf, Validate)]
|
||||||
|
pub struct DownloadFilePB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
#[validate(url)]
|
||||||
|
pub url: String,
|
||||||
|
|
||||||
|
#[pb(index = 2)]
|
||||||
|
#[validate(custom = "required_valid_path")]
|
||||||
|
pub local_file_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, ProtoBuf)]
|
#[derive(Default, ProtoBuf)]
|
||||||
pub struct CreateDocumentPayloadPB {
|
pub struct CreateDocumentPayloadPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
@ -443,22 +443,22 @@ pub(crate) async fn upload_file_handler(
|
|||||||
} = params.try_into_inner()?;
|
} = params.try_into_inner()?;
|
||||||
|
|
||||||
let manager = upgrade_document(manager)?;
|
let manager = upgrade_document(manager)?;
|
||||||
let url = manager
|
let upload = manager
|
||||||
.upload_file(workspace_id, &document_id, &local_file_path)
|
.upload_file(workspace_id, &document_id, &local_file_path)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(AFPluginData(UploadedFilePB {
|
data_result_ok(UploadedFilePB {
|
||||||
url,
|
url: upload.url,
|
||||||
local_file_path,
|
local_file_path,
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip_all, err)]
|
#[instrument(level = "debug", skip_all, err)]
|
||||||
pub(crate) async fn download_file_handler(
|
pub(crate) async fn download_file_handler(
|
||||||
params: AFPluginData<UploadedFilePB>,
|
params: AFPluginData<DownloadFilePB>,
|
||||||
manager: AFPluginState<Weak<DocumentManager>>,
|
manager: AFPluginState<Weak<DocumentManager>>,
|
||||||
) -> FlowyResult<()> {
|
) -> FlowyResult<()> {
|
||||||
let UploadedFilePB {
|
let DownloadFilePB {
|
||||||
url,
|
url,
|
||||||
local_file_path,
|
local_file_path,
|
||||||
} = params.try_into_inner()?;
|
} = params.try_into_inner()?;
|
||||||
@ -469,10 +469,10 @@ pub(crate) async fn download_file_handler(
|
|||||||
|
|
||||||
// Handler for deleting file
|
// Handler for deleting file
|
||||||
pub(crate) async fn delete_file_handler(
|
pub(crate) async fn delete_file_handler(
|
||||||
params: AFPluginData<UploadedFilePB>,
|
params: AFPluginData<DownloadFilePB>,
|
||||||
manager: AFPluginState<Weak<DocumentManager>>,
|
manager: AFPluginState<Weak<DocumentManager>>,
|
||||||
) -> FlowyResult<()> {
|
) -> FlowyResult<()> {
|
||||||
let UploadedFilePB {
|
let DownloadFilePB {
|
||||||
url,
|
url,
|
||||||
local_file_path,
|
local_file_path,
|
||||||
} = params.try_into_inner()?;
|
} = params.try_into_inner()?;
|
||||||
|
@ -123,9 +123,9 @@ pub enum DocumentEvent {
|
|||||||
|
|
||||||
#[event(input = "UploadFileParamsPB", output = "UploadedFilePB")]
|
#[event(input = "UploadFileParamsPB", output = "UploadedFilePB")]
|
||||||
UploadFile = 15,
|
UploadFile = 15,
|
||||||
#[event(input = "UploadedFilePB")]
|
#[event(input = "DownloadFilePB")]
|
||||||
DownloadFile = 16,
|
DownloadFile = 16,
|
||||||
#[event(input = "UploadedFilePB")]
|
#[event(input = "DownloadFilePB")]
|
||||||
DeleteFile = 17,
|
DeleteFile = 17,
|
||||||
|
|
||||||
#[event(input = "UpdateDocumentAwarenessStatePB")]
|
#[event(input = "UpdateDocumentAwarenessStatePB")]
|
||||||
|
@ -20,7 +20,7 @@ use tracing::{event, instrument};
|
|||||||
use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabBuilderConfig};
|
use collab_integrate::collab_builder::{AppFlowyCollabBuilder, CollabBuilderConfig};
|
||||||
use flowy_document_pub::cloud::DocumentCloudService;
|
use flowy_document_pub::cloud::DocumentCloudService;
|
||||||
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
|
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
|
||||||
use flowy_storage_pub::storage::StorageService;
|
use flowy_storage_pub::storage::{CreatedUpload, StorageService};
|
||||||
use lib_dispatch::prelude::af_spawn;
|
use lib_dispatch::prelude::af_spawn;
|
||||||
|
|
||||||
use crate::document::MutexDocument;
|
use crate::document::MutexDocument;
|
||||||
@ -346,13 +346,12 @@ impl DocumentManager {
|
|||||||
workspace_id: String,
|
workspace_id: String,
|
||||||
document_id: &str,
|
document_id: &str,
|
||||||
local_file_path: &str,
|
local_file_path: &str,
|
||||||
) -> FlowyResult<String> {
|
) -> FlowyResult<CreatedUpload> {
|
||||||
let storage_service = self.storage_service_upgrade()?;
|
let storage_service = self.storage_service_upgrade()?;
|
||||||
let url = storage_service
|
let upload = storage_service
|
||||||
.create_upload(&workspace_id, document_id, local_file_path)
|
.create_upload(&workspace_id, document_id, local_file_path)
|
||||||
.await?
|
.await?;
|
||||||
.url;
|
Ok(upload)
|
||||||
Ok(url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download_file(&self, local_file_path: String, url: String) -> FlowyResult<()> {
|
pub async fn download_file(&self, local_file_path: String, url: String) -> FlowyResult<()> {
|
||||||
|
@ -286,6 +286,18 @@ pub enum ErrorCode {
|
|||||||
|
|
||||||
#[error("Local AI unavailable")]
|
#[error("Local AI unavailable")]
|
||||||
LocalAIUnavailable = 99,
|
LocalAIUnavailable = 99,
|
||||||
|
|
||||||
|
#[error("File storage limit exceeded")]
|
||||||
|
FileStorageLimitExceeded = 100,
|
||||||
|
|
||||||
|
#[error("AI Response limit exceeded")]
|
||||||
|
AIResponseLimitExceeded = 101,
|
||||||
|
|
||||||
|
#[error("Duplicate record")]
|
||||||
|
DuplicateSqliteRecord = 102,
|
||||||
|
|
||||||
|
#[error("Response timeout")]
|
||||||
|
ResponseTimeout = 103,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorCode {
|
impl ErrorCode {
|
||||||
|
@ -72,6 +72,14 @@ impl FlowyError {
|
|||||||
self.code == ErrorCode::LocalVersionNotSupport
|
self.code == ErrorCode::LocalVersionNotSupport
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_file_limit_exceeded(&self) -> bool {
|
||||||
|
self.code == ErrorCode::FileStorageLimitExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_ai_response_limit_exceeded(&self) -> bool {
|
||||||
|
self.code == ErrorCode::AIResponseLimitExceeded
|
||||||
|
}
|
||||||
|
|
||||||
static_flowy_error!(internal, ErrorCode::Internal);
|
static_flowy_error!(internal, ErrorCode::Internal);
|
||||||
static_flowy_error!(record_not_found, ErrorCode::RecordNotFound);
|
static_flowy_error!(record_not_found, ErrorCode::RecordNotFound);
|
||||||
static_flowy_error!(workspace_initialize, ErrorCode::WorkspaceInitializeError);
|
static_flowy_error!(workspace_initialize, ErrorCode::WorkspaceInitializeError);
|
||||||
@ -120,6 +128,8 @@ impl FlowyError {
|
|||||||
static_flowy_error!(workspace_data_not_match, ErrorCode::WorkspaceDataNotMatch);
|
static_flowy_error!(workspace_data_not_match, ErrorCode::WorkspaceDataNotMatch);
|
||||||
static_flowy_error!(local_ai, ErrorCode::LocalAIError);
|
static_flowy_error!(local_ai, ErrorCode::LocalAIError);
|
||||||
static_flowy_error!(local_ai_unavailable, ErrorCode::LocalAIUnavailable);
|
static_flowy_error!(local_ai_unavailable, ErrorCode::LocalAIUnavailable);
|
||||||
|
static_flowy_error!(response_timeout, ErrorCode::ResponseTimeout);
|
||||||
|
static_flowy_error!(file_storage_limit, ErrorCode::FileStorageLimitExceeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::convert::From<ErrorCode> for FlowyError {
|
impl std::convert::From<ErrorCode> for FlowyError {
|
||||||
@ -188,3 +198,9 @@ impl From<tokio::sync::oneshot::error::RecvError> for FlowyError {
|
|||||||
FlowyError::internal().with_context(e)
|
FlowyError::internal().with_context(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<String> for FlowyError {
|
||||||
|
fn from(e: String) -> Self {
|
||||||
|
FlowyError::internal().with_context(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,6 +24,8 @@ impl From<AppResponseError> for FlowyError {
|
|||||||
AppErrorCode::UserUnAuthorized => ErrorCode::UserUnauthorized,
|
AppErrorCode::UserUnAuthorized => ErrorCode::UserUnauthorized,
|
||||||
AppErrorCode::WorkspaceLimitExceeded => ErrorCode::WorkspaceLimitExceeded,
|
AppErrorCode::WorkspaceLimitExceeded => ErrorCode::WorkspaceLimitExceeded,
|
||||||
AppErrorCode::WorkspaceMemberLimitExceeded => ErrorCode::WorkspaceMemberLimitExceeded,
|
AppErrorCode::WorkspaceMemberLimitExceeded => ErrorCode::WorkspaceMemberLimitExceeded,
|
||||||
|
AppErrorCode::AIResponseLimitExceeded => ErrorCode::AIResponseLimitExceeded,
|
||||||
|
AppErrorCode::FileStorageLimitExceeded => ErrorCode::FileStorageLimitExceeded,
|
||||||
_ => ErrorCode::Internal,
|
_ => ErrorCode::Internal,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13,7 +13,11 @@ pub mod reqwest;
|
|||||||
#[cfg(feature = "impl_from_sqlite")]
|
#[cfg(feature = "impl_from_sqlite")]
|
||||||
pub mod database;
|
pub mod database;
|
||||||
|
|
||||||
#[cfg(feature = "impl_from_collab_document")]
|
#[cfg(any(
|
||||||
|
feature = "impl_from_collab_document",
|
||||||
|
feature = "impl_from_collab_folder",
|
||||||
|
feature = "impl_from_collab_database"
|
||||||
|
))]
|
||||||
pub mod collab;
|
pub mod collab;
|
||||||
|
|
||||||
#[cfg(feature = "impl_from_collab_persistence")]
|
#[cfg(feature = "impl_from_collab_persistence")]
|
||||||
|
@ -1098,7 +1098,7 @@ impl FolderManager {
|
|||||||
|
|
||||||
/// Get the publish info of the view with the given view id.
|
/// Get the publish info of the view with the given view id.
|
||||||
/// The publish info contains the namespace and publish_name of the view.
|
/// The publish info contains the namespace and publish_name of the view.
|
||||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
pub async fn get_publish_info(&self, view_id: &str) -> FlowyResult<PublishInfoResponse> {
|
pub async fn get_publish_info(&self, view_id: &str) -> FlowyResult<PublishInfoResponse> {
|
||||||
let publish_info = self.cloud_service.get_publish_info(view_id).await?;
|
let publish_info = self.cloud_service.get_publish_info(view_id).await?;
|
||||||
Ok(publish_info)
|
Ok(publish_info)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user