diff --git a/packages/frontend/core/src/__tests__/ai/utils/apply-model/generate-render-diff.spec.ts b/packages/frontend/core/src/__tests__/ai/utils/apply-model/generate-render-diff.spec.ts index 4fd489cdb6..90fd51169f 100644 --- a/packages/frontend/core/src/__tests__/ai/utils/apply-model/generate-render-diff.spec.ts +++ b/packages/frontend/core/src/__tests__/ai/utils/apply-model/generate-render-diff.spec.ts @@ -334,4 +334,58 @@ Inserted at tail. updates: {}, }); }); + + test('should handle interval insertions & deletions', () => { + const oldMd = ` + +# 1 + + +2 + + +3 + + +4 + + +5 +`; + const newMd = ` + +# 1 + + +2 + + +4 + + +6 + + +7 +`; + const diff = generateRenderDiff(oldMd, newMd); + expect(diff).toEqual({ + deletes: ['block-003', 'block-005'], + inserts: { + 'block-004': [ + { + id: 'block-006', + type: 'paragraph', + content: '6', + }, + { + id: 'block-007', + type: 'paragraph', + content: '7', + }, + ], + }, + updates: {}, + }); + }); }); diff --git a/packages/frontend/core/src/__tests__/ai/utils/apply-model/markdown-diff.spec.ts b/packages/frontend/core/src/__tests__/ai/utils/apply-model/markdown-diff.spec.ts index 9e5319d9ee..4728fc2306 100644 --- a/packages/frontend/core/src/__tests__/ai/utils/apply-model/markdown-diff.spec.ts +++ b/packages/frontend/core/src/__tests__/ai/utils/apply-model/markdown-diff.spec.ts @@ -21,6 +21,7 @@ This is a new paragraph. { op: 'insert', index: 1, + after: 'block-001', block: { id: 'block-002', type: 'paragraph', @@ -104,6 +105,7 @@ New paragraph. { op: 'insert', index: 2, + after: 'block-002', block: { id: 'block-004', type: 'paragraph', @@ -138,6 +140,7 @@ Second inserted paragraph. { op: 'insert', index: 1, + after: 'block-001', block: { id: 'block-002', type: 'paragraph', @@ -147,6 +150,7 @@ Second inserted paragraph. { op: 'insert', index: 2, + after: 'block-002', block: { id: 'block-003', type: 'paragraph', @@ -213,6 +217,7 @@ This is a new paragraph inserted after deletion. { op: 'insert', index: 2, + after: 'block-003', block: { id: 'block-004', type: 'paragraph', @@ -225,4 +230,133 @@ This is a new paragraph inserted after deletion. }, ]); }); + + test('should diff interval insertions & deletions', () => { + const oldMd = ` + +# 1 + + +2 + + +3 + + +4 + + +5 +`; + + const newMd = ` + +# 1 + + +2 + + +4 + + +6 + + +7 +`; + const { patches } = diffMarkdown(oldMd, newMd); + expect(patches).toEqual([ + { + op: 'insert', + index: 3, + after: 'block-004', + block: { + id: 'block-006', + type: 'paragraph', + content: '6', + }, + }, + { + op: 'insert', + index: 4, + after: 'block-006', + block: { + id: 'block-007', + type: 'paragraph', + content: '7', + }, + }, + { + op: 'delete', + id: 'block-003', + }, + { + op: 'delete', + id: 'block-005', + }, + ]); + }); + + test('should diff insertions after remove all', () => { + const oldMd = ` + +# 1 + + +2 +`; + + const newMd = ` + +3 + + +4 + + +5 +`; + const { patches } = diffMarkdown(oldMd, newMd); + expect(patches).toEqual([ + { + op: 'insert', + index: 0, + after: 'HEAD', + block: { + id: 'block-003', + type: 'paragraph', + content: '3', + }, + }, + { + op: 'insert', + index: 1, + after: 'block-003', + block: { + id: 'block-004', + type: 'paragraph', + content: '4', + }, + }, + { + op: 'insert', + index: 2, + after: 'block-004', + block: { + id: 'block-005', + type: 'paragraph', + content: '5', + }, + }, + { + op: 'delete', + id: 'block-001', + }, + { + op: 'delete', + id: 'block-002', + }, + ]); + }); }); diff --git a/packages/frontend/core/src/blocksuite/ai/utils/apply-model/generate-render-diff.ts b/packages/frontend/core/src/blocksuite/ai/utils/apply-model/generate-render-diff.ts index 4e0805f305..7473c64bd1 100644 --- a/packages/frontend/core/src/blocksuite/ai/utils/apply-model/generate-render-diff.ts +++ b/packages/frontend/core/src/blocksuite/ai/utils/apply-model/generate-render-diff.ts @@ -22,50 +22,57 @@ export interface RenderDiffs { * * * This is the fourth paragraph + * + * + * This is the fifth paragraph * ``` * * New markdown: * ```md * - * This is the first paragraph + * This is the 1st paragraph * - * - * This is the 3rd paragraph + * + * This is the second paragraph * - * - * New inserted paragraph 1 + * + * This is the fourth paragraph * * + * New inserted paragraph 1 + * + * * New inserted paragraph 2 * ``` * * The generated patches: * ```js * [ - * { op: 'insert', index: 2, block: { id: '005', ... } }, - * { op: 'insert', index: 3, bthirdlock: { id: '006', ... } }, - * { op: 'update', id: '003', content: 'This is the 3rd paragraph' }, - * { op: 'delete', id: '002' }, - * { op: 'delete', id: '004' } + * { op: 'insert', index: 3, after: '004', block: { id: '006', ... } }, + * { op: 'insert', index: 4, after: '006', block: { id: '007', ... } }, + * { op: 'update', id: '001', content: 'This is the 1st paragraph' }, + * { op: 'delete', id: '003' }, + * { op: 'delete', id: '005' } * ] * ``` * * UI expected: * ``` - * This is the first paragraph - * [DELETE DIFF] This is the second paragraph - * This is the third paragraph - * [DELETE DIFF] This is the fourth paragraph + * [UPDATE DIFF]This is the first paragraph + * This is the second paragraph + * [DELETE DIFF]This is the third paragraph + * This is the fourth paragraph * [INSERT DIFF] New inserted paragraph 1 * [INSERT DIFF] New inserted paragraph 2 + * [DELETE DIFF] This is the fifth paragraph * ``` * * The resulting diffMap: * ```js * { - * deletes: ['002', '004'], - * inserts: { 3: [block_005, block_006] }, - * updates: {} + * deletes: ['003', '005'], + * inserts: { '004': [block_006, block_007] }, + * updates: { '001': 'This is the 1st paragraph' } * } * ``` */ @@ -73,10 +80,7 @@ export function generateRenderDiff( originalMarkdown: string, changedMarkdown: string ) { - const { patches, oldBlocks } = diffMarkdown( - originalMarkdown, - changedMarkdown - ); + const { patches } = diffMarkdown(originalMarkdown, changedMarkdown); const diffMap: RenderDiffs = { deletes: [], @@ -84,19 +88,6 @@ export function generateRenderDiff( updates: {}, }; - const indexToBlockId: Record = {}; - oldBlocks.forEach((block, idx) => { - indexToBlockId[idx] = block.id; - }); - - function getPrevBlock(index: number) { - let start = index - 1; - while (!indexToBlockId[start] && start >= 0) { - start--; - } - return indexToBlockId[start] || 'HEAD'; - } - const insertGroups: Record = {}; let lastInsertKey: string | null = null; let lastInsertIndex: number | null = null; @@ -107,7 +98,7 @@ export function generateRenderDiff( diffMap.deletes.push(patch.id); break; case 'insert': { - const prevBlockId = getPrevBlock(patch.index); + const prevBlockId = patch.after; if ( lastInsertKey !== null && lastInsertIndex !== null && diff --git a/packages/frontend/core/src/blocksuite/ai/utils/apply-model/markdown-diff.ts b/packages/frontend/core/src/blocksuite/ai/utils/apply-model/markdown-diff.ts index d600317aea..2082f6bc2e 100644 --- a/packages/frontend/core/src/blocksuite/ai/utils/apply-model/markdown-diff.ts +++ b/packages/frontend/core/src/blocksuite/ai/utils/apply-model/markdown-diff.ts @@ -7,7 +7,7 @@ export type Block = { export type PatchOp = | { op: 'replace'; id: string; content: string } | { op: 'delete'; id: string } - | { op: 'insert'; index: number; block: Block }; + | { op: 'insert'; index: number; after: string; block: Block }; const BLOCK_MATCH_REGEXP = /^\s*/; @@ -61,7 +61,6 @@ function diffBlockLists(oldBlocks: Block[], newBlocks: Block[]): PatchOp[] { // Mark old blocks that have been handled const handledOld = new Set(); - // First process newBlocks in order newBlocks.forEach((newBlock, newIdx) => { const old = oldMap.get(newBlock.id); if (old) { @@ -74,9 +73,11 @@ function diffBlockLists(oldBlocks: Block[], newBlocks: Block[]): PatchOp[] { }); } } else { + const after = newIdx > 0 ? newBlocks[newIdx - 1].id : 'HEAD'; patch.push({ op: 'insert', index: newIdx, + after, block: { id: newBlock.id, type: newBlock.type,