From 039c191d1f653667e5d05c7b61d7b40b1900210c Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Tue, 25 Mar 2025 22:41:35 +0800 Subject: [PATCH] fix: ai text insertion (#7615) * fix: dont scroll to ai writer node if path not found * chore: rename text robot clear method and add reset * fix: insert position is off if using ai writer multiple times * chore: reorganize code * fix: undo not working after accept --- .../ai_writer_block_operations.dart | 30 ++++- .../ai/operations/ai_writer_cubit.dart | 117 +++++------------- .../ai/widgets/ai_writer_scroll_wrapper.dart | 8 +- .../base/markdown_text_robot.dart | 8 +- 4 files changed, 65 insertions(+), 98 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_block_operations.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_block_operations.dart index d48c655e56..1b495a5b23 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_block_operations.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_block_operations.dart @@ -48,16 +48,16 @@ Future removeAiWriterNode( ); } -void formatSelection( +Future formatSelection( EditorState editorState, Selection selection, - Transaction transaction, ApplySuggestionFormatType formatType, -) { +) async { final nodes = editorState.getNodesInSelection(selection).toList(); if (nodes.isEmpty) { return; } + final transaction = editorState.transaction; if (nodes.length == 1) { final node = nodes.removeAt(0); @@ -103,25 +103,43 @@ void formatSelection( } transaction.compose(); + await editorState.apply( + transaction, + options: ApplyOptions( + inMemoryUpdate: true, + recordUndo: false, + ), + withUpdateSelection: false, + ); } -Position ensurePreviousNodeIsEmptyParagraph( +Future ensurePreviousNodeIsEmptyParagraph( EditorState editorState, Node aiWriterNode, - Transaction transaction, -) { +) async { final previous = aiWriterNode.previous; final needsEmptyParagraphNode = previous == null || previous.type != ParagraphBlockKeys.type || (previous.delta?.toPlainText().isNotEmpty ?? false); final Position position; + final transaction = editorState.transaction; + if (needsEmptyParagraphNode) { position = Position(path: aiWriterNode.path); transaction.insertNode(aiWriterNode.path, paragraphNode()); } else { position = Position(path: previous.path); } + transaction.afterSelection = Selection.collapsed(position); + + await editorState.apply( + transaction, + options: ApplyOptions( + inMemoryUpdate: true, + recordUndo: false, + ), + ); return position; } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_cubit.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_cubit.dart index 07d5d84ae9..20247bf3a3 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_cubit.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_cubit.dart @@ -54,6 +54,7 @@ class AiWriterCubit extends Cubit { if (withDiscard) { await _textRobot.discard(); } + _textRobot.clear(); _textRobot.reset(); onRemoveNode?.call(); records.clear(); @@ -65,21 +66,11 @@ class AiWriterCubit extends Cubit { if (selection == null) { return; } - final transaction = editorState.transaction; - formatSelection( + await formatSelection( editorState, selection, - transaction, ApplySuggestionFormatType.clear, ); - await editorState.apply( - transaction, - options: const ApplyOptions( - inMemoryUpdate: true, - recordUndo: false, - ), - withUpdateSelection: false, - ); } if (aiWriterNode != null) { await removeAiWriterNode(editorState, aiWriterNode!); @@ -122,7 +113,7 @@ class AiWriterCubit extends Cubit { } await _textRobot.discard(); - _textRobot.reset(); + _textRobot.clear(); switch (command) { case AiWriterCommand.continueWriting: @@ -222,11 +213,15 @@ class AiWriterCubit extends Cubit { if (action case SuggestionAction.accept) { await _textRobot.persist(); + await formatSelection( + editorState, + selection, + ApplySuggestionFormatType.clear, + ); final nodes = editorState.getNodesInSelection(selection); final transaction = editorState.transaction..deleteNodes(nodes); await editorState.apply( transaction, - options: const ApplyOptions(recordUndo: false), withUpdateSelection: false, ); await exit(withDiscard: false, withUnformat: false); @@ -235,6 +230,8 @@ class AiWriterCubit extends Cubit { if (action case SuggestionAction.keep) { await _textRobot.persist(); + await exit(withDiscard: false); + return; } if (action case SuggestionAction.insertBelow) { @@ -244,20 +241,9 @@ class AiWriterCubit extends Cubit { final command = (state as ReadyAiWriterState).command; final markdownText = (state as ReadyAiWriterState).markdownText; if (command == AiWriterCommand.explain && markdownText.isNotEmpty) { - final transaction = editorState.transaction; - final position = ensurePreviousNodeIsEmptyParagraph( + final position = await ensurePreviousNodeIsEmptyParagraph( editorState, aiWriterNode!, - transaction, - ); - transaction.afterSelection = null; - await editorState.apply( - transaction, - options: ApplyOptions( - inMemoryUpdate: true, - recordUndo: false, - ), - withUpdateSelection: false, ); _textRobot.start(position: position); await _textRobot.persist(markdownText: markdownText); @@ -265,21 +251,13 @@ class AiWriterCubit extends Cubit { await _textRobot.persist(); } - final transaction = editorState.transaction; - formatSelection( + await formatSelection( editorState, selection, - transaction, ApplySuggestionFormatType.clear, ); - await editorState.apply( - transaction, - options: const ApplyOptions(recordUndo: false), - withUpdateSelection: false, - ); + await exit(withDiscard: false); } - - await exit(withDiscard: false); } bool hasUnusedResponse() { @@ -308,22 +286,20 @@ class AiWriterCubit extends Cubit { if (command == AiWriterCommand.continueWriting) { return (true, ''); - } else { - if (selection.isCollapsed) { - return (true, ''); - } else { - final selectionText = - await editorState.getMarkdownInSelection(selection); + } + if (selection.isCollapsed) { + return (true, ''); + } - if (command == AiWriterCommand.userQuestion) { - records.add( - AiWriterRecord.user(content: selectionText, format: null), - ); - return (true, ''); - } else { - return (true, selectionText); - } - } + final selectionText = await editorState.getMarkdownInSelection(selection); + + if (command == AiWriterCommand.userQuestion) { + records.add( + AiWriterRecord.user(content: selectionText, format: null), + ); + return (true, ''); + } else { + return (true, selectionText); } } @@ -360,19 +336,9 @@ class AiWriterCubit extends Cubit { sourceIds: selectedSourcesNotifier.value, completionType: command.toCompletionType(), onStart: () async { - final transaction = editorState.transaction; - final position = ensurePreviousNodeIsEmptyParagraph( + final position = await ensurePreviousNodeIsEmptyParagraph( editorState, aiWriterNode!, - transaction, - ); - await editorState.apply( - transaction, - options: ApplyOptions( - inMemoryUpdate: true, - recordUndo: false, - ), - withUpdateSelection: false, ); _textRobot.start(position: position); records.add( @@ -461,20 +427,9 @@ class AiWriterCubit extends Cubit { sourceIds: selectedSourcesNotifier.value, format: predefinedFormat, onStart: () async { - final transaction = editorState.transaction; - final position = ensurePreviousNodeIsEmptyParagraph( + final position = await ensurePreviousNodeIsEmptyParagraph( editorState, aiWriterNode!, - transaction, - ); - transaction.afterSelection = null; - await editorState.apply( - transaction, - options: ApplyOptions( - inMemoryUpdate: true, - recordUndo: false, - ), - withUpdateSelection: false, ); _textRobot.start(position: position); records.add( @@ -555,26 +510,14 @@ class AiWriterCubit extends Cubit { history: records, sourceIds: selectedSourcesNotifier.value, onStart: () async { - final transaction = editorState.transaction; - formatSelection( + await formatSelection( editorState, selection, - transaction, ApplySuggestionFormatType.original, ); - final position = ensurePreviousNodeIsEmptyParagraph( + final position = await ensurePreviousNodeIsEmptyParagraph( editorState, aiWriterNode!, - transaction, - ); - transaction.afterSelection = null; - await editorState.apply( - transaction, - options: ApplyOptions( - inMemoryUpdate: true, - recordUndo: false, - ), - withUpdateSelection: false, ); _textRobot.start(position: position); records.add( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart index 8a054de1c0..326d1a44ab 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart @@ -198,9 +198,11 @@ class _AiWriterScrollWrapperState extends State { throttler.call(() { if (aiWriterCubit.aiWriterNode != null) { final path = aiWriterCubit.aiWriterNode!.path; - widget.editorState.updateSelectionWithReason( - Selection.collapsed(Position(path: path)), - ); + if (path.isNotEmpty) { + widget.editorState.updateSelectionWithReason( + Selection.collapsed(Position(path: path)), + ); + } } }); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart index 62c024b31a..378c4d88cc 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart @@ -58,7 +58,7 @@ class MarkdownTextRobot { void start({ Position? position, }) { - _insertPosition ??= position ?? editorState.selection?.start; + _insertPosition = position ?? editorState.selection?.start; if (_enableDebug) { Log.info( @@ -148,11 +148,15 @@ class MarkdownTextRobot { } } - void reset() { + void clear() { _markdownText = ''; _insertedNodes = []; } + void reset() { + _insertPosition = null; + } + Future _refresh({ required bool inMemoryUpdate, bool updateSelection = false,