UNPKG

@ckeditor/ckeditor5-ai

Version:

AI features for CKEditor 5.

292 lines (291 loc) • 10.4 kB
/** * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ import { type Element, type Document } from '../utils/htmlparser.js'; declare const AIResponseApplier_base: { new (): import("ckeditor5/src/utils.js").Observable; prototype: import("ckeditor5/src/utils.js").Observable; }; /** * AIResponseApplier is a part of the AI API response processing pipeline. * * The main purpose of this class is to merge HTML suggestions (modified content chunks) returned * from the AI API into the existing editor content. This is "black box" processing, which means that * the class does not know anything about the editor content structure, it just merges the content * based on provided HTML data (part/suggestion and content) and 'data-id' attributes. */ export declare class AIResponseApplier extends /* #__PURE__ -- @preserve */ AIResponseApplier_base { constructor(generateUid?: () => string); /** * Merges the given content part (AI suggestion) with the provided editor content. * * The main purpose of this function is to merge a piece of AI modified content (`contentPart`) * into the given content (editor content, `content`). * * This is done based on a few rules: * * 1. Any removed element is not present in the `contentPart` itself but is represented by a special * "removed comment" node: `<!-- removed data-id="some_id" -->`. This comment node is located * in the same position (relative position to other present nodes) as the element in the original * content. * 2. Any newly added element is presented in the `contentPart` with a special `data-id="new-element"` * attribute. * 3. Any existing (from the original `content`) and modified element is presented in the `contentPart` * with the same `data-id` value as in the original `content`. Its contents may be different (tag, * attributes, entire subtree may change). Also modified elements don't have to be a top level elements * from the original `content`, which means the `contentPart` may not keep the original structure * or nesting order of the elements. * * Each type of modification is handled differently when merged into the `content`: * * 1. Modified elements (with `data-id` attribute with a value which exists in the `content`) replaces * the existing element with the same `data-id`, without changing its position in the `content`. * 2. Removed elements (marked with a special "removed comment") are removed from the `content` based * on the `data-id` comment value. * 3. New elements (marked with `data-id="new-element"`) are inserted into the `content` based on their * relative position to other nodes in the `contentPart`. The detailed rules for inserting new elements are * described in the next section. * 4. Invalid elements (elements with `data-id` attribute which value is not present in the `content`, * or without `data-id` attribute at all) are ignored and not inserted into the `content`. * * The position on which new elements from the `contentPart` are inserted into the `content` * is determined based on the reference nodes. The reference node is: * * - A node in the `contentPart` which also exists in the `content` with the same `data-id` attribute value. * - A special "removed comment" node which points to the existing element in the `content`. * - A special "existing document" comment node which implicates that there should be any content * before/after the new element position on which it is inserted into the `content`. * * The algorithm operates on the top level nodes of the `contentPart` which means only direct root children * of the `contentPart` are processed. Nested elements are not processed directly, but as a children * of the top level nodes they are included in the merge process. * * The algorithm iterates over the `contentPart` top level nodes and tries to find a position (directly or via reference node) * for any element which should be merged into the `content`. * * Example 1: Modified element. * * ```html * // content: * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * * // contentPart: * <p data-id="1">New content</p> * * // merge result: * <p data-id="1">New content</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * ``` * * Example 2: Multiple modified element. * * ```html * // content: * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * * // contentPart: * <p data-id="1">New content</p> * <!-- existing document !--> * <p data-id="3">Another <strong>change</strong></p> * * // merge result: * <p data-id="1">New content</p> * <p data-id="2">Bar</p> * <p data-id="3">Another <strong>change</strong></p> * ``` * * Example 3: Removed element. * * ```html * // content: * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * * // contentPart: * <!-- removed data-id="1" !--> * * // merge result: * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * ``` * * Example 4: New element with reference node before. * * ```html * // content: * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * * // contentPart: * <p data-id="1">Foo</p> * <p data-id="new-element">New element</p> * <!-- existing document !--> * * // merge result: * <p data-id="1">Foo</p> * <p data-id="new-id">New element</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * ``` * * Example 5: New element with reference node after. * * ```html * // content: * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * * // contentPart: * <!-- existing document !--> * <p data-id="new-element">New element</p> * <p data-id="3">Cup</p> * * // merge result: * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="new-id">New element</p> * <p data-id="3">Cup</p> * ``` * * Example 6: New element at the start of the content. * * ```html * // content: * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * * // contentPart: * <p data-id="new-element">New element</p> * <!-- existing document !--> * * // merge result: * <p data-id="new-id">New element</p> * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * ``` * * Example 7: New element at the end of the content. * * ```html * // content: * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * * // contentPart: * <!-- existing document !--> * <p data-id="new-element">New element</p> * * // merge result: * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * <p data-id="new-id">New element</p> * ``` * * Example 8: New element after removed element. * * ```html * // content: * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * * // contentPart: * <!-- removed data-id="2" !--> * <p data-id="new-element">New element</p> * * // merge result: * <p data-id="1">Foo</p> * <p data-id="new-id">New element</p> * <p data-id="3">Cup</p> * ``` * * Example 9: Wrapped element. * * ```html * // content: * <p data-id="1">Foo</p> * <p data-id="2">Bar</p> * <p data-id="3">Cup</p> * * // contentPart: * <p data-id="1">Foo</p> * <div data-id="new-element"> * <p data-id="2">Bar</p> * </div> * * // merge result: * <p data-id="1">Foo</p> * <div data-id="new-id"> * <p data-id="2">Bar</p> * </div> * <p data-id="3">Cup</p> * ``` * * Example 10: Modification of nested elements. * * ```html * // content: * <table data-id="1"> * <tr data-id="11"> * <td data-id="111">Foo</td> * </tr> * <tr data-id="12"> * <td data-id="121">Bar</td> * <td data-id="122">Cup</td> * </tr> * </table> * * // contentPart: * <tr data-id="12"> * <td data-id="new-element">New content</td> * </tr> * * // merge result: * <table data-id="1"> * <tr data-id="11"> * <td data-id="111">Foo</td> * </tr> * <tr data-id="12"> * <td data-id="new-id">New content</td> * </tr> * </table> * ``` * * Since existing elements are either modifications or only reference nodes in the `contentPart`, there is an additional logic * for checking if such elements are identical to the existing ones in the `content`. This way the algorithm can correctly detect * if an existing element was modified or not. * * Additionally, this method supports the following options: * - `cutAfterLastChange`: if set to `true`, the content will be cut after the last modified element. * - `markUnstableElements`: if set to `true`, the function will mark all elements starting from the last tag element * as unstable by adding a special 'data-unstable' attribute to them. * - `markUnstableElementsDepth`: if set, it will determine how deep the unstable marking should go. */ merge(contentPart: Document, parsedContent: Document, options?: MergeOptions): MergeResult; } export type MergeResult = { parsedContent: Document; newNodeIds: Array<string>; modifiedNodeIds: Array<string>; removedNodeIds: Array<string>; }; export type MergeOptions = { cutAfterLastChange?: boolean; markUnstableElements?: boolean; markUnstableElementsDepth?: number; replaceRemovedWith?: Element; }; export {};