mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-26 10:45:57 +08:00
fix(core): ai replace selection (#11875)
### TL;DR * Fix the issue of inaccurate content replacement in AI Replace Selection * Optimize unit Tests utils ### What Changed 1. Fixed the issue of inaccurate content replacement in AI Replace Selection: - Convert the AI Answer into a Snapshot, then transform it into a sequence of Blocks ready for insertion. - Invoke the `replaceSelectedTextWithBlocks` command to replace the current selection with blocks (given the complexity of block combinations, this command uses [ts-pattern](https://github.com/gvergnaud/ts-pattern) implementation to ensure compile-time prevention of pattern handling omissions). 2. Optimized unit test assertions for commands, now allowing direct document content comparison using `toEqualDoc`. ```ts const host = affine` <affine-page id="page"> <affine-note id="note"> <affine-paragraph id="paragraph-1">Hel<anchor />lo</affine-paragraph> <affine-paragraph id="paragraph-2">Wor<focus />ld</affine-paragraph> </affine-note> </affine-page> `; // .... const expected = affine` <affine-page id="page"> <affine-note id="note"> <affine-paragraph id="paragraph-1">Hel111</affine-paragraph> <affine-code id="code"></affine-code> <affine-paragraph id="paragraph-2">222ld</affine-paragraph> </affine-note> </affine-page> `; expect(host.doc).toEqualDoc(expected.doc); ``` 3. Added support for text cursors in unit test template syntax. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit > CLOSE BS-3278 - **New Features** - Introduced the ability to replace selected text in documents with blocks, supporting advanced merging and insertion scenarios for various block types. - **Bug Fixes** - Improved handling of text selection and cursor placement in document templates used for testing. - **Tests** - Added comprehensive tests for replacing selected text with blocks, including edge cases and complex selection scenarios. - Enhanced test utilities for document structure comparison and selection handling. - Updated end-to-end tests to verify correct replacement of selected text via AI-driven actions. - **Chores** - Added and updated dependencies to support new features. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { deleteTextCommand } from '@blocksuite/affine/inlines/preset';
|
||||
import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace';
|
||||
import { defaultImageProxyMiddleware } from '@blocksuite/affine/shared/adapters';
|
||||
import { replaceSelectedTextWithBlocksCommand } from '@blocksuite/affine/shared/commands';
|
||||
import { isInsideEdgelessEditor } from '@blocksuite/affine/shared/utils';
|
||||
import {
|
||||
type BlockComponent,
|
||||
@@ -8,7 +9,12 @@ import {
|
||||
SurfaceSelection,
|
||||
type TextSelection,
|
||||
} from '@blocksuite/affine/std';
|
||||
import { type BlockModel, Slice } from '@blocksuite/affine/store';
|
||||
import {
|
||||
type BlockModel,
|
||||
type BlockSnapshot,
|
||||
Slice,
|
||||
} from '@blocksuite/affine/store';
|
||||
import { Doc as YDoc } from 'yjs';
|
||||
|
||||
import {
|
||||
insertFromMarkdown,
|
||||
@@ -109,19 +115,53 @@ export const replace = async (
|
||||
);
|
||||
|
||||
if (textSelection) {
|
||||
host.std.command.exec(deleteTextCommand, { textSelection });
|
||||
const { snapshot, transformer } = await markdownToSnapshot(
|
||||
content,
|
||||
host.store,
|
||||
host
|
||||
);
|
||||
if (snapshot) {
|
||||
await transformer.snapshotToSlice(
|
||||
snapshot,
|
||||
host.store,
|
||||
firstBlockParent.model.id,
|
||||
firstIndex + 1
|
||||
const collection = new WorkspaceImpl({
|
||||
id: 'AI_REPLACE',
|
||||
rootDoc: new YDoc({ guid: 'AI_REPLACE' }),
|
||||
});
|
||||
collection.meta.initialize();
|
||||
const fragmentDoc = collection.createDoc();
|
||||
|
||||
try {
|
||||
const fragment = fragmentDoc.getStore();
|
||||
fragmentDoc.load();
|
||||
|
||||
const rootId = fragment.addBlock('affine:page');
|
||||
fragment.addBlock('affine:surface', {}, rootId);
|
||||
const noteId = fragment.addBlock('affine:note', {}, rootId);
|
||||
|
||||
const { snapshot, transformer } = await markdownToSnapshot(
|
||||
content,
|
||||
fragment,
|
||||
host
|
||||
);
|
||||
|
||||
if (snapshot) {
|
||||
const blockSnapshots = (
|
||||
snapshot.content[0].flavour === 'affine:note'
|
||||
? snapshot.content[0].children
|
||||
: snapshot.content
|
||||
) as BlockSnapshot[];
|
||||
|
||||
const blocks = (
|
||||
await Promise.all(
|
||||
blockSnapshots.map(async blockSnapshot => {
|
||||
return await transformer.snapshotToBlock(
|
||||
blockSnapshot,
|
||||
fragment,
|
||||
noteId,
|
||||
0
|
||||
);
|
||||
})
|
||||
)
|
||||
).filter(block => block) as BlockModel[];
|
||||
host.std.command.exec(replaceSelectedTextWithBlocksCommand, {
|
||||
textSelection,
|
||||
blocks,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
collection.dispose();
|
||||
}
|
||||
} else {
|
||||
selectedModels.forEach(model => {
|
||||
|
||||
Reference in New Issue
Block a user