Merge branch 'main' into feat/link_preview

This commit is contained in:
Morn 2025-04-09 11:23:22 +08:00
commit 136e0dbb7d
184 changed files with 8914 additions and 1730 deletions

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a database and add a linked database view
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
@ -29,6 +30,11 @@ void main() {
await tester.tapHidePropertyButton();
tester.noFieldWithName('New field 1');
// create another field, New field 1 to be hidden still
await tester.tapNewPropertyButton();
await tester.dismissFieldEditor();
tester.noFieldWithName('New field 1');
// go back to inline database view, expect field to be shown
await tester.tapTabBarLinkedViewByViewName('Untitled');
tester.findFieldWithName('New field 1');
@ -60,5 +66,40 @@ void main() {
await tester.tapDatabaseSortButton();
await tester.tapCreateSortByFieldType(FieldType.RichText, "New field 1");
});
testWidgets('field cell width', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a database and add a linked database view
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
// create a field
await tester.scrollToRight(find.byType(GridPage));
await tester.tapNewPropertyButton();
await tester.renameField('New field 1');
await tester.dismissFieldEditor();
// check the width of the field
expect(tester.getFieldWidth('New field 1'), 150);
// change the width of the field
await tester.changeFieldWidth('New field 1', 200);
expect(tester.getFieldWidth('New field 1'), 205);
// create another field, New field 1 to be same width
await tester.tapNewPropertyButton();
await tester.dismissFieldEditor();
expect(tester.getFieldWidth('New field 1'), 205);
// go back to inline database view, expect New field 1 to be 150px
await tester.tapTabBarLinkedViewByViewName('Untitled');
expect(tester.getFieldWidth('New field 1'), 150);
// go back to linked database view, expect New field 1 to be 205px
await tester.tapTabBarLinkedViewByViewName('Grid');
expect(tester.getFieldWidth('New field 1'), 205);
});
});
}

View file

@ -942,6 +942,31 @@ extension AppFlowyDatabaseTest on WidgetTester {
await pumpAndSettle(const Duration(milliseconds: 200));
}
Future<void> changeFieldWidth(String fieldName, double width) async {
final field = find.byWidgetPredicate(
(widget) => widget is GridFieldCell && widget.fieldInfo.name == fieldName,
);
await hoverOnWidget(
field,
onHover: () async {
final dragHandle = find.descendant(
of: field,
matching: find.byType(DragToExpandLine),
);
await drag(dragHandle, Offset(width - getSize(field).width, 0));
await pumpAndSettle(const Duration(milliseconds: 200));
},
);
}
double getFieldWidth(String fieldName) {
final field = find.byWidgetPredicate(
(widget) => widget is GridFieldCell && widget.fieldInfo.name == fieldName,
);
return getSize(field).width;
}
Future<void> findDateEditor(dynamic matcher) async {
final finder = find.byType(DateCellEditor);
expect(finder, matcher);

View file

@ -203,7 +203,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
);
showToastNotification(
context,
message: LocaleKeys.grid_url_copy.tr(),
message: LocaleKeys.message_copy_success.tr(),
);
}
}

View file

@ -102,7 +102,7 @@ class MobileInlineActionsWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final hasIcon = item.icon != null;
final hasIcon = item.iconBuilder != null;
return Container(
height: 36,
decoration: BoxDecoration(
@ -119,7 +119,7 @@ class MobileInlineActionsWidget extends StatelessWidget {
child: Row(
children: [
if (hasIcon) ...[
item.icon!.call(isSelected),
item.iconBuilder!.call(isSelected),
SizedBox(width: 12),
],
Flexible(

View file

@ -185,7 +185,7 @@ class CopyButton extends StatelessWidget {
if (context.mounted) {
showToastNotification(
context,
message: LocaleKeys.grid_url_copiedNotification.tr(),
message: LocaleKeys.message_copy_success.tr(),
);
}
},

View file

@ -377,7 +377,7 @@ class ChatAIMessagePopup extends StatelessWidget {
if (context.mounted) {
showToastNotification(
context,
message: LocaleKeys.grid_url_copiedNotification.tr(),
message: LocaleKeys.message_copy_success.tr(),
);
}
},

View file

@ -241,6 +241,11 @@ class SelectOptionCellEditorBloc
} else if (!state.selectedOptions
.any((option) => option.id == focusedOptionId)) {
_selectOptionService.select(optionIds: [focusedOptionId]);
emit(
state.copyWith(
clearFilter: true,
),
);
}
}

View file

@ -411,23 +411,28 @@ class FieldController {
/// Listen for field setting changes in the backend.
void _listenOnFieldSettingsChanged() {
FieldInfo? updateFieldSettings(FieldSettingsPB updatedFieldSettings) {
final List<FieldInfo> newFields = fieldInfos;
var updatedField = newFields.firstOrNull;
final newFields = [...fieldInfos];
if (updatedField == null) {
if (newFields.isEmpty) {
return null;
}
final index = newFields
.indexWhere((field) => field.id == updatedFieldSettings.fieldId);
if (index != -1) {
newFields[index] =
newFields[index].copyWith(fieldSettings: updatedFieldSettings);
updatedField = newFields[index];
_fieldNotifier.fieldInfos = newFields;
_fieldSettings
..removeWhere(
(field) => field.fieldId == updatedFieldSettings.fieldId,
)
..add(updatedFieldSettings);
return newFields[index];
}
_fieldNotifier.fieldInfos = newFields;
return updatedField;
return null;
}
_fieldSettingsListener.start(

View file

@ -108,7 +108,7 @@ class _GridFieldCellState extends State<GridFieldCell> {
top: 0,
bottom: 0,
right: 0,
child: _DragToExpandLine(),
child: DragToExpandLine(),
);
return _GridHeaderCellContainer(
@ -158,8 +158,11 @@ class _GridHeaderCellContainer extends StatelessWidget {
}
}
class _DragToExpandLine extends StatelessWidget {
const _DragToExpandLine();
@visibleForTesting
class DragToExpandLine extends StatelessWidget {
const DragToExpandLine({
super.key,
});
@override
Widget build(BuildContext context) {

View file

@ -203,7 +203,7 @@ class MobileURLEditor extends StatelessWidget {
ClipboardData(text: textEditingController.text),
);
Fluttertoast.showToast(
msg: LocaleKeys.grid_url_copiedNotification.tr(),
msg: LocaleKeys.message_copy_success.tr(),
gravity: ToastGravity.BOTTOM,
);
context.pop();

View file

@ -70,13 +70,12 @@ extension InsertDatabase on EditorState {
node,
selection.end.offset,
0,
r'$',
attributes: {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.page.name,
MentionBlockKeys.pageId: view.id,
},
},
MentionBlockKeys.mentionChar,
attributes: MentionBlockKeys.buildMentionPageAttributes(
mentionType: MentionType.page,
pageId: view.id,
blockId: null,
),
);
}

View file

@ -43,13 +43,11 @@ extension PasteFromBlockLink on EditorState {
node,
selection.startIndex,
MentionBlockKeys.mentionChar,
attributes: {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.page.name,
MentionBlockKeys.blockId: blockId,
MentionBlockKeys.pageId: pageId,
},
},
attributes: MentionBlockKeys.buildMentionPageAttributes(
mentionType: MentionType.page,
pageId: pageId,
blockId: blockId,
),
);
await apply(transaction);

View file

@ -8,6 +8,7 @@ import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_svg/flowy_svg.dart';
import 'package:flutter/material.dart';
import 'package:string_validator/string_validator.dart';
@ -113,24 +114,21 @@ class _RawEmojiIconWidgetState extends State<RawEmojiIconWidget> {
try {
switch (widget.emoji.type) {
case FlowyIconType.emoji:
return EmojiText(
emoji: widget.emoji.emoji,
return FlowyText.emoji(
widget.emoji.emoji,
fontSize: widget.emojiSize,
textAlign: TextAlign.justify,
lineHeight: widget.lineHeight,
);
case FlowyIconType.icon:
IconsData iconData =
IconsData.fromJson(jsonDecode(widget.emoji.emoji));
IconsData iconData = IconsData.fromJson(
jsonDecode(widget.emoji.emoji),
);
if (!widget.enableColor) {
iconData = iconData.noColor();
}
/// Under the same width conditions, icons on macOS seem to appear
/// larger than emojis, so 0.9 is used here to slightly reduce the
/// size of the icons
final iconSize =
Platform.isMacOS ? widget.emojiSize * 0.9 : widget.emojiSize;
final iconSize = widget.emojiSize;
return IconWidget(
iconsData: iconData,
size: iconSize,

View file

@ -179,13 +179,6 @@ class ChildPageTransactionHandler extends MentionTransactionHandler {
await duplicatedViewOrFailure.fold(
(newView) async {
final newMentionAttributes = {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.childPage.name,
MentionBlockKeys.pageId: newView.id,
},
};
// The index is the index of the delta, to get the index of the mention character
// in all the text, we need to calculate it based on the deltas before the current delta.
int mentionIndex = 0;
@ -202,7 +195,11 @@ class ChildPageTransactionHandler extends MentionTransactionHandler {
node,
mentionIndex,
MentionBlockKeys.mentionChar.length,
newMentionAttributes,
MentionBlockKeys.buildMentionPageAttributes(
mentionType: MentionType.childPage,
pageId: newView.id,
blockId: null,
),
);
await editorState.apply(
transaction,

View file

@ -192,15 +192,12 @@ class DateTransactionHandler extends MentionTransactionHandler {
),
);
final newMentionAttributes = {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.date.name,
MentionBlockKeys.date: dateTime.toIso8601String(),
MentionBlockKeys.reminderId: reminderId,
MentionBlockKeys.includeTime: data.includeTime,
MentionBlockKeys.reminderOption: data.reminderOption.name,
},
};
final newMentionAttributes = MentionBlockKeys.buildMentionDateAttributes(
date: dateTime.toIso8601String(),
reminderId: reminderId,
reminderOption: data.reminderOption.name,
includeTime: data.includeTime,
);
// The index is the index of the delta, to get the index of the mention character
// in all the text, we need to calculate it based on the deltas before the current delta.

View file

@ -31,12 +31,12 @@ Node dateMentionNode() {
operations: [
TextInsert(
MentionBlockKeys.mentionChar,
attributes: {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.date.name,
MentionBlockKeys.date: DateTime.now().toIso8601String(),
},
},
attributes: MentionBlockKeys.buildMentionDateAttributes(
date: DateTime.now().toIso8601String(),
reminderId: null,
reminderOption: null,
includeTime: false,
),
),
],
),
@ -46,9 +46,9 @@ Node dateMentionNode() {
class MentionBlockKeys {
const MentionBlockKeys._();
static const reminderId = 'reminder_id'; // ReminderID
static const mention = 'mention';
static const type = 'type'; // MentionType, String
static const pageId = 'page_id';
static const blockId = 'block_id';
static const url = 'url';
@ -56,9 +56,42 @@ class MentionBlockKeys {
// Related to Reminder and Date blocks
static const date = 'date'; // Start Date
static const includeTime = 'include_time';
static const reminderId = 'reminder_id'; // ReminderID
static const reminderOption = 'reminder_option';
static const mentionChar = '\$';
static Map<String, dynamic> buildMentionPageAttributes({
required MentionType mentionType,
required String pageId,
required String? blockId,
}) {
return {
MentionBlockKeys.mention: {
MentionBlockKeys.type: mentionType.name,
MentionBlockKeys.pageId: pageId,
if (blockId != null) MentionBlockKeys.blockId: blockId,
},
};
}
static Map<String, dynamic> buildMentionDateAttributes({
required String date,
required String? reminderId,
required String? reminderOption,
required bool includeTime,
}) {
return {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.date.name,
MentionBlockKeys.date: date,
MentionBlockKeys.includeTime: includeTime,
if (reminderId != null) MentionBlockKeys.reminderId: reminderId,
if (reminderOption != null)
MentionBlockKeys.reminderOption: reminderOption,
},
};
}
}
class MentionBlock extends StatelessWidget {

View file

@ -201,16 +201,17 @@ class _MentionDateBlockState extends State<MentionDateBlock> {
(reminderOption == ReminderOption.none ? null : widget.reminderId);
final transaction = widget.editorState.transaction
..formatText(widget.node, widget.index, 1, {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.date.name,
MentionBlockKeys.date: date.toIso8601String(),
MentionBlockKeys.reminderId: rId,
MentionBlockKeys.includeTime: includeTime,
MentionBlockKeys.reminderOption:
reminderOption?.name ?? widget.reminderOption.name,
},
});
..formatText(
widget.node,
widget.index,
1,
MentionBlockKeys.buildMentionDateAttributes(
date: date.toIso8601String(),
reminderId: rId,
includeTime: includeTime,
reminderOption: reminderOption?.name ?? widget.reminderOption.name,
),
);
widget.editorState.apply(transaction, withUpdateSelection: false);

View file

@ -50,12 +50,11 @@ Node pageMentionNode(String viewId) {
operations: [
TextInsert(
MentionBlockKeys.mentionChar,
attributes: {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.page.name,
MentionBlockKeys.pageId: viewId,
},
},
attributes: MentionBlockKeys.buildMentionPageAttributes(
mentionType: MentionType.page,
pageId: viewId,
blockId: null,
),
),
],
),
@ -284,12 +283,11 @@ class _MentionSubPageBlockState extends State<MentionSubPageBlock> {
widget.node,
widget.index,
MentionBlockKeys.mentionChar.length,
{
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.page.name,
MentionBlockKeys.pageId: widget.pageId,
},
},
MentionBlockKeys.buildMentionPageAttributes(
mentionType: MentionType.page,
pageId: widget.pageId,
blockId: null,
),
);
widget.editorState.apply(
@ -383,25 +381,24 @@ Future<void> _handleDoubleTap(
}
final currentViewId = context.read<DocumentBloc>().documentId;
final newViewId = await showPageSelectorSheet(
final newView = await showPageSelectorSheet(
context,
currentViewId: currentViewId,
selectedViewId: viewId,
);
if (newViewId != null) {
if (newView != null) {
// Update this nodes pageId
final transaction = editorState.transaction
..formatText(
node,
index,
1,
{
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.page.name,
MentionBlockKeys.pageId: newViewId,
},
},
MentionBlockKeys.buildMentionPageAttributes(
mentionType: MentionType.page,
pageId: newView.id,
blockId: null,
),
);
await editorState.apply(transaction, withUpdateSelection: false);

View file

@ -46,12 +46,12 @@ extension on EditorState {
selection.start.offset,
0,
MentionBlockKeys.mentionChar,
attributes: {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.date.name,
MentionBlockKeys.date: DateTime.now().toIso8601String(),
},
},
attributes: MentionBlockKeys.buildMentionDateAttributes(
date: DateTime.now().toIso8601String(),
reminderId: null,
reminderOption: null,
includeTime: false,
),
);
await apply(transaction);

View file

@ -568,7 +568,6 @@ class EditorStyleCustomizer {
if (style == null) {
return null;
}
final fontSize = style.fontSize ?? 14.0;
final isLight = Theme.of(context).isLightMode;
final textColor = isLight ? Color(0xFF007296) : Color(0xFF49CFF4);
final underlineColor = isLight ? Color(0x33005A7A) : Color(0x3349CFF4);
@ -578,17 +577,10 @@ class EditorStyleCustomizer {
decoration: TextDecoration.lineThrough,
),
AiWriterBlockKeys.suggestionReplacement => style.copyWith(
color: Colors.transparent,
color: textColor,
decoration: TextDecoration.underline,
decorationColor: underlineColor,
decorationThickness: 1.0,
// hack: https://jtmuller5.medium.com/the-ultimate-guide-to-underlining-text-in-flutter-57936f5c79bb
shadows: [
Shadow(
color: textColor,
offset: Offset(0, -fontSize * 0.2),
),
],
),
_ => style,
};

View file

@ -24,7 +24,7 @@ class InlineChildPageService extends InlineActionsDelegate {
results.add(
InlineActionsMenuItem(
label: LocaleKeys.inlineActions_createPage.tr(args: [search]),
icon: (_) => const FlowySvg(FlowySvgs.add_s),
iconBuilder: (_) => const FlowySvg(FlowySvgs.add_s),
onSelected: (context, editorState, service, replacement) =>
_onSelected(context, editorState, service, replacement, search),
),
@ -71,12 +71,11 @@ class InlineChildPageService extends InlineActionsDelegate {
replacement.$1,
replacement.$2,
MentionBlockKeys.mentionChar,
attributes: {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.childPage.name,
MentionBlockKeys.pageId: view.id,
},
},
attributes: MentionBlockKeys.buildMentionPageAttributes(
mentionType: MentionType.childPage,
pageId: view.id,
blockId: null,
),
);
await editorState.apply(transaction);

View file

@ -122,12 +122,12 @@ class DateReferenceService extends InlineActionsDelegate {
start,
end,
MentionBlockKeys.mentionChar,
attributes: {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.date.name,
MentionBlockKeys.date: date.toIso8601String(),
},
},
attributes: MentionBlockKeys.buildMentionDateAttributes(
date: date.toIso8601String(),
includeTime: false,
reminderId: null,
reminderOption: null,
),
);
await editorState.apply(transaction);

View file

@ -221,12 +221,11 @@ class InlinePageReferenceService extends InlineActionsDelegate {
replace.$1,
replace.$2,
MentionBlockKeys.mentionChar,
attributes: {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.page.name,
MentionBlockKeys.pageId: view.id,
},
},
attributes: MentionBlockKeys.buildMentionPageAttributes(
mentionType: MentionType.page,
pageId: view.id,
blockId: null,
),
);
await editorState.apply(transaction);
@ -235,12 +234,19 @@ class InlinePageReferenceService extends InlineActionsDelegate {
InlineActionsMenuItem _fromView(ViewPB view) => InlineActionsMenuItem(
keywords: [view.nameOrDefault.toLowerCase()],
label: view.nameOrDefault,
icon: (onSelected) => view.icon.value.isNotEmpty
? RawEmojiIconWidget(
emoji: view.icon.toEmojiIconData(),
emojiSize: 14,
)
: view.defaultIcon(),
iconBuilder: (onSelected) {
final child = view.icon.value.isNotEmpty
? RawEmojiIconWidget(
emoji: view.icon.toEmojiIconData(),
emojiSize: 16.0,
lineHeight: 18.0 / 16.0,
)
: view.defaultIcon(size: const Size(16, 16));
return SizedBox(
width: 16,
child: child,
);
},
onSelected: (context, editorState, menu, replace) => insertPage
? _onInsertPageRef(view, context, editorState, replace)
: _onInsertLinkRef(view, context, editorState, menu, replace),

View file

@ -148,14 +148,12 @@ class ReminderReferenceService extends InlineActionsDelegate {
start,
end,
MentionBlockKeys.mentionChar,
attributes: {
MentionBlockKeys.mention: {
MentionBlockKeys.type: MentionType.date.name,
MentionBlockKeys.date: date.toIso8601String(),
MentionBlockKeys.reminderId: reminder.id,
MentionBlockKeys.reminderOption: ReminderOption.atTimeOfEvent.name,
},
},
attributes: MentionBlockKeys.buildMentionDateAttributes(
date: date.toIso8601String(),
reminderId: reminder.id,
reminderOption: ReminderOption.atTimeOfEvent.name,
includeTime: false,
),
);
await editorState.apply(transaction);

View file

@ -12,13 +12,13 @@ typedef SelectItemHandler = void Function(
class InlineActionsMenuItem {
InlineActionsMenuItem({
required this.label,
this.icon,
this.iconBuilder,
this.keywords,
this.onSelected,
});
final String label;
final Widget Function(bool onSelected)? icon;
final Widget Function(bool onSelected)? iconBuilder;
final List<String>? keywords;
final SelectItemHandler? onSelected;
}

View file

@ -92,8 +92,8 @@ class InlineActionsWidget extends StatefulWidget {
class _InlineActionsWidgetState extends State<InlineActionsWidget> {
@override
Widget build(BuildContext context) {
final icon = widget.item.icon;
final hasIcon = icon != null;
final iconBuilder = widget.item.iconBuilder;
final hasIcon = iconBuilder != null;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: SizedBox(
@ -104,7 +104,7 @@ class _InlineActionsWidgetState extends State<InlineActionsWidget> {
text: Row(
children: [
if (hasIcon) ...[
icon.call(widget.isSelected),
iconBuilder.call(widget.isSelected),
SizedBox(width: 12),
],
Flexible(

View file

@ -175,7 +175,7 @@ class ExportTab extends StatelessWidget {
);
showToastNotification(
context,
message: LocaleKeys.grid_url_copiedNotification.tr(),
message: LocaleKeys.message_copy_success.tr(),
);
},
(error) => showToastNotification(context, message: error.msg),

View file

@ -183,7 +183,7 @@ class _PublishedWidgetState extends State<_PublishedWidget> {
showToastNotification(
context,
message: LocaleKeys.grid_url_copy.tr(),
message: LocaleKeys.message_copy_success.tr(),
);
},
onSubmitted: (pathName) {

View file

@ -118,7 +118,7 @@ class _ShareTabContent extends StatelessWidget {
showToastNotification(
context,
message: LocaleKeys.grid_url_copy.tr(),
message: LocaleKeys.message_copy_success.tr(),
);
}
}

View file

@ -1,14 +1,18 @@
import 'dart:io';
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
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/text.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_svg/flowy_svg.dart';
import 'package:url_launcher/url_launcher.dart';
class FlowyErrorPage extends StatelessWidget {
factory FlowyErrorPage.error(
@ -86,7 +90,9 @@ class FlowyErrorPage extends StatelessWidget {
Listener(
behavior: HitTestBehavior.translucent,
onPointerDown: (_) async {
await Clipboard.setData(ClipboardData(text: message));
await getIt<ClipboardService>().setData(
ClipboardServiceData(plainText: message),
);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@ -188,8 +194,8 @@ class StackTracePreview extends StatelessWidget {
"Copy",
),
useIntrinsicWidth: true,
onTap: () => Clipboard.setData(
ClipboardData(text: stackTrace),
onTap: () => getIt<ClipboardService>().setData(
ClipboardServiceData(plainText: stackTrace),
),
),
),
@ -252,18 +258,14 @@ class GitHubRedirectButton extends StatelessWidget {
Widget build(BuildContext context) {
return FlowyButton(
leftIconSize: const Size.square(_height),
text: const FlowyText(
"AppFlowy",
),
text: FlowyText(LocaleKeys.appName.tr()),
useIntrinsicWidth: true,
leftIcon: const Padding(
padding: EdgeInsets.all(4.0),
child: FlowySvg(FlowySvgData('login/github-mark')),
),
onTap: () async {
if (await canLaunchUrl(_gitHubNewBugUri)) {
await launchUrl(_gitHubNewBugUri);
}
await afLaunchUri(_gitHubNewBugUri);
},
);
}

View file

@ -99,6 +99,7 @@ class InitAppWidgetTask extends LaunchTask {
Locale('zh', 'TW'),
Locale('fa'),
Locale('hin'),
Locale('mr','IN'),
],
path: 'assets/translations',
fallbackLocale: const Locale('en'),

View file

@ -73,10 +73,10 @@ Future<Directory> appFlowyApplicationDataDirectory() async {
case IntegrationMode.develop:
final Directory documentsDir = await getApplicationSupportDirectory()
.then((directory) => directory.create());
return Directory(path.join(documentsDir.path, 'data_dev')).create();
return Directory(path.join(documentsDir.path, 'data_dev'));
case IntegrationMode.release:
final Directory documentsDir = await getApplicationSupportDirectory();
return Directory(path.join(documentsDir.path, 'data')).create();
return Directory(path.join(documentsDir.path, 'data'));
case IntegrationMode.unitTest:
case IntegrationMode.integrationTest:
return Directory(path.join(Directory.current.path, '.sandbox'));

View file

@ -8,7 +8,6 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
show SignInPayloadPB, SignUpPayloadPB, UserProfilePB;
import 'package:appflowy_result/appflowy_result.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import '../../../generated/locale_keys.g.dart';
import 'device_id.dart';
@ -65,8 +64,7 @@ class BackendAuthService implements AuthService {
Map<String, String> params = const {},
}) async {
const password = "Guest!@123456";
final uid = uuid();
final userEmail = "$uid@appflowy.io";
final userEmail = "anon@appflowy.io";
final request = SignUpPayloadPB.create()
..name = LocaleKeys.defaultUsername.tr()

View file

@ -1,9 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:local_notifier/local_notifier.dart';
const _appName = "AppFlowy";
/// Manages Local Notifications
///
/// Currently supports:
@ -13,7 +12,7 @@ const _appName = "AppFlowy";
///
class NotificationService {
static Future<void> initialize() async {
await localNotifier.setup(appName: _appName);
await localNotifier.setup(appName: LocaleKeys.appName.tr());
}
}

View file

@ -632,7 +632,11 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
Widget _buildViewIconButton() {
final iconData = widget.view.icon.toEmojiIconData();
final icon = iconData.isNotEmpty
? RawEmojiIconWidget(emoji: iconData, emojiSize: 16.0)
? RawEmojiIconWidget(
emoji: iconData,
emojiSize: 16.0,
lineHeight: 18.0 / 16.0,
)
: Opacity(opacity: 0.6, child: widget.view.defaultIcon());
final Widget child = AppFlowyPopover(

View file

@ -7,6 +7,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/math_equation/math_equation_shortcut.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/toggle/toggle_block_shortcuts.dart';
import 'package:appflowy/shared/error_page/error_page.dart';
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_cubit.dart';
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
@ -21,7 +22,6 @@ import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View file

@ -54,7 +54,7 @@ class SettingsPageSitesEvent {
getIt<ClipboardService>().setData(ClipboardServiceData(plainText: url));
showToastNotification(
context,
message: LocaleKeys.grid_url_copy.tr(),
message: LocaleKeys.message_copy_success.tr(),
);
}
}

View file

@ -3,6 +3,7 @@ import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/env/env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/shared/share/constants.dart';
import 'package:appflowy/shared/error_page/error_page.dart';
import 'package:appflowy/workspace/application/settings/appflowy_cloud_setting_bloc.dart';
import 'package:appflowy/workspace/application/settings/appflowy_cloud_urls_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/_restart_app_button.dart';
@ -19,7 +20,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@ -130,7 +130,7 @@ class CustomAppFlowyCloudView extends StatelessWidget {
final List<Widget> children = [];
children.addAll([
const AppFlowyCloudEnableSync(),
const AppFlowyCloudSyncLogEnabled(),
// const AppFlowyCloudSyncLogEnabled(),
const VSpace(40),
]);

View file

@ -1,12 +1,12 @@
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/error_page/error_page.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.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/widget/buttons/secondary_button.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart';
class ThemeUploadLearnMoreButton extends StatelessWidget {

View file

@ -48,6 +48,8 @@ String languageFromLocale(Locale locale) {
default:
return locale.languageCode;
}
case "mr":
return "मराठी";
case "he":
return "עברית";
case "hu":
@ -79,7 +81,7 @@ String languageFromLocale(Locale locale) {
case "ur":
return "اردو";
case "hin":
return "हिन्दी";
return "हिन्दी";
}
// If not found then the language code will be displayed
return locale.languageCode;

View file

@ -311,14 +311,16 @@
"questionBubble": {
"shortcuts": "الاختصارات",
"whatsNew": "ما هو الجديد؟",
"help": "المساعدة والدعم",
"helpAndDocumentation": "المساعدة والتوثيق",
"getSupport": "احصل على الدعم",
"markdown": "Markdown",
"debug": {
"name": "معلومات التصحيح",
"success": "تم نسخ معلومات التصحيح إلى الحافظة!",
"fail": "تعذر نسخ معلومات التصحيح إلى الحافظة"
},
"feedback": "تعليق"
"feedback": "تعليق",
"help": "المساعدة والدعم"
},
"menuAppHeader": {
"moreButtonToolTip": "إزالة وإعادة تسمية والمزيد...",
@ -513,6 +515,8 @@
"settings": "إعدادات",
"members": "الأعضاء",
"trash": "سلة المحذوفات",
"helpAndDocumentation": "المساعدة والتوثيق",
"getSupport": "احصل على الدعم",
"helpAndSupport": "المساعدة والدعم"
},
"sites": {
@ -1672,8 +1676,7 @@
"url": {
"launch": "فتح في المتصفح",
"copy": "إنسخ الرابط",
"textFieldHint": "أدخل عنوان URL",
"copiedNotification": "تمت نسخها إلى الحافظة!"
"textFieldHint": "أدخل عنوان URL"
},
"relation": {
"relatedDatabasePlaceLabel": "قاعدة البيانات ذات الصلة",

View file

@ -133,14 +133,14 @@
"questionBubble": {
"shortcuts": "Dreceres",
"whatsNew": "Què hi ha de nou?",
"help": "Ajuda i Suport",
"markdown": "Reducció",
"debug": {
"name": "Informació de depuració",
"success": "S'ha copiat la informació de depuració!",
"fail": "No es pot copiar la informació de depuració"
},
"feedback": "Feedback"
"feedback": "Feedback",
"help": "Ajuda i Suport"
},
"menuAppHeader": {
"moreButtonToolTip": "Suprimeix, canvia el nom i més...",

View file

@ -170,14 +170,14 @@
"questionBubble": {
"shortcuts": "کورتە ڕێگاکان",
"whatsNew": "نوێترین",
"help": "پشتیوانی و یارمەتی",
"markdown": "Markdown",
"debug": {
"name": "زانیاری دیباگ",
"success": "زانیارییەکانی دیباگ کۆپی کراون بۆ کلیپبۆرد!",
"fail": "ناتوانرێت زانیارییەکانی دیباگ کۆپی بکات بۆ کلیپبۆرد"
},
"feedback": "فیدباک"
"feedback": "فیدباک",
"help": "پشتیوانی و یارمەتی"
},
"menuAppHeader": {
"moreButtonToolTip": "سڕینەوە، گۆڕینی ناو، و زۆر شتی تر...",

View file

@ -134,14 +134,14 @@
"questionBubble": {
"shortcuts": "Klávesové zkratky",
"whatsNew": "Co je nového?",
"help": "Pomoc a podpora",
"markdown": "Markdown",
"debug": {
"name": "Debug informace",
"success": "Debug informace zkopírovány do schránky!",
"fail": "Nepodařilo se zkopáí"
},
"feedback": "Zpětná vazba"
"feedback": "Zpětná vazba",
"help": "Pomoc a podpora"
},
"menuAppHeader": {
"moreButtonToolTip": "Smazat, přejmenovat, a další...",

View file

@ -252,14 +252,14 @@
"questionBubble": {
"shortcuts": "Tastenkürzel",
"whatsNew": "Was gibt es Neues?",
"help": "Hilfe & Support",
"markdown": "Markdown",
"debug": {
"name": "Debug-Informationen",
"success": "Debug-Informationen in die Zwischenablage kopiert!",
"fail": "Debug-Informationen konnten nicht in die Zwischenablage kopiert werden"
},
"feedback": "Feedback"
"feedback": "Feedback",
"help": "Hilfe & Support"
},
"menuAppHeader": {
"moreButtonToolTip": "Entfernen, umbenennen und mehr...",
@ -1625,8 +1625,7 @@
"url": {
"launch": "Im Browser öffnen",
"copy": "Webadresse kopieren",
"textFieldHint": "Gebe eine URL ein",
"copiedNotification": "In die Zwischenablage kopiert!"
"textFieldHint": "Gebe eine URL ein"
},
"relation": {
"relatedDatabasePlaceLabel": "Verwandte Datenbank",

View file

@ -723,9 +723,8 @@
},
"url": {
"launch": "Άνοιγμα συνδέσμου στο πρόγραμμα περιήγησης",
"copy": "Copied link to clipboard",
"textFieldHint": "Enter a URL",
"copiedNotification": "Copied to clipboard!"
"copy": "Copy link to clipboard",
"textFieldHint": "Enter a URL"
},
"relation": {
"relatedDatabasePlaceLabel": "Related Database",

View file

@ -1647,9 +1647,8 @@
},
"url": {
"launch": "Open link in browser",
"copy": "Copied link to clipboard",
"textFieldHint": "Enter a URL",
"copiedNotification": "Copied to clipboard!"
"copy": "Copy link to clipboard",
"textFieldHint": "Enter a URL"
},
"relation": {
"relatedDatabasePlaceLabel": "Related Database",
@ -2294,7 +2293,7 @@
},
"message": {
"copy": {
"success": "Copied!",
"success": "Copied to clipboard",
"fail": "Unable to copy"
}
},

View file

@ -218,14 +218,14 @@
"questionBubble": {
"shortcuts": "Atajos",
"whatsNew": "¿Qué hay de nuevo?",
"help": "Ayuda y Soporte",
"markdown": "Reducción",
"debug": {
"name": "Información de depuración",
"success": "¡Información copiada!",
"fail": "No fue posible copiar la información"
},
"feedback": "Comentario"
"feedback": "Comentario",
"help": "Ayuda y Soporte"
},
"menuAppHeader": {
"moreButtonToolTip": "Eliminar, renombrar y más...",
@ -866,8 +866,7 @@
"url": {
"launch": "Abrir en el navegador",
"copy": "Copiar URL",
"textFieldHint": "Introduce una URL",
"copiedNotification": "¡Copiado al portapapeles!"
"textFieldHint": "Introduce una URL"
},
"relation": {
"relatedDatabasePlaceLabel": "Base de datos relacionada",

View file

@ -99,14 +99,14 @@
"questionBubble": {
"shortcuts": "Lasterbideak",
"whatsNew": "Ze berri?",
"help": "Laguntza",
"markdown": "Markdown",
"debug": {
"name": "Debug informazioa",
"success": "Debug informazioa kopiatu da!",
"fail": "Ezin izan da debug informazioa kopiatu"
},
"feedback": "Iritzia"
"feedback": "Iritzia",
"help": "Laguntza"
},
"menuAppHeader": {
"addPageTooltip": "Gehitu orri bat",

View file

@ -139,14 +139,14 @@
"questionBubble": {
"shortcuts": "میانبرها",
"whatsNew": "تازه‌ترین‌ها",
"help": "پشتیبانی و مستندات",
"markdown": "Markdown",
"debug": {
"name": "اطلاعات اشکال‌زدایی",
"success": "طلاعات اشکال زدایی در کلیپ بورد کپی شد!",
"fail": "نمی توان اطلاعات اشکال زدایی را در کلیپ بورد کپی کرد"
},
"feedback": "بازخورد"
"feedback": "بازخورد",
"help": "پشتیبانی و مستندات"
},
"menuAppHeader": {
"moreButtonToolTip": "حذف، تغییر نام، و موارد دیگر...",

View file

@ -196,14 +196,14 @@
"questionBubble": {
"shortcuts": "Raccourcis",
"whatsNew": "Nouveautés",
"help": "Aide et Support Technique",
"markdown": "Réduction",
"debug": {
"name": "Infos du système",
"success": "Informations de débogage copiées dans le presse-papiers !",
"fail": "Impossible de copier les informations de débogage dans le presse-papiers"
},
"feedback": "Retour"
"feedback": "Retour",
"help": "Aide et Support Technique"
},
"menuAppHeader": {
"moreButtonToolTip": "Supprimer, renommer et plus...",

View file

@ -269,14 +269,14 @@
"questionBubble": {
"shortcuts": "Raccourcis",
"whatsNew": "Nouveautés",
"help": "Aide et Support",
"markdown": "Rédaction",
"debug": {
"name": "Informations de Débogage",
"success": "Informations de débogage copiées dans le presse-papiers !",
"fail": "Impossible de copier les informations de débogage dans le presse-papiers"
},
"feedback": "Retour"
"feedback": "Retour",
"help": "Aide et Support"
},
"menuAppHeader": {
"moreButtonToolTip": "Supprimer, renommer et plus...",
@ -1612,8 +1612,7 @@
"url": {
"launch": "Ouvrir dans le navigateur",
"copy": "Copier l'URL",
"textFieldHint": "Entrez une URL",
"copiedNotification": "Copié dans le presse-papier!"
"textFieldHint": "Entrez une URL"
},
"relation": {
"relatedDatabasePlaceLabel": "Base de données associée",

View file

@ -206,14 +206,14 @@
"questionBubble": {
"shortcuts": "מקשי קיצור",
"whatsNew": "מה חדש?",
"help": "עזרה ותמיכה",
"markdown": "Markdown",
"debug": {
"name": "פרטי ניפוי שגיאות",
"success": "פרטי ניפוי השגיאות הועתקו ללוח הגזירים!",
"fail": "לא ניתן להעתיק את פרטי ניפוי השגיאות ללוח הגזירים"
},
"feedback": "משוב"
"feedback": "משוב",
"help": "עזרה ותמיכה"
},
"menuAppHeader": {
"moreButtonToolTip": "הסרה, שינוי שם ועוד…",
@ -1243,8 +1243,7 @@
"url": {
"launch": "פתיחת קישור בדפדפן",
"copy": "העתקת קישור ללוח הגזירים",
"textFieldHint": "נא למלא כתובת",
"copiedNotification": "הועתק ללוח הגזירים!"
"textFieldHint": "נא למלא כתובת"
},
"relation": {
"relatedDatabasePlaceLabel": "מסד נתונים קשור",

View file

@ -103,14 +103,14 @@
"questionBubble": {
"shortcuts": "Parancsikonok",
"whatsNew": "Újdonságok",
"help": "Segítség & Támogatás",
"markdown": "Markdown",
"debug": {
"name": "Debug Információ",
"success": "Debug információ a vágólapra másolva",
"fail": "A Debug információ nem másolható a vágólapra"
},
"feedback": "Visszacsatolás"
"feedback": "Visszacsatolás",
"help": "Segítség & Támogatás"
},
"menuAppHeader": {
"addPageTooltip": "Belső oldal hozzáadása",

View file

@ -160,14 +160,14 @@
"questionBubble": {
"shortcuts": "Pintasan",
"whatsNew": "Apa yang baru?",
"help": "Bantuan & Dukungan",
"markdown": "Penurunan harga",
"debug": {
"name": "Info debug",
"success": "Info debug disalin ke papan klip!",
"fail": "Tidak dapat menyalin info debug ke papan klip"
},
"feedback": "Masukan"
"feedback": "Masukan",
"help": "Bantuan & Dukungan"
},
"menuAppHeader": {
"moreButtonToolTip": "Menghapus, merubah nama, dan banyak lagi...",

View file

@ -221,14 +221,14 @@
"questionBubble": {
"shortcuts": "Scorciatoie",
"whatsNew": "Cosa c'è di nuovo?",
"help": "Aiuto & Supporto",
"markdown": "Markdown",
"debug": {
"name": "Informazioni di debug",
"success": "Informazioni di debug copiate negli appunti!",
"fail": "Impossibile copiare le informazioni di debug negli appunti"
},
"feedback": "Feedback"
"feedback": "Feedback",
"help": "Aiuto & Supporto"
},
"menuAppHeader": {
"moreButtonToolTip": "Rimuovi, rinomina e altro...",

View file

@ -263,14 +263,14 @@
"questionBubble": {
"shortcuts": "ショートカット",
"whatsNew": "新着情報",
"help": "ヘルプ & サポート",
"markdown": "Markdown",
"debug": {
"name": "デバッグ情報",
"success": "デバッグ情報をクリップボードにコピーしました!",
"fail": "デバッグ情報をクリップボードにコピーできませんでした"
},
"feedback": "フィードバック"
"feedback": "フィードバック",
"help": "ヘルプ & サポート"
},
"menuAppHeader": {
"moreButtonToolTip": "削除、名前の変更、その他...",
@ -1577,8 +1577,7 @@
"url": {
"launch": "リンクをブラウザで開く",
"copy": "リンクをクリップボードにコピー",
"textFieldHint": "URLを入力",
"copiedNotification": "クリップボードにコピーされました!"
"textFieldHint": "URLを入力"
},
"relation": {
"relatedDatabasePlaceLabel": "関連データベース",

View file

@ -301,14 +301,14 @@
"questionBubble": {
"shortcuts": "단축키",
"whatsNew": "새로운 기능",
"help": "도움말 및 지원",
"markdown": "Markdown",
"debug": {
"name": "디버그 정보",
"success": "디버그 정보를 클립보드에 복사했습니다!",
"fail": "디버그 정보를 클립보드에 복사할 수 없습니다"
},
"feedback": "피드백"
"feedback": "피드백",
"help": "도움말 및 지원"
},
"menuAppHeader": {
"moreButtonToolTip": "제거, 이름 변경 등...",
@ -1634,8 +1634,7 @@
"url": {
"launch": "브라우저에서 링크 열기",
"copy": "링크를 클립보드에 복사",
"textFieldHint": "URL 입력",
"copiedNotification": "클립보드에 복사되었습니다!"
"textFieldHint": "URL 입력"
},
"relation": {
"relatedDatabasePlaceLabel": "관련 데이터베이스",

File diff suppressed because it is too large Load diff

View file

@ -164,14 +164,14 @@
"questionBubble": {
"shortcuts": "Skróty",
"whatsNew": "Co nowego?",
"help": "Pomoc & Wsparcie",
"markdown": "Markdown",
"debug": {
"name": "Informacje Debugowania",
"success": "Skopiowano informacje debugowania do schowka!",
"fail": "Nie mozna skopiować informacji debugowania do schowka"
},
"feedback": "Feedback"
"feedback": "Feedback",
"help": "Pomoc & Wsparcie"
},
"menuAppHeader": {
"moreButtonToolTip": "Usuń, zmień nazwę i więcej...",

View file

@ -225,14 +225,14 @@
"questionBubble": {
"shortcuts": "Atalhos",
"whatsNew": "O que há de novo?",
"help": "Ajuda e Suporte",
"markdown": "Remarcação",
"debug": {
"name": "Informação de depuração",
"success": "Informação de depuração copiada para a área de transferência!",
"fail": "Falha ao copiar a informação de depuração para a área de transferência"
},
"feedback": "Opinião"
"feedback": "Opinião",
"help": "Ajuda e Suporte"
},
"menuAppHeader": {
"moreButtonToolTip": "Remover, renomear e muito mais...",

View file

@ -128,14 +128,14 @@
"questionBubble": {
"shortcuts": "Atalhos",
"whatsNew": "O que há de novo?",
"help": "Ajuda & Suporte",
"markdown": "Remarcação",
"debug": {
"name": "Informação de depuração",
"success": "Copiar informação de depuração para o clipboard!",
"fail": "Falha em copiar a informação de depuração para o clipboard"
},
"feedback": "Opinião"
"feedback": "Opinião",
"help": "Ajuda & Suporte"
},
"menuAppHeader": {
"moreButtonToolTip": "Remover, renomear e muito mais...",

View file

@ -251,14 +251,14 @@
"questionBubble": {
"shortcuts": "Горячие клавиши",
"whatsNew": "Что нового?",
"help": "Помощь и поддержка",
"markdown": "Markdown",
"debug": {
"name": "Отладочная информация",
"success": "Отладочная информация скопирована в буфер обмена!",
"fail": "Не удалось скопировать отладочную информацию в буфер обмена"
},
"feedback": "Обратная связь"
"feedback": "Обратная связь",
"help": "Помощь и поддержка"
},
"menuAppHeader": {
"moreButtonToolTip": "Удалить, переименовать и другие действия...",
@ -1420,8 +1420,7 @@
"url": {
"launch": "Открыть в браузере",
"copy": "Скопировать URL",
"textFieldHint": "Введите URL-адрес",
"copiedNotification": "Скопировано в буфер обмена!"
"textFieldHint": "Введите URL-адрес"
},
"relation": {
"relatedDatabasePlaceLabel": "Связанная база данных",

View file

@ -107,14 +107,14 @@
"questionBubble": {
"shortcuts": "Genvägar",
"whatsNew": "Vad nytt?",
"help": "Hjälp & Support",
"markdown": "Prissänkning",
"debug": {
"name": "Felsökningsinfo",
"success": "Kopierade felsökningsinfo till urklipp!",
"fail": "Kunde inte kopiera felsökningsinfo till urklipp"
},
"feedback": "Återkoppling"
"feedback": "Återkoppling",
"help": "Hjälp & Support"
},
"menuAppHeader": {
"addPageTooltip": "Lägg till en underliggande sida",

View file

@ -250,14 +250,14 @@
"questionBubble": {
"shortcuts": "ทางลัด",
"whatsNew": "มีอะไรใหม่?",
"help": "ช่วยเหลือและสนับสนุน",
"markdown": "Markdown",
"debug": {
"name": "ข้อมูลดีบัก",
"success": "คัดลอกข้อมูลดีบักไปยังคลิปบอร์ดแล้ว!",
"fail": "ไม่สามารถคัดลอกข้อมูลดีบักไปยังคลิปบอร์ด"
},
"feedback": "ข้อเสนอแนะ"
"feedback": "ข้อเสนอแนะ",
"help": "ช่วยเหลือและสนับสนุน"
},
"menuAppHeader": {
"moreButtonToolTip": "ลบ เปลี่ยนชื่อ และอื่นๆ...",
@ -1570,8 +1570,7 @@
"url": {
"launch": "เปิดในเบราว์เซอร์",
"copy": "คัดลอก URL",
"textFieldHint": "ป้อน URL",
"copiedNotification": "คัดลอกไปยังคลิปบอร์ดแล้ว!"
"textFieldHint": "ป้อน URL"
},
"relation": {
"relatedDatabasePlaceLabel": "ฐานข้อมูลที่เกี่ยวข้อง",

View file

@ -288,14 +288,14 @@
"questionBubble": {
"shortcuts": "Kısayollar",
"whatsNew": "Yenilikler",
"help": "Yardım ve Destek",
"markdown": "Markdown",
"debug": {
"name": "Hata Ayıklama Bilgisi",
"success": "Hata ayıklama bilgisi panoya kopyalandı!",
"fail": "Hata ayıklama bilgisi panoya kopyalanamadı"
},
"feedback": "Geri Bildirim"
"feedback": "Geri Bildirim",
"help": "Yardım ve Destek"
},
"menuAppHeader": {
"moreButtonToolTip": "Kaldır, yeniden adlandır ve daha fazlası...",
@ -1608,8 +1608,7 @@
"url": {
"launch": "Bağlantıyı tarayıcıda aç",
"copy": "Bağlantıyı panoya kopyala",
"textFieldHint": "Bir URL girin",
"copiedNotification": "Panoya kopyalandı!"
"textFieldHint": "Bir URL girin"
},
"relation": {
"relatedDatabasePlaceLabel": "İlişkili Veritabanı",

View file

@ -225,14 +225,14 @@
"questionBubble": {
"shortcuts": "Комбінації клавіш",
"whatsNew": "Що нового?",
"help": "Довідка та підтримка",
"markdown": "Markdown",
"debug": {
"name": "Інформація для налагодження",
"success": "Інформацію для налагодження скопійовано в буфер обміну!",
"fail": "Не вдалося скопіювати інформацію для налагодження в буфер обміну"
},
"feedback": "Зворотний зв'язок"
"feedback": "Зворотний зв'язок",
"help": "Довідка та підтримка"
},
"menuAppHeader": {
"moreButtonToolTip": "Видалити, перейменувати та інше...",
@ -1445,8 +1445,7 @@
"url": {
"launch": "Відкрити посилання в браузері",
"copy": "Копіювати посилання в буфер обміну",
"textFieldHint": "Введіть URL",
"copiedNotification": "Скопійовано в буфер обміну!"
"textFieldHint": "Введіть URL"
},
"relation": {
"relatedDatabasePlaceLabel": "Пов'язана база даних",

View file

@ -226,14 +226,14 @@
"questionBubble": {
"shortcuts": "Phím tắt",
"whatsNew": "Có gì mới?",
"help": "Trợ giúp &amp; Hỗ trợ",
"markdown": "Markdown",
"debug": {
"name": "Thông tin gỡ lỗi",
"success": "Đã sao chép thông tin gỡ lỗi vào khay nhớ tạm!",
"fail": "Không thể sao chép thông tin gỡ lỗi vào khay nhớ tạm"
},
"feedback": "Nhận xét"
"feedback": "Nhận xét",
"help": "Trợ giúp &amp; Hỗ trợ"
},
"menuAppHeader": {
"moreButtonToolTip": "Xóa, đổi tên và hơn thế nữa...",
@ -1439,8 +1439,7 @@
"url": {
"launch": "Mở liên kết trong trình duyệt",
"copy": "Sao chép URL",
"textFieldHint": "Nhập một URL",
"copiedNotification": "Đã sao chép vào bảng tạm!"
"textFieldHint": "Nhập một URL"
},
"relation": {
"relatedDatabasePlaceLabel": "Cơ sở dữ liệu liên quan",

View file

@ -270,14 +270,14 @@
"questionBubble": {
"shortcuts": "快捷键",
"whatsNew": "新功能",
"help": "帮助和支持",
"markdown": "Markdown",
"debug": {
"name": "调试信息",
"success": "将调试信息复制到剪贴板!",
"fail": "无法将调试信息复制到剪贴板"
},
"feedback": "反馈"
"feedback": "反馈",
"help": "帮助和支持"
},
"menuAppHeader": {
"moreButtonToolTip": "删除、重命名等等...",
@ -1270,8 +1270,7 @@
"url": {
"launch": "在浏览器中打开链接",
"copy": "将链接复制到剪贴板",
"textFieldHint": "输入 URL",
"copiedNotification": "已复制到剪贴板!"
"textFieldHint": "输入 URL"
},
"relation": {
"rowSearchTextFieldPlaceholder": "搜索"

View file

@ -216,14 +216,14 @@
"questionBubble": {
"shortcuts": "快捷鍵",
"whatsNew": "有什麼新功能?",
"help": "幫助 & 支援",
"markdown": "Markdown",
"debug": {
"name": "除錯資訊",
"success": "已將除錯資訊複製到剪貼簿!",
"fail": "無法將除錯資訊複製到剪貼簿"
},
"feedback": "意見回饋"
"feedback": "意見回饋",
"help": "幫助 & 支援"
},
"menuAppHeader": {
"moreButtonToolTip": "移除、重新命名等等...",
@ -838,8 +838,7 @@
"url": {
"launch": "在瀏覽器中開啟",
"copy": "複製網址",
"textFieldHint": "輸入網址",
"copiedNotification": "已複製到剪貼簿"
"textFieldHint": "輸入網址"
},
"menuName": "網格",
"referencedGridPrefix": "檢視",

View file

@ -496,7 +496,7 @@ checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]]
name = "app-error"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"anyhow",
"bincode",
@ -516,16 +516,16 @@ dependencies = [
[[package]]
name = "appflowy-ai-client"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"anyhow",
"bytes",
"futures",
"pin-project",
"serde",
"serde_json",
"serde_repr",
"thiserror 1.0.64",
"uuid",
]
[[package]]
@ -1137,7 +1137,7 @@ dependencies = [
[[package]]
name = "client-api"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"again",
"anyhow",
@ -1192,7 +1192,7 @@ dependencies = [
[[package]]
name = "client-api-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"collab-entity",
"collab-rt-entity",
@ -1205,7 +1205,7 @@ dependencies = [
[[package]]
name = "client-websocket"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"futures-channel",
"futures-util",
@ -1248,7 +1248,7 @@ dependencies = [
[[package]]
name = "collab"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=80d1c6147d1139289c2eaadab40557cc86c0f4b6#80d1c6147d1139289c2eaadab40557cc86c0f4b6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4e717c2c6a15c42feda9e1ff1b122c7b0baf1821#4e717c2c6a15c42feda9e1ff1b122c7b0baf1821"
dependencies = [
"anyhow",
"arc-swap",
@ -1273,7 +1273,7 @@ dependencies = [
[[package]]
name = "collab-database"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=80d1c6147d1139289c2eaadab40557cc86c0f4b6#80d1c6147d1139289c2eaadab40557cc86c0f4b6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4e717c2c6a15c42feda9e1ff1b122c7b0baf1821#4e717c2c6a15c42feda9e1ff1b122c7b0baf1821"
dependencies = [
"anyhow",
"async-trait",
@ -1313,7 +1313,7 @@ dependencies = [
[[package]]
name = "collab-document"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=80d1c6147d1139289c2eaadab40557cc86c0f4b6#80d1c6147d1139289c2eaadab40557cc86c0f4b6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4e717c2c6a15c42feda9e1ff1b122c7b0baf1821#4e717c2c6a15c42feda9e1ff1b122c7b0baf1821"
dependencies = [
"anyhow",
"arc-swap",
@ -1334,7 +1334,7 @@ dependencies = [
[[package]]
name = "collab-entity"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=80d1c6147d1139289c2eaadab40557cc86c0f4b6#80d1c6147d1139289c2eaadab40557cc86c0f4b6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4e717c2c6a15c42feda9e1ff1b122c7b0baf1821#4e717c2c6a15c42feda9e1ff1b122c7b0baf1821"
dependencies = [
"anyhow",
"bytes",
@ -1354,7 +1354,7 @@ dependencies = [
[[package]]
name = "collab-folder"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=80d1c6147d1139289c2eaadab40557cc86c0f4b6#80d1c6147d1139289c2eaadab40557cc86c0f4b6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4e717c2c6a15c42feda9e1ff1b122c7b0baf1821#4e717c2c6a15c42feda9e1ff1b122c7b0baf1821"
dependencies = [
"anyhow",
"arc-swap",
@ -1376,7 +1376,7 @@ dependencies = [
[[package]]
name = "collab-importer"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=80d1c6147d1139289c2eaadab40557cc86c0f4b6#80d1c6147d1139289c2eaadab40557cc86c0f4b6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4e717c2c6a15c42feda9e1ff1b122c7b0baf1821#4e717c2c6a15c42feda9e1ff1b122c7b0baf1821"
dependencies = [
"anyhow",
"async-recursion",
@ -1418,7 +1418,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"arc-swap",
"async-trait",
"collab",
"collab-database",
"collab-document",
@ -1429,18 +1428,18 @@ dependencies = [
"diesel",
"flowy-error",
"flowy-sqlite",
"futures",
"lib-infra",
"serde",
"serde_json",
"tokio",
"tracing",
"uuid",
]
[[package]]
name = "collab-plugins"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=80d1c6147d1139289c2eaadab40557cc86c0f4b6#80d1c6147d1139289c2eaadab40557cc86c0f4b6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4e717c2c6a15c42feda9e1ff1b122c7b0baf1821#4e717c2c6a15c42feda9e1ff1b122c7b0baf1821"
dependencies = [
"anyhow",
"async-stream",
@ -1478,7 +1477,7 @@ dependencies = [
[[package]]
name = "collab-rt-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"anyhow",
"bincode",
@ -1500,7 +1499,7 @@ dependencies = [
[[package]]
name = "collab-rt-protocol"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"anyhow",
"async-trait",
@ -1511,13 +1510,14 @@ dependencies = [
"thiserror 1.0.64",
"tokio",
"tracing",
"uuid",
"yrs",
]
[[package]]
name = "collab-user"
version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=80d1c6147d1139289c2eaadab40557cc86c0f4b6#80d1c6147d1139289c2eaadab40557cc86c0f4b6"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=4e717c2c6a15c42feda9e1ff1b122c7b0baf1821#4e717c2c6a15c42feda9e1ff1b122c7b0baf1821"
dependencies = [
"anyhow",
"collab",
@ -1764,7 +1764,7 @@ dependencies = [
"cssparser-macros",
"dtoa-short",
"itoa",
"phf 0.11.2",
"phf 0.8.0",
"smallvec",
]
@ -1947,7 +1947,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "database-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"bincode",
"bytes",
@ -2513,13 +2513,13 @@ dependencies = [
name = "flowy-ai-pub"
version = "0.1.0"
dependencies = [
"bytes",
"client-api",
"flowy-error",
"futures",
"lib-infra",
"serde",
"serde_json",
"uuid",
]
[[package]]
@ -2592,7 +2592,6 @@ dependencies = [
"flowy-storage-pub",
"flowy-user",
"flowy-user-pub",
"futures",
"futures-core",
"lib-dispatch",
"lib-infra",
@ -2605,20 +2604,20 @@ dependencies = [
"tokio",
"tokio-stream",
"tracing",
"url",
"uuid",
"walkdir",
]
[[package]]
name = "flowy-database-pub"
version = "0.1.0"
dependencies = [
"anyhow",
"client-api",
"collab",
"collab-entity",
"flowy-error",
"lib-infra",
"uuid",
]
[[package]]
@ -2667,6 +2666,7 @@ dependencies = [
"tokio-util",
"tracing",
"url",
"uuid",
"validator 0.18.1",
]
@ -2744,11 +2744,11 @@ dependencies = [
name = "flowy-document-pub"
version = "0.1.0"
dependencies = [
"anyhow",
"collab",
"collab-document",
"flowy-error",
"lib-infra",
"uuid",
]
[[package]]
@ -2778,6 +2778,7 @@ dependencies = [
"thiserror 1.0.64",
"tokio",
"url",
"uuid",
"validator 0.18.1",
]
@ -2864,16 +2865,12 @@ dependencies = [
"bytes",
"collab",
"collab-folder",
"diesel",
"diesel_derives",
"diesel_migrations",
"flowy-codegen",
"flowy-derive",
"flowy-error",
"flowy-folder",
"flowy-notification",
"flowy-search-pub",
"flowy-sqlite",
"flowy-user",
"futures",
"lib-dispatch",
@ -2887,7 +2884,7 @@ dependencies = [
"tempfile",
"tokio",
"tracing",
"validator 0.18.1",
"uuid",
]
[[package]]
@ -2898,8 +2895,8 @@ dependencies = [
"collab",
"collab-folder",
"flowy-error",
"futures",
"lib-infra",
"uuid",
]
[[package]]
@ -2991,7 +2988,6 @@ name = "flowy-storage"
version = "0.1.0"
dependencies = [
"allo-isolate",
"anyhow",
"async-trait",
"bytes",
"chrono",
@ -3003,8 +2999,6 @@ dependencies = [
"flowy-notification",
"flowy-sqlite",
"flowy-storage-pub",
"futures-util",
"fxhash",
"lib-dispatch",
"lib-infra",
"mime_guess",
@ -3032,9 +3026,8 @@ dependencies = [
"mime",
"mime_guess",
"serde",
"serde_json",
"tokio",
"tracing",
"uuid",
]
[[package]]
@ -3057,7 +3050,6 @@ dependencies = [
"collab-user",
"dashmap 6.0.1",
"diesel",
"diesel_derives",
"fake",
"fancy-regex 0.11.0",
"flowy-codegen",
@ -3072,7 +3064,6 @@ dependencies = [
"lib-dispatch",
"lib-infra",
"nanoid",
"once_cell",
"protobuf",
"quickcheck",
"quickcheck_macros",
@ -3082,7 +3073,6 @@ dependencies = [
"semver",
"serde",
"serde_json",
"serde_repr",
"strum",
"strum_macros 0.25.2",
"tokio",
@ -3432,7 +3422,7 @@ dependencies = [
[[package]]
name = "gotrue"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"anyhow",
"getrandom 0.2.10",
@ -3447,7 +3437,7 @@ dependencies = [
[[package]]
name = "gotrue-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"app-error",
"jsonwebtoken",
@ -4068,7 +4058,7 @@ dependencies = [
[[package]]
name = "infra"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"anyhow",
"bytes",
@ -5181,7 +5171,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_macros 0.8.0",
"phf_macros",
"phf_shared 0.8.0",
"proc-macro-hack",
]
@ -5201,7 +5191,6 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_macros 0.11.3",
"phf_shared 0.11.2",
]
@ -5269,19 +5258,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "phf_macros"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
dependencies = [
"phf_generator 0.11.2",
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.94",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
@ -6782,7 +6758,7 @@ dependencies = [
[[package]]
name = "shared-entity"
version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=f7288f46c27dc8e3c7829cda1b70b61118e88336#f7288f46c27dc8e3c7829cda1b70b61118e88336"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e#2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e"
dependencies = [
"anyhow",
"app-error",

View file

@ -103,8 +103,8 @@ dashmap = "6.0.1"
# Run the script.add_workspace_members:
# scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "f7288f46c27dc8e3c7829cda1b70b61118e88336" }
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "f7288f46c27dc8e3c7829cda1b70b61118e88336" }
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e" }
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "2922db6801ca23c5fd6fe8b4958f03bc54dbcb7e" }
[profile.dev]
opt-level = 0
@ -139,14 +139,14 @@ rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb", rev = "1710120
# To switch to the local path, run:
# scripts/tool/update_collab_source.sh
# ⚠️⚠️⚠️️
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "80d1c6147d1139289c2eaadab40557cc86c0f4b6" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "80d1c6147d1139289c2eaadab40557cc86c0f4b6" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "80d1c6147d1139289c2eaadab40557cc86c0f4b6" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "80d1c6147d1139289c2eaadab40557cc86c0f4b6" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "80d1c6147d1139289c2eaadab40557cc86c0f4b6" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "80d1c6147d1139289c2eaadab40557cc86c0f4b6" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "80d1c6147d1139289c2eaadab40557cc86c0f4b6" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "80d1c6147d1139289c2eaadab40557cc86c0f4b6" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4e717c2c6a15c42feda9e1ff1b122c7b0baf1821" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4e717c2c6a15c42feda9e1ff1b122c7b0baf1821" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4e717c2c6a15c42feda9e1ff1b122c7b0baf1821" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4e717c2c6a15c42feda9e1ff1b122c7b0baf1821" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4e717c2c6a15c42feda9e1ff1b122c7b0baf1821" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4e717c2c6a15c42feda9e1ff1b122c7b0baf1821" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4e717c2c6a15c42feda9e1ff1b122c7b0baf1821" }
collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "4e717c2c6a15c42feda9e1ff1b122c7b0baf1821" }
# Working directory: frontend
# To update the commit ID, run:

View file

@ -19,14 +19,13 @@ serde.workspace = true
serde_json.workspace = true
anyhow.workspace = true
tracing.workspace = true
async-trait.workspace = true
tokio = { workspace = true, features = ["sync"] }
lib-infra = { workspace = true }
futures = "0.3.31"
arc-swap = "1.7"
flowy-sqlite = { workspace = true }
diesel.workspace = true
flowy-error.workspace = true
uuid.workspace = true
[features]
default = []

View file

@ -33,8 +33,10 @@ use collab_plugins::local_storage::kv::KVTransactionDB;
use collab_plugins::local_storage::CollabPersistenceConfig;
use collab_user::core::{UserAwareness, UserAwarenessNotifier};
use flowy_error::FlowyError;
use lib_infra::{if_native, if_wasm};
use tracing::{error, instrument, trace, warn};
use uuid::Uuid;
#[derive(Clone, Debug)]
pub enum CollabPluginProviderType {
@ -66,8 +68,8 @@ impl Display for CollabPluginProviderContext {
}
pub trait WorkspaceCollabIntegrate: Send + Sync {
fn workspace_id(&self) -> Result<String, Error>;
fn device_id(&self) -> Result<String, Error>;
fn workspace_id(&self) -> Result<Uuid, FlowyError>;
fn device_id(&self) -> Result<String, FlowyError>;
}
pub struct AppFlowyCollabBuilder {
@ -119,15 +121,15 @@ impl AppFlowyCollabBuilder {
pub fn collab_object(
&self,
workspace_id: &str,
workspace_id: &Uuid,
uid: i64,
object_id: &str,
object_id: &Uuid,
collab_type: CollabType,
) -> Result<CollabObject, Error> {
// Compare the workspace_id with the currently opened workspace_id. Return an error if they do not match.
// This check is crucial in asynchronous code contexts where the workspace_id might change during operation.
let actual_workspace_id = self.workspace_integrate.workspace_id()?;
if workspace_id != actual_workspace_id {
if workspace_id != &actual_workspace_id {
return Err(anyhow::anyhow!(
"workspace_id not match when build collab. expect workspace_id: {}, actual workspace_id: {}",
workspace_id,
@ -135,12 +137,11 @@ impl AppFlowyCollabBuilder {
));
}
let device_id = self.workspace_integrate.device_id()?;
let workspace_id = self.workspace_integrate.workspace_id()?;
Ok(CollabObject::new(
uid,
object_id.to_string(),
collab_type,
workspace_id,
workspace_id.to_string(),
device_id,
))
}
@ -399,11 +400,11 @@ impl CollabBuilderConfig {
pub struct CollabPersistenceImpl {
pub db: Weak<CollabKVDB>,
pub uid: i64,
pub workspace_id: String,
pub workspace_id: Uuid,
}
impl CollabPersistenceImpl {
pub fn new(db: Weak<CollabKVDB>, uid: i64, workspace_id: String) -> Self {
pub fn new(db: Weak<CollabKVDB>, uid: i64, workspace_id: Uuid) -> Self {
Self {
db,
uid,
@ -425,10 +426,11 @@ impl CollabPersistence for CollabPersistenceImpl {
let object_id = collab.object_id().to_string();
let rocksdb_read = collab_db.read_txn();
let workspace_id = self.workspace_id.to_string();
if rocksdb_read.is_exist(self.uid, &self.workspace_id, &object_id) {
if rocksdb_read.is_exist(self.uid, &workspace_id, &object_id) {
let mut txn = collab.transact_mut();
match rocksdb_read.load_doc_with_txn(self.uid, &self.workspace_id, &object_id, &mut txn) {
match rocksdb_read.load_doc_with_txn(self.uid, &workspace_id, &object_id, &mut txn) {
Ok(update_count) => {
trace!(
"did load collab:{}-{} from disk, update_count:{}",
@ -453,6 +455,7 @@ impl CollabPersistence for CollabPersistenceImpl {
object_id: &str,
encoded_collab: EncodedCollab,
) -> Result<(), CollabError> {
let workspace_id = self.workspace_id.to_string();
let collab_db = self
.db
.upgrade()
@ -461,7 +464,7 @@ impl CollabPersistence for CollabPersistenceImpl {
write_txn
.flush_doc(
self.uid,
self.workspace_id.as_str(),
workspace_id.as_str(),
object_id,
encoded_collab.state_vector.to_vec(),
encoded_collab.doc_state.to_vec(),

View file

@ -7,6 +7,8 @@ use flowy_sqlite::{
DBConnection, ExpressionMethods, Identifiable, Insertable, Queryable,
};
use std::collections::HashMap;
use std::str::FromStr;
use uuid::Uuid;
#[derive(Queryable, Insertable, Identifiable)]
#[diesel(table_name = af_collab_metadata)]
@ -43,13 +45,18 @@ pub fn batch_insert_collab_metadata(
pub fn batch_select_collab_metadata(
mut conn: DBConnection,
object_ids: &[String],
) -> FlowyResult<HashMap<String, AFCollabMetadata>> {
object_ids: &[Uuid],
) -> FlowyResult<HashMap<Uuid, AFCollabMetadata>> {
let object_ids = object_ids
.iter()
.map(|id| id.to_string())
.collect::<Vec<String>>();
let metadata = dsl::af_collab_metadata
.filter(af_collab_metadata::object_id.eq_any(object_ids))
.filter(af_collab_metadata::object_id.eq_any(&object_ids))
.load::<AFCollabMetadata>(&mut conn)?
.into_iter()
.map(|m| (m.object_id.clone(), m))
.flat_map(|m| Uuid::from_str(&m.object_id).map(|v| (v, m)))
.collect();
Ok(metadata)
}

View file

@ -1,8 +1,6 @@
use collab::entity::EncodedCollab;
use std::collections::HashMap;
use serde_json::Value;
use flowy_document::entities::*;
use flowy_document::event_map::DocumentEvent;
use flowy_document::parser::parser_entities::{
@ -11,6 +9,8 @@ use flowy_document::parser::parser_entities::{
};
use flowy_folder::entities::{CreateViewPayloadPB, ViewLayoutPB, ViewPB};
use flowy_folder::event_map::FolderEvent;
use serde_json::Value;
use uuid::Uuid;
use crate::document::utils::{gen_delta_str, gen_id, gen_text_block_data};
use crate::event_builder::EventBuilder;
@ -37,7 +37,7 @@ impl DocumentEventTest {
Self { event_test: core }
}
pub async fn get_encoded_v1(&self, doc_id: &str) -> EncodedCollab {
pub async fn get_encoded_v1(&self, doc_id: &Uuid) -> EncodedCollab {
let doc = self
.event_test
.appflowy_core

View file

@ -1,4 +1,5 @@
use flowy_folder::view_operation::{GatherEncodedCollab, ViewData};
use std::str::FromStr;
use std::sync::Arc;
use collab_folder::{FolderData, View};
@ -16,6 +17,7 @@ use flowy_user::entities::{
use flowy_user::errors::FlowyError;
use flowy_user::event_map::UserEvent;
use flowy_user_pub::entities::Role;
use uuid::Uuid;
use crate::event_builder::EventBuilder;
use crate::EventIntegrationTest;
@ -123,10 +125,10 @@ impl EventIntegrationTest {
let create_view_params = views
.into_iter()
.map(|view| CreateViewParams {
parent_view_id: view.parent_view_id,
parent_view_id: Uuid::from_str(&view.parent_view_id).unwrap(),
name: view.name,
layout: view.layout.into(),
view_id: view.id,
view_id: Uuid::from_str(&view.id).unwrap(),
initial_data: ViewData::Empty,
meta: Default::default(),
set_as_current: false,
@ -195,9 +197,10 @@ impl EventIntegrationTest {
view_id: &str,
layout: ViewLayout,
) -> GatherEncodedCollab {
let view_id = Uuid::from_str(view_id).unwrap();
self
.folder_manager
.gather_publish_encode_collab(view_id, &layout)
.gather_publish_encode_collab(&view_id, &layout)
.await
.unwrap()
}

View file

@ -1,3 +1,4 @@
use crate::user_event::TestNotificationSender;
use collab::core::collab::DataSource;
use collab::core::origin::CollabOrigin;
use collab::preclude::Collab;
@ -15,14 +16,14 @@ use nanoid::nanoid;
use semver::Version;
use std::env::temp_dir;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::select;
use tokio::task::LocalSet;
use tokio::time::sleep;
use crate::user_event::TestNotificationSender;
use uuid::Uuid;
mod chat_event;
pub mod database_event;
@ -145,10 +146,16 @@ impl EventIntegrationTest {
) -> Result<Vec<u8>, FlowyError> {
let server = self.server_provider.get_server().unwrap();
let workspace_id = self.get_current_workspace().await.id;
let oid = Uuid::from_str(oid).unwrap();
let uid = self.get_user_profile().await?.id;
let doc_state = server
.folder_service()
.get_folder_doc_state(&workspace_id, uid, collab_type, oid)
.get_folder_doc_state(
&Uuid::from_str(&workspace_id).unwrap(),
uid,
collab_type,
&oid,
)
.await?;
Ok(doc_state)

View file

@ -3,10 +3,12 @@ use event_integration_test::user_event::use_localhost_af_cloud;
use event_integration_test::EventIntegrationTest;
use flowy_ai::entities::ChatMessageListPB;
use flowy_ai::notification::ChatNotification;
use std::str::FromStr;
use flowy_ai_pub::cloud::ChatMessageType;
use std::time::Duration;
use uuid::Uuid;
#[tokio::test]
async fn af_cloud_create_chat_message_test() {
@ -21,8 +23,8 @@ async fn af_cloud_create_chat_message_test() {
for i in 0..10 {
let _ = chat_service
.create_question(
&current_workspace.id,
&chat_id,
&Uuid::from_str(&current_workspace.id).unwrap(),
&Uuid::from_str(&chat_id).unwrap(),
&format!("hello world {}", i),
ChatMessageType::System,
&[],
@ -77,8 +79,8 @@ async fn af_cloud_load_remote_system_message_test() {
for i in 0..10 {
let _ = chat_service
.create_question(
&current_workspace.id,
&chat_id,
&Uuid::from_str(&current_workspace.id).unwrap(),
&Uuid::from_str(&chat_id).unwrap(),
&format!("hello server {}", i),
ChatMessageType::System,
&[],

View file

@ -8,6 +8,8 @@ use flowy_document::parser::parser_entities::{
};
use serde_json::{json, Value};
use std::collections::HashMap;
use std::str::FromStr;
use uuid::Uuid;
#[tokio::test]
async fn get_document_event_test() {
@ -101,8 +103,8 @@ async fn document_size_test() {
let s = generate_random_string(string_size);
test.insert_index(&view.id, &s, 1, None).await;
}
let encoded_v1 = test.get_encoded_v1(&view.id).await;
let view_id = Uuid::from_str(&view.id).unwrap();
let encoded_v1 = test.get_encoded_v1(&view_id).await;
if encoded_v1.doc_state.len() > max_size {
panic!(
"The document size is too large. {}",

View file

@ -1,8 +1,8 @@
use collab_folder::ViewLayout;
use event_integration_test::EventIntegrationTest;
use flowy_folder::entities::icon::{ViewIconPB, ViewIconTypePB};
use flowy_folder::entities::ViewLayoutPB;
use uuid::Uuid;
use crate::folder::local_test::script::FolderScript::*;
use crate::folder::local_test::script::FolderTest;
@ -338,11 +338,11 @@ async fn move_view_event_test() {
async fn create_orphan_child_view_and_get_its_ancestors_test() {
let test = EventIntegrationTest::new_anon().await;
let name = "Orphan View";
let view_id = "20240521";
let view_id = Uuid::new_v4().to_string();
test
.create_orphan_view(name, view_id, ViewLayoutPB::Grid)
.create_orphan_view(name, &view_id, ViewLayoutPB::Grid)
.await;
let ancestors = test.get_view_ancestors(view_id).await;
let ancestors = test.get_view_ancestors(&view_id).await;
assert_eq!(ancestors.len(), 1);
assert_eq!(ancestors[0].name, "Orphan View");
assert_eq!(ancestors[0].id, view_id);

View file

@ -6,6 +6,7 @@ use flowy_folder::view_operation::GatherEncodedCollab;
use flowy_folder_pub::entities::{
PublishDocumentPayload, PublishPayload, PublishViewInfo, PublishViewMeta, PublishViewMetaData,
};
use uuid::Uuid;
async fn mock_single_document_view_publish_payload(
test: &EventIntegrationTest,
@ -140,11 +141,11 @@ async fn create_nested_document(test: &EventIntegrationTest, view_id: &str, name
#[tokio::test]
async fn single_document_get_publish_view_payload_test() {
let test = EventIntegrationTest::new_anon().await;
let view_id = "20240521";
let view_id = Uuid::new_v4().to_string();
let name = "Orphan View";
create_single_document(&test, view_id, name).await;
let view = test.get_view(view_id).await;
let payload = test.get_publish_payload(view_id, true).await;
create_single_document(&test, &view_id, name).await;
let view = test.get_view(&view_id).await;
let payload = test.get_publish_payload(&view_id, true).await;
let expect_payload = mock_single_document_view_publish_payload(
&test,
@ -160,10 +161,10 @@ async fn single_document_get_publish_view_payload_test() {
async fn nested_document_get_publish_view_payload_test() {
let test = EventIntegrationTest::new_anon().await;
let name = "Orphan View";
let view_id = "20240521";
create_nested_document(&test, view_id, name).await;
let view = test.get_view(view_id).await;
let payload = test.get_publish_payload(view_id, true).await;
let view_id = Uuid::new_v4().to_string();
create_nested_document(&test, &view_id, name).await;
let view = test.get_view(&view_id).await;
let payload = test.get_publish_payload(&view_id, true).await;
let expect_payload = mock_nested_document_view_publish_payload(
&test,
@ -180,10 +181,10 @@ async fn nested_document_get_publish_view_payload_test() {
async fn no_children_publish_view_payload_test() {
let test = EventIntegrationTest::new_anon().await;
let name = "Orphan View";
let view_id = "20240521";
create_nested_document(&test, view_id, name).await;
let view = test.get_view(view_id).await;
let payload = test.get_publish_payload(view_id, false).await;
let view_id = Uuid::new_v4().to_string();
create_nested_document(&test, &view_id, name).await;
let view = test.get_view(&view_id).await;
let payload = test.get_publish_payload(&view_id, false).await;
let data = mock_single_document_view_publish_payload(
&test,

View file

@ -1,44 +1,9 @@
use event_integration_test::EventIntegrationTest;
use flowy_core::DEFAULT_NAME;
use flowy_folder::entities::ViewLayoutPB;
use std::time::Duration;
use crate::util::unzip;
#[tokio::test]
async fn migrate_020_historical_empty_document_test() {
let user_db_path = unzip(
"./tests/user/migration_test/history_user_db",
"020_historical_user_data",
)
.unwrap();
let test =
EventIntegrationTest::new_with_user_data_path(user_db_path, DEFAULT_NAME.to_string()).await;
let mut views = test.get_all_workspace_views().await;
assert_eq!(views.len(), 1);
// Check the parent view
let parent_view = views.pop().unwrap();
assert_eq!(parent_view.layout, ViewLayoutPB::Document);
let data = test.open_document(parent_view.id.clone()).await.data;
assert!(!data.page_id.is_empty());
assert_eq!(data.blocks.len(), 2);
assert!(!data.meta.children_map.is_empty());
// Check the child views of the parent view
let child_views = test.get_view(&parent_view.id).await.child_views;
assert_eq!(child_views.len(), 4);
assert_eq!(child_views[0].layout, ViewLayoutPB::Document);
assert_eq!(child_views[1].layout, ViewLayoutPB::Grid);
assert_eq!(child_views[2].layout, ViewLayoutPB::Calendar);
assert_eq!(child_views[3].layout, ViewLayoutPB::Board);
let database = test.get_database(&child_views[1].id).await;
assert_eq!(database.fields.len(), 8);
assert_eq!(database.rows.len(), 3);
}
#[tokio::test]
async fn migrate_036_fav_v1_workspace_array_test() {
// Used to test migration: FavoriteV1AndWorkspaceArrayMigration

View file

@ -9,7 +9,7 @@ edition = "2021"
lib-infra = { workspace = true }
flowy-error = { workspace = true }
client-api = { workspace = true }
bytes.workspace = true
futures.workspace = true
serde_json.workspace = true
serde.workspace = true
serde.workspace = true
uuid.workspace = true

View file

@ -19,6 +19,7 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;
use uuid::Uuid;
pub type ChatMessageStream = BoxStream<'static, Result<ChatMessage, AppResponseError>>;
pub type StreamAnswer = BoxStream<'static, Result<QuestionStreamValue, FlowyError>>;
@ -81,15 +82,15 @@ pub trait ChatCloudService: Send + Sync + 'static {
async fn create_chat(
&self,
uid: &i64,
workspace_id: &str,
chat_id: &str,
rag_ids: Vec<String>,
workspace_id: &Uuid,
chat_id: &Uuid,
rag_ids: Vec<Uuid>,
) -> Result<(), FlowyError>;
async fn create_question(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message: &str,
message_type: ChatMessageType,
metadata: &[ChatMessageMetadata],
@ -97,8 +98,8 @@ pub trait ChatCloudService: Send + Sync + 'static {
async fn create_answer(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message: &str,
question_id: i64,
metadata: Option<serde_json::Value>,
@ -106,8 +107,8 @@ pub trait ChatCloudService: Send + Sync + 'static {
async fn stream_answer(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message_id: i64,
format: ResponseFormat,
ai_model: Option<AIModel>,
@ -115,68 +116,68 @@ pub trait ChatCloudService: Send + Sync + 'static {
async fn get_answer(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
question_message_id: i64,
) -> Result<ChatMessage, FlowyError>;
async fn get_chat_messages(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
offset: MessageCursor,
limit: u64,
) -> Result<RepeatedChatMessage, FlowyError>;
async fn get_question_from_answer_id(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
answer_message_id: i64,
) -> Result<ChatMessage, FlowyError>;
async fn get_related_message(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message_id: i64,
) -> Result<RepeatedRelatedQuestion, FlowyError>;
async fn stream_complete(
&self,
workspace_id: &str,
workspace_id: &Uuid,
params: CompleteTextParams,
ai_model: Option<AIModel>,
) -> Result<StreamComplete, FlowyError>;
async fn embed_file(
&self,
workspace_id: &str,
workspace_id: &Uuid,
file_path: &Path,
chat_id: &str,
chat_id: &Uuid,
metadata: Option<HashMap<String, Value>>,
) -> Result<(), FlowyError>;
async fn get_local_ai_config(&self, workspace_id: &str) -> Result<LocalAIConfig, FlowyError>;
async fn get_local_ai_config(&self, workspace_id: &Uuid) -> Result<LocalAIConfig, FlowyError>;
async fn get_workspace_plan(
&self,
workspace_id: &str,
workspace_id: &Uuid,
) -> Result<Vec<SubscriptionPlan>, FlowyError>;
async fn get_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
) -> Result<ChatSettings, FlowyError>;
async fn update_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
params: UpdateChatParams,
) -> Result<(), FlowyError>;
async fn get_available_models(&self, workspace_id: &str) -> Result<ModelList, FlowyError>;
async fn get_workspace_default_model(&self, workspace_id: &str) -> Result<String, FlowyError>;
async fn get_available_models(&self, workspace_id: &Uuid) -> Result<ModelList, FlowyError>;
async fn get_workspace_default_model(&self, workspace_id: &Uuid) -> Result<String, FlowyError>;
}

View file

@ -27,14 +27,16 @@ use flowy_storage_pub::storage::StorageService;
use lib_infra::async_trait::async_trait;
use lib_infra::util::timestamp;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{Arc, Weak};
use tokio::sync::RwLock;
use tracing::{error, info, instrument, trace};
use uuid::Uuid;
pub trait AIUserService: Send + Sync + 'static {
fn user_id(&self) -> Result<i64, FlowyError>;
fn device_id(&self) -> Result<String, FlowyError>;
fn workspace_id(&self) -> Result<String, FlowyError>;
fn workspace_id(&self) -> Result<Uuid, FlowyError>;
fn sqlite_connection(&self, uid: i64) -> Result<DBConnection, FlowyError>;
fn application_root_dir(&self) -> Result<PathBuf, FlowyError>;
}
@ -44,18 +46,18 @@ pub trait AIUserService: Send + Sync + 'static {
pub trait AIExternalService: Send + Sync + 'static {
async fn query_chat_rag_ids(
&self,
parent_view_id: &str,
chat_id: &str,
) -> Result<Vec<String>, FlowyError>;
parent_view_id: &Uuid,
chat_id: &Uuid,
) -> Result<Vec<Uuid>, FlowyError>;
async fn sync_rag_documents(
&self,
workspace_id: &str,
rag_ids: Vec<String>,
rag_metadata_map: HashMap<String, AFCollabMetadata>,
workspace_id: &Uuid,
rag_ids: Vec<Uuid>,
rag_metadata_map: HashMap<Uuid, AFCollabMetadata>,
) -> Result<Vec<AFCollabMetadata>, FlowyError>;
async fn notify_did_send_message(&self, chat_id: &str, message: &str) -> Result<(), FlowyError>;
async fn notify_did_send_message(&self, chat_id: &Uuid, message: &str) -> Result<(), FlowyError>;
}
#[derive(Debug, Default)]
@ -70,7 +72,7 @@ pub struct AIManager {
pub cloud_service_wm: Arc<AICloudServiceMiddleware>,
pub user_service: Arc<dyn AIUserService>,
pub external_service: Arc<dyn AIExternalService>,
chats: Arc<DashMap<String, Arc<Chat>>>,
chats: Arc<DashMap<Uuid, Arc<Chat>>>,
pub local_ai: Arc<LocalAIController>,
pub store_preferences: Arc<KVStorePreferences>,
server_models: Arc<RwLock<ServerModelsCache>>,
@ -132,11 +134,11 @@ impl AIManager {
Ok(())
}
pub async fn open_chat(&self, chat_id: &str) -> Result<(), FlowyError> {
self.chats.entry(chat_id.to_string()).or_insert_with(|| {
pub async fn open_chat(&self, chat_id: &Uuid) -> Result<(), FlowyError> {
self.chats.entry(*chat_id).or_insert_with(|| {
Arc::new(Chat::new(
self.user_service.user_id().unwrap(),
chat_id.to_string(),
*chat_id,
self.user_service.clone(),
self.cloud_service_wm.clone(),
))
@ -150,7 +152,7 @@ impl AIManager {
let cloud_service_wm = self.cloud_service_wm.clone();
let store_preferences = self.store_preferences.clone();
let external_service = self.external_service.clone();
let chat_id = chat_id.to_string();
let chat_id = *chat_id;
tokio::spawn(async move {
match refresh_chat_setting(
&user_service,
@ -161,7 +163,12 @@ impl AIManager {
.await
{
Ok(settings) => {
let _ = sync_chat_documents(user_service, external_service, settings.rag_ids).await;
let rag_ids = settings
.rag_ids
.into_iter()
.flat_map(|r| Uuid::from_str(&r).ok())
.collect();
let _ = sync_chat_documents(user_service, external_service, rag_ids).await;
},
Err(err) => {
error!("failed to refresh chat settings: {}", err);
@ -172,13 +179,13 @@ impl AIManager {
Ok(())
}
pub async fn close_chat(&self, chat_id: &str) -> Result<(), FlowyError> {
pub async fn close_chat(&self, chat_id: &Uuid) -> Result<(), FlowyError> {
trace!("close chat: {}", chat_id);
self.local_ai.close_chat(chat_id);
Ok(())
}
pub async fn delete_chat(&self, chat_id: &str) -> Result<(), FlowyError> {
pub async fn delete_chat(&self, chat_id: &Uuid) -> Result<(), FlowyError> {
if let Some((_, chat)) = self.chats.remove(chat_id) {
chat.close();
@ -212,8 +219,8 @@ impl AIManager {
pub async fn create_chat(
&self,
uid: &i64,
parent_view_id: &str,
chat_id: &str,
parent_view_id: &Uuid,
chat_id: &Uuid,
) -> Result<Arc<Chat>, FlowyError> {
let workspace_id = self.user_service.workspace_id()?;
let rag_ids = self
@ -231,11 +238,11 @@ impl AIManager {
let chat = Arc::new(Chat::new(
self.user_service.user_id()?,
chat_id.to_string(),
*chat_id,
self.user_service.clone(),
self.cloud_service_wm.clone(),
));
self.chats.insert(chat_id.to_string(), chat.clone());
self.chats.insert(*chat_id, chat.clone());
Ok(chat)
}
@ -244,7 +251,7 @@ impl AIManager {
params: StreamMessageParams,
) -> Result<ChatMessagePB, FlowyError> {
let chat = self.get_or_create_chat_instance(&params.chat_id).await?;
let ai_model = self.get_active_model(&params.chat_id).await;
let ai_model = self.get_active_model(&params.chat_id.to_string()).await;
let question = chat.stream_chat_message(&params, ai_model).await?;
let _ = self
.external_service
@ -255,7 +262,7 @@ impl AIManager {
pub async fn stream_regenerate_response(
&self,
chat_id: &str,
chat_id: &Uuid,
answer_message_id: i64,
answer_stream_port: i64,
format: Option<PredefinedFormatPB>,
@ -270,7 +277,7 @@ impl AIManager {
|| {
self
.store_preferences
.get_object::<AIModel>(&ai_available_models_key(chat_id))
.get_object::<AIModel>(&ai_available_models_key(&chat_id.to_string()))
},
|model| Some(model.into()),
);
@ -520,17 +527,17 @@ impl AIManager {
})
}
pub async fn get_or_create_chat_instance(&self, chat_id: &str) -> Result<Arc<Chat>, FlowyError> {
pub async fn get_or_create_chat_instance(&self, chat_id: &Uuid) -> Result<Arc<Chat>, FlowyError> {
let chat = self.chats.get(chat_id).as_deref().cloned();
match chat {
None => {
let chat = Arc::new(Chat::new(
self.user_service.user_id()?,
chat_id.to_string(),
*chat_id,
self.user_service.clone(),
self.cloud_service_wm.clone(),
));
self.chats.insert(chat_id.to_string(), chat.clone());
self.chats.insert(*chat_id, chat.clone());
Ok(chat)
},
Some(chat) => Ok(chat),
@ -554,7 +561,7 @@ impl AIManager {
pub async fn load_prev_chat_messages(
&self,
chat_id: &str,
chat_id: &Uuid,
limit: i64,
before_message_id: Option<i64>,
) -> Result<ChatMessageListPB, FlowyError> {
@ -567,7 +574,7 @@ impl AIManager {
pub async fn load_latest_chat_messages(
&self,
chat_id: &str,
chat_id: &Uuid,
limit: i64,
after_message_id: Option<i64>,
) -> Result<ChatMessageListPB, FlowyError> {
@ -580,7 +587,7 @@ impl AIManager {
pub async fn get_related_questions(
&self,
chat_id: &str,
chat_id: &Uuid,
message_id: i64,
) -> Result<RepeatedRelatedQuestionPB, FlowyError> {
let chat = self.get_or_create_chat_instance(chat_id).await?;
@ -590,7 +597,7 @@ impl AIManager {
pub async fn generate_answer(
&self,
chat_id: &str,
chat_id: &Uuid,
question_message_id: i64,
) -> Result<ChatMessagePB, FlowyError> {
let chat = self.get_or_create_chat_instance(chat_id).await?;
@ -598,19 +605,19 @@ impl AIManager {
Ok(resp)
}
pub async fn stop_stream(&self, chat_id: &str) -> Result<(), FlowyError> {
pub async fn stop_stream(&self, chat_id: &Uuid) -> Result<(), FlowyError> {
let chat = self.get_or_create_chat_instance(chat_id).await?;
chat.stop_stream_message().await;
Ok(())
}
pub async fn chat_with_file(&self, chat_id: &str, file_path: PathBuf) -> FlowyResult<()> {
pub async fn chat_with_file(&self, chat_id: &Uuid, file_path: PathBuf) -> FlowyResult<()> {
let chat = self.get_or_create_chat_instance(chat_id).await?;
chat.index_file(file_path).await?;
Ok(())
}
pub async fn get_rag_ids(&self, chat_id: &str) -> FlowyResult<Vec<String>> {
pub async fn get_rag_ids(&self, chat_id: &Uuid) -> FlowyResult<Vec<String>> {
if let Some(settings) = self
.store_preferences
.get_object::<ChatSettings>(&setting_store_key(chat_id))
@ -628,7 +635,7 @@ impl AIManager {
Ok(settings.rag_ids)
}
pub async fn update_rag_ids(&self, chat_id: &str, rag_ids: Vec<String>) -> FlowyResult<()> {
pub async fn update_rag_ids(&self, chat_id: &Uuid, rag_ids: Vec<String>) -> FlowyResult<()> {
info!("[Chat] update chat:{} rag ids: {:?}", chat_id, rag_ids);
let workspace_id = self.user_service.workspace_id()?;
let update_setting = UpdateChatParams {
@ -659,6 +666,10 @@ impl AIManager {
let user_service = self.user_service.clone();
let external_service = self.external_service.clone();
let rag_ids = rag_ids
.into_iter()
.flat_map(|r| Uuid::from_str(&r).ok())
.collect();
sync_chat_documents(user_service, external_service, rag_ids).await?;
Ok(())
}
@ -667,7 +678,7 @@ impl AIManager {
async fn sync_chat_documents(
user_service: Arc<dyn AIUserService>,
external_service: Arc<dyn AIExternalService>,
rag_ids: Vec<String>,
rag_ids: Vec<Uuid>,
) -> FlowyResult<()> {
if rag_ids.is_empty() {
return Ok(());
@ -697,7 +708,7 @@ async fn sync_chat_documents(
Ok(())
}
fn save_chat(conn: DBConnection, chat_id: &str) -> FlowyResult<()> {
fn save_chat(conn: DBConnection, chat_id: &Uuid) -> FlowyResult<()> {
let row = ChatTable {
chat_id: chat_id.to_string(),
created_at: timestamp(),
@ -716,7 +727,7 @@ async fn refresh_chat_setting(
user_service: &Arc<dyn AIUserService>,
cloud_service: &Arc<AICloudServiceMiddleware>,
store_preferences: &Arc<KVStorePreferences>,
chat_id: &str,
chat_id: &Uuid,
) -> FlowyResult<ChatSettings> {
info!("[Chat] refresh chat:{} setting", chat_id);
let workspace_id = user_service.workspace_id()?;
@ -728,7 +739,7 @@ async fn refresh_chat_setting(
error!("failed to set chat settings: {}", err);
}
chat_notification_builder(chat_id, ChatNotification::DidUpdateChatSettings)
chat_notification_builder(chat_id.to_string(), ChatNotification::DidUpdateChatSettings)
.payload(ChatSettingsPB {
rag_ids: settings.rag_ids.clone(),
})
@ -737,6 +748,6 @@ async fn refresh_chat_setting(
Ok(settings)
}
fn setting_store_key(chat_id: &str) -> String {
fn setting_store_key(chat_id: &Uuid) -> String {
format!("chat_settings_{}", chat_id)
}

View file

@ -23,6 +23,7 @@ use std::sync::atomic::{AtomicBool, AtomicI64};
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use tracing::{error, instrument, trace};
use uuid::Uuid;
enum PrevMessageState {
HasMore,
@ -31,7 +32,7 @@ enum PrevMessageState {
}
pub struct Chat {
chat_id: String,
chat_id: Uuid,
uid: i64,
user_service: Arc<dyn AIUserService>,
chat_service: Arc<AICloudServiceMiddleware>,
@ -44,7 +45,7 @@ pub struct Chat {
impl Chat {
pub fn new(
uid: i64,
chat_id: String,
chat_id: Uuid,
user_service: Arc<dyn AIUserService>,
chat_service: Arc<AICloudServiceMiddleware>,
) -> Chat {
@ -197,13 +198,13 @@ impl Chat {
answer_stream_port: i64,
answer_stream_buffer: Arc<Mutex<StringBuffer>>,
uid: i64,
workspace_id: String,
workspace_id: Uuid,
question_id: i64,
format: ResponseFormat,
ai_model: Option<AIModel>,
) {
let stop_stream = self.stop_stream.clone();
let chat_id = self.chat_id.clone();
let chat_id = self.chat_id;
let cloud_service = self.chat_service.clone();
let user_service = self.user_service.clone();
tokio::spawn(async move {
@ -254,10 +255,10 @@ impl Chat {
.send(StreamMessage::OnError(err.msg.clone()).to_string())
.await;
let pb = ChatMessageErrorPB {
chat_id: chat_id.clone(),
chat_id: chat_id.to_string(),
error_message: err.to_string(),
};
chat_notification_builder(&chat_id, ChatNotification::StreamChatMessageError)
chat_notification_builder(chat_id, ChatNotification::StreamChatMessageError)
.payload(pb)
.send();
return Err(err);
@ -293,17 +294,17 @@ impl Chat {
}
let pb = ChatMessageErrorPB {
chat_id: chat_id.clone(),
chat_id: chat_id.to_string(),
error_message: err.to_string(),
};
chat_notification_builder(&chat_id, ChatNotification::StreamChatMessageError)
chat_notification_builder(chat_id, ChatNotification::StreamChatMessageError)
.payload(pb)
.send();
return Err(err);
},
}
chat_notification_builder(&chat_id, ChatNotification::FinishStreaming).send();
chat_notification_builder(chat_id, ChatNotification::FinishStreaming).send();
trace!("[Chat] finish streaming");
if answer_stream_buffer.lock().await.is_empty() {
@ -359,7 +360,7 @@ impl Chat {
has_more: true,
total: 0,
};
chat_notification_builder(&self.chat_id, ChatNotification::DidLoadPrevChatMessage)
chat_notification_builder(self.chat_id, ChatNotification::DidLoadPrevChatMessage)
.payload(pb.clone())
.send();
return Ok(pb);
@ -432,7 +433,7 @@ impl Chat {
after_message_id
);
let workspace_id = self.user_service.workspace_id()?;
let chat_id = self.chat_id.clone();
let chat_id = self.chat_id;
let cloud_service = self.chat_service.clone();
let user_service = self.user_service.clone();
let uid = self.uid;
@ -480,11 +481,11 @@ impl Chat {
} else {
*prev_message_state.write().await = PrevMessageState::NoMore;
}
chat_notification_builder(&chat_id, ChatNotification::DidLoadPrevChatMessage)
chat_notification_builder(chat_id, ChatNotification::DidLoadPrevChatMessage)
.payload(pb)
.send();
} else {
chat_notification_builder(&chat_id, ChatNotification::DidLoadLatestChatMessage)
chat_notification_builder(chat_id, ChatNotification::DidLoadLatestChatMessage)
.payload(pb)
.send();
}
@ -510,7 +511,7 @@ impl Chat {
}
let workspace_id = self.user_service.workspace_id()?;
let chat_id = self.chat_id.clone();
let chat_id = self.chat_id;
let cloud_service = self.chat_service.clone();
let question = cloud_service
@ -566,7 +567,7 @@ impl Chat {
let conn = self.user_service.sqlite_connection(self.uid)?;
let records = select_chat_messages(
conn,
&self.chat_id,
&self.chat_id.to_string(),
limit,
after_message_id,
before_message_id,
@ -628,7 +629,7 @@ impl Chat {
fn save_chat_message_disk(
conn: DBConnection,
chat_id: &str,
chat_id: &Uuid,
messages: Vec<ChatMessage>,
) -> FlowyResult<()> {
let records = messages
@ -683,7 +684,7 @@ impl StringBuffer {
pub(crate) fn save_and_notify_message(
uid: i64,
chat_id: &str,
chat_id: &Uuid,
user_service: &Arc<dyn AIUserService>,
message: ChatMessage,
) -> Result<(), FlowyError> {

View file

@ -1,6 +1,7 @@
use crate::ai_manager::AIUserService;
use crate::entities::{CompleteTextPB, CompleteTextTaskPB, CompletionTypePB};
use allo_isolate::Isolate;
use std::str::FromStr;
use dashmap::DashMap;
use flowy_ai_pub::cloud::{
@ -15,7 +16,8 @@ use lib_infra::isolate_stream::IsolateSink;
use crate::stream_message::StreamMessage;
use std::sync::{Arc, Weak};
use tokio::select;
use tracing::info;
use tracing::{error, info};
use uuid::Uuid;
pub struct AICompletion {
tasks: Arc<DashMap<String, tokio::sync::mpsc::Sender<()>>>,
@ -77,7 +79,7 @@ impl AICompletion {
}
pub struct CompletionTask {
workspace_id: String,
workspace_id: Uuid,
task_id: String,
stop_rx: tokio::sync::mpsc::Receiver<()>,
context: CompleteTextPB,
@ -87,7 +89,7 @@ pub struct CompletionTask {
impl CompletionTask {
pub fn new(
workspace_id: String,
workspace_id: Uuid,
context: CompleteTextPB,
preferred_model: Option<AIModel>,
cloud_service: Weak<dyn ChatCloudService>,
@ -122,59 +124,63 @@ impl CompletionTask {
let _ = sink.send("start:".to_string()).await;
let completion_history = Some(self.context.history.iter().map(Into::into).collect());
let format = self.context.format.map(Into::into).unwrap_or_default();
let params = CompleteTextParams {
text: self.context.text,
completion_type: Some(complete_type),
metadata: Some(CompletionMetadata {
object_id: self.context.object_id,
workspace_id: Some(self.workspace_id.clone()),
rag_ids: Some(self.context.rag_ids),
completion_history,
custom_prompt: self
.context
.custom_prompt
.map(|v| CustomPrompt { system: v }),
}),
format,
};
if let Ok(object_id) = Uuid::from_str(&self.context.object_id) {
let params = CompleteTextParams {
text: self.context.text,
completion_type: Some(complete_type),
metadata: Some(CompletionMetadata {
object_id,
workspace_id: Some(self.workspace_id),
rag_ids: Some(self.context.rag_ids),
completion_history,
custom_prompt: self
.context
.custom_prompt
.map(|v| CustomPrompt { system: v }),
}),
format,
};
info!("start completion: {:?}", params);
match cloud_service
.stream_complete(&self.workspace_id, params, self.preferred_model)
.await
{
Ok(mut stream) => loop {
select! {
_ = self.stop_rx.recv() => {
return;
},
result = stream.next() => {
match result {
Some(Ok(data)) => {
match data {
CompletionStreamValue::Answer{ value } => {
let _ = sink.send(format!("data:{}", value)).await;
info!("start completion: {:?}", params);
match cloud_service
.stream_complete(&self.workspace_id, params, self.preferred_model)
.await
{
Ok(mut stream) => loop {
select! {
_ = self.stop_rx.recv() => {
return;
},
result = stream.next() => {
match result {
Some(Ok(data)) => {
match data {
CompletionStreamValue::Answer{ value } => {
let _ = sink.send(format!("data:{}", value)).await;
}
CompletionStreamValue::Comment{ value } => {
let _ = sink.send(format!("comment:{}", value)).await;
}
}
CompletionStreamValue::Comment{ value } => {
let _ = sink.send(format!("comment:{}", value)).await;
}
}
},
Some(Err(error)) => {
handle_error(&mut sink, error).await;
return;
},
None => {
let _ = sink.send(format!("finish:{}", self.task_id)).await;
return;
},
},
Some(Err(error)) => {
handle_error(&mut sink, error).await;
return;
},
None => {
let _ = sink.send(format!("finish:{}", self.task_id)).await;
return;
},
}
}
}
}
},
Err(error) => {
handle_error(&mut sink, error).await;
},
}
},
Err(error) => {
handle_error(&mut sink, error).await;
},
}
} else {
error!("Invalid uuid: {}", self.context.object_id);
}
}
});

View file

@ -1,8 +1,6 @@
use af_plugin::core::plugin::RunningState;
use std::collections::HashMap;
use crate::local_ai::controller::LocalAISetting;
use crate::local_ai::resource::PendingResource;
use af_plugin::core::plugin::RunningState;
use flowy_ai_pub::cloud::{
AIModel, ChatMessage, ChatMessageMetadata, ChatMessageType, CompletionMessage, LLMModel,
OutputContent, OutputLayout, RelatedQuestion, RepeatedChatMessage, RepeatedRelatedQuestion,
@ -10,6 +8,8 @@ use flowy_ai_pub::cloud::{
};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use lib_infra::validator_fn::required_not_empty_str;
use std::collections::HashMap;
use uuid::Uuid;
use validator::Validate;
#[derive(Default, ProtoBuf, Validate, Clone, Debug)]
@ -78,7 +78,7 @@ pub struct StreamChatPayloadPB {
#[derive(Default, Debug)]
pub struct StreamMessageParams {
pub chat_id: String,
pub chat_id: Uuid,
pub message: String,
pub message_type: ChatMessageType,
pub answer_stream_port: i64,

View file

@ -1,6 +1,3 @@
use std::fs;
use std::path::PathBuf;
use crate::ai_manager::{AIManager, GLOBAL_ACTIVE_MODEL_KEY};
use crate::completion::AICompletion;
use crate::entities::*;
@ -10,8 +7,12 @@ use flowy_ai_pub::cloud::{
};
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::{Arc, Weak};
use tracing::trace;
use uuid::Uuid;
use validator::Validate;
fn upgrade_ai_manager(ai_manager: AFPluginState<Weak<AIManager>>) -> FlowyResult<Arc<AIManager>> {
@ -70,6 +71,7 @@ pub(crate) async fn stream_chat_message_handler(
trace!("Stream chat message with metadata: {:?}", metadata);
let chat_id = Uuid::from_str(&chat_id)?;
let params = StreamMessageParams {
chat_id,
message,
@ -91,11 +93,12 @@ pub(crate) async fn regenerate_response_handler(
ai_manager: AFPluginState<Weak<AIManager>>,
) -> FlowyResult<()> {
let data = data.try_into_inner()?;
let chat_id = Uuid::from_str(&data.chat_id)?;
let ai_manager = upgrade_ai_manager(ai_manager)?;
ai_manager
.stream_regenerate_response(
&data.chat_id,
&chat_id,
data.answer_message_id,
data.answer_stream_port,
data.format,
@ -147,8 +150,9 @@ pub(crate) async fn load_prev_message_handler(
let data = data.into_inner();
data.validate()?;
let chat_id = Uuid::from_str(&data.chat_id)?;
let messages = ai_manager
.load_prev_chat_messages(&data.chat_id, data.limit, data.before_message_id)
.load_prev_chat_messages(&chat_id, data.limit, data.before_message_id)
.await?;
data_result_ok(messages)
}
@ -162,8 +166,9 @@ pub(crate) async fn load_next_message_handler(
let data = data.into_inner();
data.validate()?;
let chat_id = Uuid::from_str(&data.chat_id)?;
let messages = ai_manager
.load_latest_chat_messages(&data.chat_id, data.limit, data.after_message_id)
.load_latest_chat_messages(&chat_id, data.limit, data.after_message_id)
.await?;
data_result_ok(messages)
}
@ -175,8 +180,9 @@ pub(crate) async fn get_related_question_handler(
) -> DataResult<RepeatedRelatedQuestionPB, FlowyError> {
let ai_manager = upgrade_ai_manager(ai_manager)?;
let data = data.into_inner();
let chat_id = Uuid::from_str(&data.chat_id)?;
let messages = ai_manager
.get_related_questions(&data.chat_id, data.message_id)
.get_related_questions(&chat_id, data.message_id)
.await?;
data_result_ok(messages)
}
@ -188,8 +194,9 @@ pub(crate) async fn get_answer_handler(
) -> DataResult<ChatMessagePB, FlowyError> {
let ai_manager = upgrade_ai_manager(ai_manager)?;
let data = data.into_inner();
let chat_id = Uuid::from_str(&data.chat_id)?;
let message = ai_manager
.generate_answer(&data.chat_id, data.message_id)
.generate_answer(&chat_id, data.message_id)
.await?;
data_result_ok(message)
}
@ -203,7 +210,8 @@ pub(crate) async fn stop_stream_handler(
data.validate()?;
let ai_manager = upgrade_ai_manager(ai_manager)?;
ai_manager.stop_stream(&data.chat_id).await?;
let chat_id = Uuid::from_str(&data.chat_id)?;
ai_manager.stop_stream(&chat_id).await?;
Ok(())
}
@ -273,7 +281,8 @@ pub(crate) async fn chat_file_handler(
tracing::debug!("File size: {} bytes", file_size);
let ai_manager = upgrade_ai_manager(ai_manager)?;
ai_manager.chat_with_file(&data.chat_id, file_path).await?;
let chat_id = Uuid::from_str(&data.chat_id)?;
ai_manager.chat_with_file(&chat_id, file_path).await?;
Ok(())
}
@ -332,6 +341,7 @@ pub(crate) async fn get_chat_settings_handler(
ai_manager: AFPluginState<Weak<AIManager>>,
) -> DataResult<ChatSettingsPB, FlowyError> {
let chat_id = data.try_into_inner()?.value;
let chat_id = Uuid::from_str(&chat_id)?;
let ai_manager = upgrade_ai_manager(ai_manager)?;
let rag_ids = ai_manager.get_rag_ids(&chat_id).await?;
let pb = ChatSettingsPB { rag_ids };
@ -345,9 +355,8 @@ pub(crate) async fn update_chat_settings_handler(
) -> FlowyResult<()> {
let params = data.try_into_inner()?;
let ai_manager = upgrade_ai_manager(ai_manager)?;
ai_manager
.update_rag_ids(&params.chat_id.value, params.rag_ids)
.await?;
let chat_id = Uuid::from_str(&params.chat_id.value)?;
ai_manager.update_rag_ids(&chat_id, params.rag_ids).await?;
Ok(())
}

View file

@ -28,6 +28,7 @@ use std::sync::Arc;
use tokio::select;
use tokio_stream::StreamExt;
use tracing::{debug, error, info, instrument};
use uuid::Uuid;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LocalAISetting {
@ -51,7 +52,7 @@ const LOCAL_AI_SETTING_KEY: &str = "appflowy_local_ai_setting:v1";
pub struct LocalAIController {
ai_plugin: Arc<OllamaAIPlugin>,
resource: Arc<LocalAIResourceController>,
current_chat_id: ArcSwapOption<String>,
current_chat_id: ArcSwapOption<Uuid>,
store_preferences: Arc<KVStorePreferences>,
user_service: Arc<dyn AIUserService>,
#[allow(dead_code)]
@ -240,7 +241,7 @@ impl LocalAIController {
Some(self.resource.get_llm_setting().chat_model_name)
}
pub fn open_chat(&self, chat_id: &str) {
pub fn open_chat(&self, chat_id: &Uuid) {
if !self.is_enabled() {
return;
}
@ -252,9 +253,7 @@ impl LocalAIController {
self.close_chat(current_chat_id);
}
self
.current_chat_id
.store(Some(Arc::new(chat_id.to_string())));
self.current_chat_id.store(Some(Arc::new(*chat_id)));
let chat_id = chat_id.to_string();
let weak_ctrl = Arc::downgrade(&self.ai_plugin);
tokio::spawn(async move {
@ -266,7 +265,7 @@ impl LocalAIController {
});
}
pub fn close_chat(&self, chat_id: &str) {
pub fn close_chat(&self, chat_id: &Uuid) {
if !self.is_running() {
return;
}
@ -383,7 +382,7 @@ impl LocalAIController {
#[instrument(level = "debug", skip_all)]
pub async fn index_message_metadata(
&self,
chat_id: &str,
chat_id: &Uuid,
metadata_list: &[ChatMessageMetadata],
index_process_sink: &mut (impl Sink<String> + Unpin),
) -> FlowyResult<()> {
@ -434,7 +433,7 @@ impl LocalAIController {
async fn process_index_file(
&self,
chat_id: &str,
chat_id: &Uuid,
file_path: PathBuf,
index_metadata: &HashMap<String, serde_json::Value>,
index_process_sink: &mut (impl Sink<String> + Unpin),
@ -456,7 +455,11 @@ impl LocalAIController {
let result = self
.ai_plugin
.embed_file(chat_id, file_path, Some(index_metadata.clone()))
.embed_file(
&chat_id.to_string(),
file_path,
Some(index_metadata.clone()),
)
.await;
match result {
Ok(_) => {
@ -616,6 +619,6 @@ impl LLMResourceService for LLMResourceServiceImpl {
}
const APPFLOWY_LOCAL_AI_ENABLED: &str = "appflowy_local_ai_enabled";
fn local_ai_enabled_key(workspace_id: &str) -> String {
fn local_ai_enabled_key(workspace_id: &Uuid) -> String {
format!("{}:{}", APPFLOWY_LOCAL_AI_ENABLED, workspace_id)
}

View file

@ -26,6 +26,7 @@ use serde_json::{json, Value};
use std::path::Path;
use std::sync::{Arc, Weak};
use tracing::{info, trace};
use uuid::Uuid;
pub struct AICloudServiceMiddleware {
cloud_service: Arc<dyn ChatCloudService>,
@ -55,7 +56,7 @@ impl AICloudServiceMiddleware {
pub async fn index_message_metadata(
&self,
chat_id: &str,
chat_id: &Uuid,
metadata_list: &[ChatMessageMetadata],
index_process_sink: &mut (impl Sink<String> + Unpin),
) -> Result<(), FlowyError> {
@ -114,9 +115,9 @@ impl ChatCloudService for AICloudServiceMiddleware {
async fn create_chat(
&self,
uid: &i64,
workspace_id: &str,
chat_id: &str,
rag_ids: Vec<String>,
workspace_id: &Uuid,
chat_id: &Uuid,
rag_ids: Vec<Uuid>,
) -> Result<(), FlowyError> {
self
.cloud_service
@ -126,8 +127,8 @@ impl ChatCloudService for AICloudServiceMiddleware {
async fn create_question(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message: &str,
message_type: ChatMessageType,
metadata: &[ChatMessageMetadata],
@ -140,8 +141,8 @@ impl ChatCloudService for AICloudServiceMiddleware {
async fn create_answer(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message: &str,
question_id: i64,
metadata: Option<serde_json::Value>,
@ -154,8 +155,8 @@ impl ChatCloudService for AICloudServiceMiddleware {
async fn stream_answer(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message_id: i64,
format: ResponseFormat,
ai_model: Option<AIModel>,
@ -171,7 +172,12 @@ impl ChatCloudService for AICloudServiceMiddleware {
let row = self.get_message_record(message_id)?;
match self
.local_ai
.stream_question(chat_id, &row.content, Some(json!(format)), json!({}))
.stream_question(
&chat_id.to_string(),
&row.content,
Some(json!(format)),
json!({}),
)
.await
{
Ok(stream) => Ok(QuestionStream::new(stream).boxed()),
@ -195,13 +201,17 @@ impl ChatCloudService for AICloudServiceMiddleware {
async fn get_answer(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
question_message_id: i64,
) -> Result<ChatMessage, FlowyError> {
if self.local_ai.is_running() {
let content = self.get_message_record(question_message_id)?.content;
match self.local_ai.ask_question(chat_id, &content).await {
match self
.local_ai
.ask_question(&chat_id.to_string(), &content)
.await
{
Ok(answer) => {
let message = self
.cloud_service
@ -224,8 +234,8 @@ impl ChatCloudService for AICloudServiceMiddleware {
async fn get_chat_messages(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
offset: MessageCursor,
limit: u64,
) -> Result<RepeatedChatMessage, FlowyError> {
@ -237,26 +247,26 @@ impl ChatCloudService for AICloudServiceMiddleware {
async fn get_question_from_answer_id(
&self,
workspace_id: &str,
chat_id: &str,
answer_id: i64,
workspace_id: &Uuid,
chat_id: &Uuid,
answer_message_id: i64,
) -> Result<ChatMessage, FlowyError> {
self
.cloud_service
.get_question_from_answer_id(workspace_id, chat_id, answer_id)
.get_question_from_answer_id(workspace_id, chat_id, answer_message_id)
.await
}
async fn get_related_message(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message_id: i64,
) -> Result<RepeatedRelatedQuestion, FlowyError> {
if self.local_ai.is_running() {
let questions = self
.local_ai
.get_related_question(chat_id)
.get_related_question(&chat_id.to_string())
.await
.map_err(|err| FlowyError::local_ai().with_context(err))?;
trace!("LocalAI related questions: {:?}", questions);
@ -280,7 +290,7 @@ impl ChatCloudService for AICloudServiceMiddleware {
async fn stream_complete(
&self,
workspace_id: &str,
workspace_id: &Uuid,
params: CompleteTextParams,
ai_model: Option<AIModel>,
) -> Result<StreamComplete, FlowyError> {
@ -329,15 +339,15 @@ impl ChatCloudService for AICloudServiceMiddleware {
async fn embed_file(
&self,
workspace_id: &str,
workspace_id: &Uuid,
file_path: &Path,
chat_id: &str,
chat_id: &Uuid,
metadata: Option<HashMap<String, Value>>,
) -> Result<(), FlowyError> {
if self.local_ai.is_running() {
self
.local_ai
.embed_file(chat_id, file_path.to_path_buf(), metadata)
.embed_file(&chat_id.to_string(), file_path.to_path_buf(), metadata)
.await
.map_err(|err| FlowyError::local_ai().with_context(err))?;
Ok(())
@ -349,21 +359,21 @@ impl ChatCloudService for AICloudServiceMiddleware {
}
}
async fn get_local_ai_config(&self, workspace_id: &str) -> Result<LocalAIConfig, FlowyError> {
async fn get_local_ai_config(&self, workspace_id: &Uuid) -> Result<LocalAIConfig, FlowyError> {
self.cloud_service.get_local_ai_config(workspace_id).await
}
async fn get_workspace_plan(
&self,
workspace_id: &str,
workspace_id: &Uuid,
) -> Result<Vec<SubscriptionPlan>, FlowyError> {
self.cloud_service.get_workspace_plan(workspace_id).await
}
async fn get_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
) -> Result<ChatSettings, FlowyError> {
self
.cloud_service
@ -373,8 +383,8 @@ impl ChatCloudService for AICloudServiceMiddleware {
async fn update_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
params: UpdateChatParams,
) -> Result<(), FlowyError> {
self
@ -383,11 +393,11 @@ impl ChatCloudService for AICloudServiceMiddleware {
.await
}
async fn get_available_models(&self, workspace_id: &str) -> Result<ModelList, FlowyError> {
async fn get_available_models(&self, workspace_id: &Uuid) -> Result<ModelList, FlowyError> {
self.cloud_service.get_available_models(workspace_id).await
}
async fn get_workspace_default_model(&self, workspace_id: &str) -> Result<String, FlowyError> {
async fn get_workspace_default_model(&self, workspace_id: &Uuid) -> Result<String, FlowyError> {
self
.cloud_service
.get_workspace_default_model(workspace_id)

View file

@ -1,5 +1,6 @@
use flowy_derive::ProtoBuf_Enum;
use flowy_notification::NotificationBuilder;
use tracing::trace;
const CHAT_OBSERVABLE_SOURCE: &str = "Chat";
pub const APPFLOWY_AI_NOTIFICATION_KEY: &str = "appflowy_ai_plugin";
@ -39,7 +40,12 @@ impl std::convert::From<i32> for ChatNotification {
}
}
#[tracing::instrument(level = "trace")]
pub(crate) fn chat_notification_builder(id: &str, ty: ChatNotification) -> NotificationBuilder {
NotificationBuilder::new(id, ty, CHAT_OBSERVABLE_SOURCE)
#[tracing::instrument(level = "trace", skip_all)]
pub(crate) fn chat_notification_builder<T: ToString>(
id: T,
ty: ChatNotification,
) -> NotificationBuilder {
let id = id.to_string();
trace!("chat_notification_builder: id = {id}, ty = {ty:?}");
NotificationBuilder::new(&id, ty, CHAT_OBSERVABLE_SOURCE)
}

View file

@ -32,7 +32,6 @@ collab = { workspace = true }
#collab = { workspace = true, features = ["verbose_log"] }
diesel.workspace = true
uuid.workspace = true
flowy-storage = { workspace = true }
flowy-storage-pub = { workspace = true }
client-api.workspace = true
@ -56,10 +55,10 @@ lib-infra = { workspace = true }
serde.workspace = true
serde_json.workspace = true
serde_repr.workspace = true
futures.workspace = true
walkdir = "2.4.0"
uuid.workspace = true
sysinfo = "0.30.5"
semver = { version = "1.0.22", features = ["serde"] }
url = "2.5.0"
[features]
profiling = ["console-subscriber", "tokio/tracing"]

View file

@ -1,9 +1,10 @@
use std::fmt;
use std::path::Path;
use std::path::{Path, PathBuf};
use base64::Engine;
use semver::Version;
use tracing::{error, info};
use url::Url;
use crate::log_filter::create_log_filter;
use flowy_server_pub::af_cloud_config::AFCloudConfiguration;
@ -28,7 +29,25 @@ pub struct AppFlowyCoreConfig {
pub(crate) log_filter: String,
pub cloud_config: Option<AFCloudConfiguration>,
}
impl AppFlowyCoreConfig {
pub fn ensure_path(&self) {
let create_if_needed = |path_str: &str, label: &str| {
let dir = std::path::Path::new(path_str);
if !dir.exists() {
match std::fs::create_dir_all(dir) {
Ok(_) => info!("Created {} path: {}", label, path_str),
Err(err) => error!(
"Failed to create {} path: {}. Error: {}",
label, path_str, err
),
}
}
};
create_if_needed(&self.storage_path, "storage");
create_if_needed(&self.application_path, "application");
}
}
impl fmt::Debug for AppFlowyCoreConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("AppFlowy Configuration");
@ -46,30 +65,60 @@ impl fmt::Debug for AppFlowyCoreConfig {
}
fn make_user_data_folder(root: &str, url: &str) -> String {
// Isolate the user data folder by using the base url of AppFlowy cloud. This is to avoid
// the user data folder being shared by different AppFlowy cloud.
let storage_path = if !url.is_empty() {
let server_base64 = URL_SAFE_ENGINE.encode(url);
format!("{}_{}", root, server_base64)
// If a URL is provided, try to parse it and extract the domain name.
// This isolates the user data folder by the domain, which prevents data sharing
// between different AppFlowy cloud instances.
print!("Creating user data folder for URL: {}, root:{}", url, root);
let mut storage_path = if url.is_empty() {
PathBuf::from(root)
} else {
root.to_string()
let server_base64 = URL_SAFE_ENGINE.encode(url);
PathBuf::from(format!("{}_{}", root, server_base64))
};
// Only use new storage path if the old one doesn't exist
if !storage_path.exists() {
let anon_path = format!("{}_anonymous", root);
// We use domain name as suffix to isolate the user data folder since version 0.8.9
let new_storage_path = if url.is_empty() {
// if the url is empty, then it's anonymous mode
anon_path
} else {
match Url::parse(url) {
Ok(parsed_url) => {
if let Some(domain) = parsed_url.host_str() {
format!("{}_{}", root, domain)
} else {
anon_path
}
},
Err(_) => anon_path,
}
};
storage_path = PathBuf::from(new_storage_path);
}
// Copy the user data folder from the root path to the isolated path
// The root path without any suffix is the created by the local version AppFlowy
if !Path::new(&storage_path).exists() && Path::new(root).exists() {
info!("Copy dir from {} to {}", root, storage_path);
if !storage_path.exists() && Path::new(root).exists() {
info!("Copy dir from {} to {:?}", root, storage_path);
let src = Path::new(root);
match copy_dir_recursive(src, Path::new(&storage_path)) {
Ok(_) => storage_path,
match copy_dir_recursive(src, &storage_path) {
Ok(_) => storage_path
.into_os_string()
.into_string()
.unwrap_or_else(|_| root.to_string()),
Err(err) => {
// when the copy dir failed, use the root path as the storage path
error!("Copy dir failed: {}", err);
root.to_string()
},
}
} else {
storage_path
.into_os_string()
.into_string()
.unwrap_or_else(|_| root.to_string())
}
}
@ -83,15 +132,11 @@ impl AppFlowyCoreConfig {
name: String,
) -> Self {
let cloud_config = AFCloudConfiguration::from_env().ok();
let mut log_crates = vec![];
// By default enable sync trace log
let log_crates = vec!["sync_trace_log".to_string()];
let storage_path = match &cloud_config {
None => custom_application_path,
Some(config) => {
if config.enable_sync_trace {
log_crates.push("sync_trace_log".to_string());
}
make_user_data_folder(&custom_application_path, &config.base_url)
},
Some(config) => make_user_data_folder(&custom_application_path, &config.base_url),
};
let log_filter = create_log_filter(

View file

@ -21,6 +21,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Weak};
use tracing::{error, info};
use uuid::Uuid;
pub struct ChatDepsResolver;
@ -56,9 +57,9 @@ struct ChatQueryServiceImpl {
impl AIExternalService for ChatQueryServiceImpl {
async fn query_chat_rag_ids(
&self,
parent_view_id: &str,
chat_id: &str,
) -> Result<Vec<String>, FlowyError> {
parent_view_id: &Uuid,
chat_id: &Uuid,
) -> Result<Vec<Uuid>, FlowyError> {
let mut ids = self
.folder_service
.get_surrounding_view_ids_with_view_layout(parent_view_id, ViewLayout::Document)
@ -72,9 +73,9 @@ impl AIExternalService for ChatQueryServiceImpl {
}
async fn sync_rag_documents(
&self,
workspace_id: &str,
rag_ids: Vec<String>,
mut rag_metadata_map: HashMap<String, AFCollabMetadata>,
workspace_id: &Uuid,
rag_ids: Vec<Uuid>,
mut rag_metadata_map: HashMap<Uuid, AFCollabMetadata>,
) -> Result<Vec<AFCollabMetadata>, FlowyError> {
let mut result = Vec::new();
@ -96,7 +97,7 @@ impl AIExternalService for ChatQueryServiceImpl {
if let Ok(prev_sv) = StateVector::decode_v1(&metadata.prev_sync_state_vector) {
let collab = Collab::new_with_source(
CollabOrigin::Empty,
&rag_id,
&rag_id.to_string(),
DataSource::DocStateV1(query_collab.encoded_collab.doc_state.to_vec()),
vec![],
false,
@ -111,7 +112,7 @@ impl AIExternalService for ChatQueryServiceImpl {
// Perform full sync if changes are detected or no state vector is found
let params = FullSyncCollabParams {
object_id: rag_id.clone(),
object_id: rag_id,
collab_type: CollabType::Document,
encoded_collab: query_collab.encoded_collab.clone(),
};
@ -125,7 +126,7 @@ impl AIExternalService for ChatQueryServiceImpl {
} else {
info!("[Chat] full sync rag document: {}", rag_id);
result.push(AFCollabMetadata {
object_id: rag_id,
object_id: rag_id.to_string(),
updated_at: timestamp(),
prev_sync_state_vector: query_collab.encoded_collab.state_vector.to_vec(),
collab_type: CollabType::Document as i32,
@ -136,7 +137,7 @@ impl AIExternalService for ChatQueryServiceImpl {
Ok(result)
}
async fn notify_did_send_message(&self, chat_id: &str, message: &str) -> Result<(), FlowyError> {
async fn notify_did_send_message(&self, chat_id: &Uuid, message: &str) -> Result<(), FlowyError> {
info!(
"notify_did_send_message: chat_id: {}, message: {}",
chat_id, message
@ -169,7 +170,7 @@ impl AIUserService for ChatUserServiceImpl {
self.upgrade_user()?.device_id()
}
fn workspace_id(&self) -> Result<String, FlowyError> {
fn workspace_id(&self) -> Result<Uuid, FlowyError> {
self.upgrade_user()?.workspace_id()
}

View file

@ -7,16 +7,6 @@ use collab::core::origin::{CollabClient, CollabOrigin};
use collab::entity::EncodedCollab;
use collab::preclude::CollabPlugin;
use collab_entity::CollabType;
use flowy_search_pub::cloud::SearchCloudService;
use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use tokio_stream::wrappers::WatchStream;
use tracing::{debug, info};
use collab_integrate::collab_builder::{
CollabCloudPluginProvider, CollabPluginProviderContext, CollabPluginProviderType,
};
@ -37,12 +27,24 @@ use flowy_folder_pub::cloud::{
Workspace, WorkspaceRecord,
};
use flowy_folder_pub::entities::PublishPayload;
use flowy_search_pub::cloud::SearchCloudService;
use flowy_server_pub::af_cloud_config::AFCloudConfiguration;
use flowy_storage_pub::cloud::{ObjectIdentity, ObjectValue, StorageCloudService};
use flowy_storage_pub::storage::{CompletedPartRequest, CreateUploadResponse, UploadPartResponse};
use flowy_user_pub::cloud::{UserCloudService, UserCloudServiceProvider};
use flowy_user_pub::entities::{Authenticator, UserTokenState};
use lib_infra::async_trait::async_trait;
use serde_json::Value;
use std::collections::HashMap;
use std::path::Path;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
use tokio_stream::wrappers::WatchStream;
use tracing::log::error;
use tracing::{debug, info};
use uuid::Uuid;
use crate::server_layer::{Server, ServerProvider};
@ -82,7 +84,7 @@ impl StorageCloudService for ServerProvider {
async fn get_object_url_v1(
&self,
workspace_id: &str,
workspace_id: &Uuid,
parent_dir: &str,
file_id: &str,
) -> FlowyResult<String> {
@ -93,7 +95,7 @@ impl StorageCloudService for ServerProvider {
.await
}
async fn parse_object_url_v1(&self, url: &str) -> Option<(String, String, String)> {
async fn parse_object_url_v1(&self, url: &str) -> Option<(Uuid, String, String)> {
self
.get_server()
.ok()?
@ -104,7 +106,7 @@ impl StorageCloudService for ServerProvider {
async fn create_upload(
&self,
workspace_id: &str,
workspace_id: &Uuid,
parent_dir: &str,
file_id: &str,
content_type: &str,
@ -119,7 +121,7 @@ impl StorageCloudService for ServerProvider {
async fn upload_part(
&self,
workspace_id: &str,
workspace_id: &Uuid,
parent_dir: &str,
upload_id: &str,
file_id: &str,
@ -142,7 +144,7 @@ impl StorageCloudService for ServerProvider {
async fn complete_upload(
&self,
workspace_id: &str,
workspace_id: &Uuid,
parent_dir: &str,
upload_id: &str,
file_id: &str,
@ -233,10 +235,9 @@ impl FolderCloudService for ServerProvider {
server.folder_service().create_workspace(uid, &name).await
}
async fn open_workspace(&self, workspace_id: &str) -> Result<(), FlowyError> {
let workspace_id = workspace_id.to_string();
async fn open_workspace(&self, workspace_id: &Uuid) -> Result<(), FlowyError> {
let server = self.get_server()?;
server.folder_service().open_workspace(&workspace_id).await
server.folder_service().open_workspace(workspace_id).await
}
async fn get_all_workspace(&self) -> Result<Vec<WorkspaceRecord>, FlowyError> {
@ -246,7 +247,7 @@ impl FolderCloudService for ServerProvider {
async fn get_folder_data(
&self,
workspace_id: &str,
workspace_id: &Uuid,
uid: &i64,
) -> Result<Option<FolderData>, FlowyError> {
let server = self.get_server()?;
@ -272,10 +273,10 @@ impl FolderCloudService for ServerProvider {
async fn get_folder_doc_state(
&self,
workspace_id: &str,
workspace_id: &Uuid,
uid: i64,
collab_type: CollabType,
object_id: &str,
object_id: &Uuid,
) -> Result<Vec<u8>, FlowyError> {
let server = self.get_server()?;
@ -287,7 +288,7 @@ impl FolderCloudService for ServerProvider {
async fn batch_create_folder_collab_objects(
&self,
workspace_id: &str,
workspace_id: &Uuid,
objects: Vec<FolderCollabParams>,
) -> Result<(), FlowyError> {
let server = self.get_server()?;
@ -307,7 +308,7 @@ impl FolderCloudService for ServerProvider {
async fn publish_view(
&self,
workspace_id: &str,
workspace_id: &Uuid,
payload: Vec<PublishPayload>,
) -> Result<(), FlowyError> {
let server = self.get_server()?;
@ -320,8 +321,8 @@ impl FolderCloudService for ServerProvider {
async fn unpublish_views(
&self,
workspace_id: &str,
view_ids: Vec<String>,
workspace_id: &Uuid,
view_ids: Vec<Uuid>,
) -> Result<(), FlowyError> {
let server = self.get_server()?;
server
@ -330,15 +331,15 @@ impl FolderCloudService for ServerProvider {
.await
}
async fn get_publish_info(&self, view_id: &str) -> Result<PublishInfo, FlowyError> {
async fn get_publish_info(&self, view_id: &Uuid) -> Result<PublishInfo, FlowyError> {
let server = self.get_server()?;
server.folder_service().get_publish_info(view_id).await
}
async fn set_publish_name(
&self,
workspace_id: &str,
view_id: String,
workspace_id: &Uuid,
view_id: Uuid,
new_name: String,
) -> Result<(), FlowyError> {
let server = self.get_server()?;
@ -350,7 +351,7 @@ impl FolderCloudService for ServerProvider {
async fn set_publish_namespace(
&self,
workspace_id: &str,
workspace_id: &Uuid,
new_namespace: String,
) -> Result<(), FlowyError> {
let server = self.get_server()?;
@ -360,7 +361,7 @@ impl FolderCloudService for ServerProvider {
.await
}
async fn get_publish_namespace(&self, workspace_id: &str) -> Result<String, FlowyError> {
async fn get_publish_namespace(&self, workspace_id: &Uuid) -> Result<String, FlowyError> {
let server = self.get_server()?;
server
.folder_service()
@ -371,7 +372,7 @@ impl FolderCloudService for ServerProvider {
/// List all published views of the current workspace.
async fn list_published_views(
&self,
workspace_id: &str,
workspace_id: &Uuid,
) -> Result<Vec<PublishInfoView>, FlowyError> {
let server = self.get_server()?;
server
@ -382,7 +383,7 @@ impl FolderCloudService for ServerProvider {
async fn get_default_published_view_info(
&self,
workspace_id: &str,
workspace_id: &Uuid,
) -> Result<PublishInfo, FlowyError> {
let server = self.get_server()?;
server
@ -393,7 +394,7 @@ impl FolderCloudService for ServerProvider {
async fn set_default_published_view(
&self,
workspace_id: &str,
workspace_id: &Uuid,
view_id: uuid::Uuid,
) -> Result<(), FlowyError> {
let server = self.get_server()?;
@ -403,7 +404,7 @@ impl FolderCloudService for ServerProvider {
.await
}
async fn remove_default_published_view(&self, workspace_id: &str) -> Result<(), FlowyError> {
async fn remove_default_published_view(&self, workspace_id: &Uuid) -> Result<(), FlowyError> {
let server = self.get_server()?;
server
.folder_service()
@ -421,7 +422,7 @@ impl FolderCloudService for ServerProvider {
async fn full_sync_collab_object(
&self,
workspace_id: &str,
workspace_id: &Uuid,
params: FullSyncCollabParams,
) -> Result<(), FlowyError> {
self
@ -436,24 +437,22 @@ impl FolderCloudService for ServerProvider {
impl DatabaseCloudService for ServerProvider {
async fn get_database_encode_collab(
&self,
object_id: &str,
object_id: &Uuid,
collab_type: CollabType,
workspace_id: &str,
workspace_id: &Uuid,
) -> Result<Option<EncodedCollab>, FlowyError> {
let workspace_id = workspace_id.to_string();
let server = self.get_server()?;
let database_id = object_id.to_string();
server
.database_service()
.get_database_encode_collab(&database_id, collab_type, &workspace_id)
.get_database_encode_collab(object_id, collab_type, workspace_id)
.await
}
async fn create_database_encode_collab(
&self,
object_id: &str,
object_id: &Uuid,
collab_type: CollabType,
workspace_id: &str,
workspace_id: &Uuid,
encoded_collab: EncodedCollab,
) -> Result<(), FlowyError> {
let server = self.get_server()?;
@ -465,30 +464,28 @@ impl DatabaseCloudService for ServerProvider {
async fn batch_get_database_encode_collab(
&self,
object_ids: Vec<String>,
object_ids: Vec<Uuid>,
object_ty: CollabType,
workspace_id: &str,
workspace_id: &Uuid,
) -> Result<EncodeCollabByOid, FlowyError> {
let workspace_id = workspace_id.to_string();
let server = self.get_server()?;
server
.database_service()
.batch_get_database_encode_collab(object_ids, object_ty, &workspace_id)
.batch_get_database_encode_collab(object_ids, object_ty, workspace_id)
.await
}
async fn get_database_collab_object_snapshots(
&self,
object_id: &str,
object_id: &Uuid,
limit: usize,
) -> Result<Vec<DatabaseSnapshot>, FlowyError> {
let server = self.get_server()?;
let database_id = object_id.to_string();
server
.database_service()
.get_database_collab_object_snapshots(&database_id, limit)
.get_database_collab_object_snapshots(object_id, limit)
.await
}
}
@ -497,29 +494,29 @@ impl DatabaseCloudService for ServerProvider {
impl DatabaseAIService for ServerProvider {
async fn summary_database_row(
&self,
workspace_id: &str,
object_id: &str,
summary_row: SummaryRowContent,
_workspace_id: &Uuid,
_object_id: &Uuid,
_summary_row: SummaryRowContent,
) -> Result<String, FlowyError> {
self
.get_server()?
.database_ai_service()
.ok_or_else(FlowyError::not_support)?
.summary_database_row(workspace_id, object_id, summary_row)
.summary_database_row(_workspace_id, _object_id, _summary_row)
.await
}
async fn translate_database_row(
&self,
workspace_id: &str,
translate_row: TranslateRowContent,
language: &str,
_workspace_id: &Uuid,
_translate_row: TranslateRowContent,
_language: &str,
) -> Result<TranslateRowResponse, FlowyError> {
self
.get_server()?
.database_ai_service()
.ok_or_else(FlowyError::not_support)?
.translate_database_row(workspace_id, translate_row, language)
.translate_database_row(_workspace_id, _translate_row, _language)
.await
}
}
@ -528,8 +525,8 @@ impl DatabaseAIService for ServerProvider {
impl DocumentCloudService for ServerProvider {
async fn get_document_doc_state(
&self,
document_id: &str,
workspace_id: &str,
document_id: &Uuid,
workspace_id: &Uuid,
) -> Result<Vec<u8>, FlowyError> {
let server = self.get_server()?;
server
@ -540,7 +537,7 @@ impl DocumentCloudService for ServerProvider {
async fn get_document_snapshots(
&self,
document_id: &str,
document_id: &Uuid,
limit: usize,
workspace_id: &str,
) -> Result<Vec<DocumentSnapshot>, FlowyError> {
@ -554,8 +551,8 @@ impl DocumentCloudService for ServerProvider {
async fn get_document_data(
&self,
document_id: &str,
workspace_id: &str,
document_id: &Uuid,
workspace_id: &Uuid,
) -> Result<Option<DocumentData>, FlowyError> {
let server = self.get_server()?;
server
@ -566,8 +563,8 @@ impl DocumentCloudService for ServerProvider {
async fn create_document_collab(
&self,
workspace_id: &str,
document_id: &str,
workspace_id: &Uuid,
document_id: &Uuid,
encoded_collab: EncodedCollab,
) -> Result<(), FlowyError> {
let server = self.get_server()?;
@ -611,26 +608,37 @@ impl CollabCloudPluginProvider for ServerProvider {
collab_object.uid,
collab_object.device_id.clone(),
));
let sync_object = SyncObject::new(
&collab_object.object_id,
&collab_object.workspace_id,
collab_object.collab_type,
&collab_object.device_id,
);
let (sink, stream) = (channel.sink(), channel.stream());
let sink_config = SinkConfig::new().send_timeout(8);
let sync_plugin = SyncPlugin::new(
origin,
sync_object,
local_collab,
sink,
sink_config,
stream,
Some(channel),
ws_connect_state,
Some(Duration::from_secs(60)),
);
plugins.push(Box::new(sync_plugin));
if let (Ok(object_id), Ok(workspace_id)) = (
Uuid::from_str(&collab_object.object_id),
Uuid::from_str(&collab_object.workspace_id),
) {
let sync_object = SyncObject::new(
object_id,
workspace_id,
collab_object.collab_type,
&collab_object.device_id,
);
let (sink, stream) = (channel.sink(), channel.stream());
let sink_config = SinkConfig::new().send_timeout(8);
let sync_plugin = SyncPlugin::new(
origin,
sync_object,
local_collab,
sink,
sink_config,
stream,
Some(channel),
ws_connect_state,
Some(Duration::from_secs(60)),
);
plugins.push(Box::new(sync_plugin));
} else {
error!(
"Failed to parse collab object id: {}",
collab_object.object_id
);
}
},
Ok(None) => {
tracing::error!("🔴Failed to get collab ws channel: channel is none");
@ -655,9 +663,9 @@ impl ChatCloudService for ServerProvider {
async fn create_chat(
&self,
uid: &i64,
workspace_id: &str,
chat_id: &str,
rag_ids: Vec<String>,
workspace_id: &Uuid,
chat_id: &Uuid,
rag_ids: Vec<Uuid>,
) -> Result<(), FlowyError> {
let server = self.get_server();
server?
@ -668,26 +676,24 @@ impl ChatCloudService for ServerProvider {
async fn create_question(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message: &str,
message_type: ChatMessageType,
metadata: &[ChatMessageMetadata],
) -> Result<ChatMessage, FlowyError> {
let workspace_id = workspace_id.to_string();
let chat_id = chat_id.to_string();
let message = message.to_string();
self
.get_server()?
.chat_service()
.create_question(&workspace_id, &chat_id, &message, message_type, metadata)
.create_question(workspace_id, chat_id, &message, message_type, metadata)
.await
}
async fn create_answer(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message: &str,
question_id: i64,
metadata: Option<serde_json::Value>,
@ -701,25 +707,23 @@ impl ChatCloudService for ServerProvider {
async fn stream_answer(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message_id: i64,
format: ResponseFormat,
ai_model: Option<AIModel>,
) -> Result<StreamAnswer, FlowyError> {
let workspace_id = workspace_id.to_string();
let chat_id = chat_id.to_string();
let server = self.get_server()?;
server
.chat_service()
.stream_answer(&workspace_id, &chat_id, message_id, format, ai_model)
.stream_answer(workspace_id, chat_id, message_id, format, ai_model)
.await
}
async fn get_chat_messages(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
offset: MessageCursor,
limit: u64,
) -> Result<RepeatedChatMessage, FlowyError> {
@ -732,8 +736,8 @@ impl ChatCloudService for ServerProvider {
async fn get_question_from_answer_id(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
answer_message_id: i64,
) -> Result<ChatMessage, FlowyError> {
self
@ -745,8 +749,8 @@ impl ChatCloudService for ServerProvider {
async fn get_related_message(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
message_id: i64,
) -> Result<RepeatedRelatedQuestion, FlowyError> {
self
@ -758,8 +762,8 @@ impl ChatCloudService for ServerProvider {
async fn get_answer(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
question_message_id: i64,
) -> Result<ChatMessage, FlowyError> {
let server = self.get_server();
@ -771,23 +775,22 @@ impl ChatCloudService for ServerProvider {
async fn stream_complete(
&self,
workspace_id: &str,
workspace_id: &Uuid,
params: CompleteTextParams,
ai_model: Option<AIModel>,
) -> Result<StreamComplete, FlowyError> {
let workspace_id = workspace_id.to_string();
let server = self.get_server()?;
server
.chat_service()
.stream_complete(&workspace_id, params, ai_model)
.stream_complete(workspace_id, params, ai_model)
.await
}
async fn embed_file(
&self,
workspace_id: &str,
workspace_id: &Uuid,
file_path: &Path,
chat_id: &str,
chat_id: &Uuid,
metadata: Option<HashMap<String, Value>>,
) -> Result<(), FlowyError> {
self
@ -797,7 +800,7 @@ impl ChatCloudService for ServerProvider {
.await
}
async fn get_local_ai_config(&self, workspace_id: &str) -> Result<LocalAIConfig, FlowyError> {
async fn get_local_ai_config(&self, workspace_id: &Uuid) -> Result<LocalAIConfig, FlowyError> {
self
.get_server()?
.chat_service()
@ -807,7 +810,7 @@ impl ChatCloudService for ServerProvider {
async fn get_workspace_plan(
&self,
workspace_id: &str,
workspace_id: &Uuid,
) -> Result<Vec<SubscriptionPlan>, FlowyError> {
self
.get_server()?
@ -818,8 +821,8 @@ impl ChatCloudService for ServerProvider {
async fn get_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
) -> Result<ChatSettings, FlowyError> {
self
.get_server()?
@ -830,8 +833,8 @@ impl ChatCloudService for ServerProvider {
async fn update_chat_settings(
&self,
workspace_id: &str,
chat_id: &str,
workspace_id: &Uuid,
chat_id: &Uuid,
params: UpdateChatParams,
) -> Result<(), FlowyError> {
self
@ -841,7 +844,7 @@ impl ChatCloudService for ServerProvider {
.await
}
async fn get_available_models(&self, workspace_id: &str) -> Result<ModelList, FlowyError> {
async fn get_available_models(&self, workspace_id: &Uuid) -> Result<ModelList, FlowyError> {
self
.get_server()?
.chat_service()
@ -849,7 +852,7 @@ impl ChatCloudService for ServerProvider {
.await
}
async fn get_workspace_default_model(&self, workspace_id: &str) -> Result<String, FlowyError> {
async fn get_workspace_default_model(&self, workspace_id: &Uuid) -> Result<String, FlowyError> {
self
.get_server()?
.chat_service()
@ -862,7 +865,7 @@ impl ChatCloudService for ServerProvider {
impl SearchCloudService for ServerProvider {
async fn document_search(
&self,
workspace_id: &str,
workspace_id: &Uuid,
query: String,
) -> Result<Vec<SearchDocumentResponseItem>, FlowyError> {
let server = self.get_server()?;

View file

@ -13,6 +13,7 @@ use collab_integrate::collab_builder::WorkspaceCollabIntegrate;
use lib_infra::util::timestamp;
use std::sync::{Arc, Weak};
use tracing::debug;
use uuid::Uuid;
pub struct SnapshotDBImpl(pub Weak<AuthenticateUser>);
@ -222,12 +223,12 @@ impl WorkspaceCollabIntegrateImpl {
}
impl WorkspaceCollabIntegrate for WorkspaceCollabIntegrateImpl {
fn workspace_id(&self) -> Result<String, anyhow::Error> {
fn workspace_id(&self) -> Result<Uuid, FlowyError> {
let workspace_id = self.upgrade_user()?.workspace_id()?;
Ok(workspace_id)
}
fn device_id(&self) -> Result<String, anyhow::Error> {
fn device_id(&self) -> Result<String, FlowyError> {
Ok(self.upgrade_user()?.user_config.device_id.clone())
}
}

View file

@ -13,6 +13,7 @@ use lib_infra::async_trait::async_trait;
use lib_infra::priority_task::TaskDispatcher;
use std::sync::{Arc, Weak};
use tokio::sync::RwLock;
use uuid::Uuid;
pub struct DatabaseDepsResolver();
@ -47,41 +48,41 @@ struct DatabaseAIServiceMiddleware {
impl DatabaseAIService for DatabaseAIServiceMiddleware {
async fn summary_database_row(
&self,
workspace_id: &str,
object_id: &str,
summary_row: SummaryRowContent,
workspace_id: &Uuid,
object_id: &Uuid,
_summary_row: SummaryRowContent,
) -> Result<String, FlowyError> {
if self.ai_manager.local_ai.is_running() {
self
.ai_manager
.local_ai
.summary_database_row(summary_row)
.summary_database_row(_summary_row)
.await
.map_err(|err| FlowyError::local_ai().with_context(err))
} else {
self
.ai_service
.summary_database_row(workspace_id, object_id, summary_row)
.summary_database_row(workspace_id, object_id, _summary_row)
.await
}
}
async fn translate_database_row(
&self,
workspace_id: &str,
translate_row: TranslateRowContent,
language: &str,
_workspace_id: &Uuid,
_translate_row: TranslateRowContent,
_language: &str,
) -> Result<TranslateRowResponse, FlowyError> {
if self.ai_manager.local_ai.is_running() {
let data = LocalAITranslateRowData {
cells: translate_row
cells: _translate_row
.into_iter()
.map(|row| LocalAITranslateItem {
title: row.title,
content: row.content,
})
.collect(),
language: language.to_string(),
language: _language.to_string(),
include_header: false,
};
let resp = self
@ -95,7 +96,7 @@ impl DatabaseAIService for DatabaseAIServiceMiddleware {
} else {
self
.ai_service
.translate_database_row(workspace_id, translate_row, language)
.translate_database_row(_workspace_id, _translate_row, _language)
.await
}
}
@ -121,11 +122,11 @@ impl DatabaseUser for DatabaseUserImpl {
self.upgrade_user()?.get_collab_db(uid)
}
fn workspace_id(&self) -> Result<String, FlowyError> {
fn workspace_id(&self) -> Result<Uuid, FlowyError> {
self.upgrade_user()?.workspace_id()
}
fn workspace_database_object_id(&self) -> Result<String, FlowyError> {
fn workspace_database_object_id(&self) -> Result<Uuid, FlowyError> {
self.upgrade_user()?.workspace_database_object_id()
}
}

Some files were not shown because too many files have changed in this diff Show more