/** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /** * @module ai/aicore/pipeline/airesponseapplier */ import { type ObservableMixinConstructor } from "@ckeditor/ckeditor5-utils"; import { type Element, type Document } from "../utils/htmlparser.js"; declare const AIResponseApplierBase: ObservableMixinConstructor; /** * 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 AIResponseApplierBase { 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: ``. 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 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. An element * is considered new when it has no `data-id`, when the LLM marks it by setting `data-id="new-element"`, or * when its `data-id` is not present in the `content` (an id the LLM made up for new 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. * * Note: "existing document" comments and "removed" comments are only meaningful at the top level of the `contentPart`. * If an existing (kept) element contains such comments inside its children, they are ignored (stripped) — the entire * element is replaced with the `contentPart` version as-is. This means that partial replacement inside a kept parent * is not supported: the LLM must provide the full content of any existing element it returns. * * 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: *

Foo

*

Bar

*

Cup

* * // contentPart: *

New content

* * // merge result: *

New content

*

Bar

*

Cup

* ``` * * Example 2: Multiple modified element. * * ```html * // content: *

Foo

*

Bar

*

Cup

* * // contentPart: *

New content

* *

Another change

* * // merge result: *

New content

*

Bar

*

Another change

* ``` * * Example 3: Removed element. * * ```html * // content: *

Foo

*

Bar

*

Cup

* * // contentPart: * * * // merge result: *

Bar

*

Cup

* ``` * * Example 4: New element with reference node before. * * ```html * // content: *

Foo

*

Bar

*

Cup

* * // contentPart: *

Foo

*

New element

* * * // merge result: *

Foo

*

New element

*

Bar

*

Cup

* ``` * * Example 5: New element with reference node after. * * ```html * // content: *

Foo

*

Bar

*

Cup

* * // contentPart: * *

New element

*

Cup

* * // merge result: *

Foo

*

Bar

*

New element

*

Cup

* ``` * * Example 6: New element at the start of the content. * * ```html * // content: *

Foo

*

Bar

*

Cup

* * // contentPart: *

New element

* * * // merge result: *

New element

*

Foo

*

Bar

*

Cup

* ``` * * Example 7: New element at the end of the content. * * ```html * // content: *

Foo

*

Bar

*

Cup

* * // contentPart: * *

New element

* * // merge result: *

Foo

*

Bar

*

Cup

*

New element

* ``` * * Example 8: New element after removed element. * * ```html * // content: *

Foo

*

Bar

*

Cup

* * // contentPart: * *

New element

* * // merge result: *

Foo

*

New element

*

Cup

* ``` * * Example 9: Wrapped element. * * ```html * // content: *

Foo

*

Bar

*

Cup

* * // contentPart: *

Foo

*
*

Bar

*
* * // merge result: *

Foo

*
*

Bar

*
*

Cup

* ``` * * Example 10: Modification of nested elements. * * ```html * // content: * * * * * * * * *
Foo
BarCup
* * // contentPart: * * New content * * * // merge result: * * * * * * * *
Foo
New content
* ``` * * 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: * - `cutAfterUnstable`: if set to `true`, marks unconfirmed elements as unstable (via `data-unstable` attribute) * and cuts the content after the last touched element. Used during streaming. */ merge(contentPart: Document, parsedContent: Document, options?: MergeOptions): MergeResult; } export type MergeResult = { parsedContent: Document; newNodeIds: Array; modifiedNodeIds: Array; removedNodeIds: Array; }; export type MergeOptions = { cutAfterUnstable?: boolean; replaceRemovedWith?: Element; }; export {};