/** * @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/aireviewcore/aireviewcoreediting */ import { InsertOperation, MarkerOperation, ModelDocumentFragment, ModelPosition, ModelRange, type Operation } from "@ckeditor/ckeditor5-engine"; import { type Emitter } from "@ckeditor/ckeditor5-utils"; import { ContextPlugin, Editor, type Context, type PluginDependenciesOf } from "@ckeditor/ckeditor5-core"; import { DocumentChanges } from "../aisdk/documentchanges.js"; import { type AIReviewCheckResultChange } from "./model/aireviewcheckresultchange.js"; import { AIEditing } from "../aicore/aiediting.js"; import { type AIEditorRoot } from "../aicore/model/aieditorroot.js"; import { type AIReviewCheckRun } from "./model/aireviewcheckrun.js"; import { AIRunResult } from "../aicore/model/airunresult.js"; import { AICheckRunSingleRootResult } from "./model/aichecksinglerootresult.js"; export declare class AIReviewCoreEditing extends ContextPlugin { /** * @inheritDoc */ static get requires(): PluginDependenciesOf<[DocumentChanges, AIEditing]>; /** * @inheritDoc */ static get pluginName(): "AIReviewCoreEditing"; /** * @inheritDoc */ static override get isOfficialPlugin(): true; /** * @inheritDoc */ static override get isPremiumPlugin(): true; /** * @inheritDoc */ constructor(context: Context | Editor); /** * Exposes the event emitter to allow listening to specific events. */ get emitter(): Emitter; /** * @inheritDoc */ afterInit(): void; /** * Collects the document data (`content`, per-block `elements`, `roots`, `documentVersion`) sent to the AI * endpoint. When `options.roots` is provided, only the listed roots are included; when omitted, every * non-`$graveyard` root across every editor in the `Context` is included. * * Throws `ai-reviewcoreediting-empty-roots` for an explicitly empty `roots` array, and * `ai-reviewcoreediting-root-not-found` (with `{ channelId, rootName }` on the error data) for any entry * that does not resolve to a live, non-`$graveyard` root. */ getDocumentData(options?: { roots?: Array; }): AIDocumentData; /** * Runs an already-constructed check-run and returns an * {@link module:ai/aicore/model/airunresult~AIRunResult}. On success the result holds one * {@link module:ai/aireviewcore/model/aichecksinglerootresult~AICheckRunSingleRootResult} per root the check-run * covered; on whole-run failures (transport / parse errors or abort) `results` stays empty and the outcome * is described by `status` and `error`. * * @experimental **Experimental:** This is a production-ready API but may change in minor releases * without the standard deprecation policy. Check the changelog for migration guidance. * @param checkRun The check-run to execute. * @param options `signal` for aborting the run. */ runCheck(checkRun: AIReviewCheckRun, options?: { signal?: AbortSignal; }): Promise>; /** * Compares two pieces of content, calculates operations, groups them and returns diffed content * and additional data for each group. * * Each group contains: * - `operations`: list of operations that are part of the group. * - `operationsIsolated`: list of operations that are part of the group but with positions/ranges re-calculated * to allow applying them in isolation (without other operations that would affect their positions). * - `groupOffset`: how many characters were inserted in this group. * - `content`: function that returns content (as HTML string) of the entire content with all changes applied and context marked. * - `context`: function that returns part of the content (as HTML string) consider as group context, with all changes applied * for this specific context. * * Operations processing logic is simplified assuming only `Insert` and `Marker` operations. Other operations, even if present * in the calculated operations, are ignored. * * There are two modes of processing diffed content that affects the output of this method. The modes * are controlled with `asSingleGroup` parameter. * * Default mode (`asSingleGroup` is `false`): * * By default each group of operations is processed separately. This mode is used by "AI Review Mode" feature code. * * The `context` is a range within a block that includes operations from a specific group, starts after the previous * change, and ends before the next change. If there is a single group, it spans over the entire block element contents. * * For example: * Initial content:

Foo bar baz. 123 456 789.

* New content:

Foo bax baz. 123 789.

* * This can be visualized as ('[]' shows removal, '{}' shows insertion): *

Foo[ bar]{ bax} baz. 123[ 456] 789.

* * There are 2 separate operation groups: * - " bar" -> " bax" (marker remove operation + insert operation) * - " 456" removed (marker remove operation) * * The context of each group (marked with '()') is: * Group 1:

(Foo[ bar]{ bax} baz. 123) 456 789.

* Group 2:

Foo bar( baz. 123 456 789.)

* * Single group mode (`asSingleGroup` is `true`): * * A single group mode assumes that entire content block are changed as a single change. This mode * is used by "AI Translate" feature code. * * Here, the contents are diffed the same, but then based on calculated operations, a single group with one * marker remove operation (spanning all removed content) and a single insert operation (spanning all inserted content) * is created. * * For example: * Initial content:

Foo bar baz. 123 456 789.

* New content:

Foo bax baz. 123 789.

* * This can be visualized as ('[]' shows removal, '{}' shows insertion): *

[Foo bar bax baz. 123 456 789.]{Foo bax baz. 123 789.}

* * The context and content is equal so both functions return the entire initial content. */ diffContent(editor: Editor, contentInitial: string, contentNew: string, asSingleGroup?: boolean): Array; markChangePositionInEditorContent(changes: Array, documentData: AIDocumentData): void; clearAllMarkers(): void; /** * Locks all context editors read-only during a review. Programmatic accept/apply still works. Released by * {@link #switchToEdit}. */ switchToReadOnly(): void; /** * Releases the read-only lock added by {@link #switchToReadOnly} from all editors attached to the context. */ switchToEdit(): void; applyChange(changeIds: Array, aiSource?: string | null): void; rejectChange(changeId: string): void; getEditor(): Editor; /** * All editors attached to this context (same order as {@link module:ai/aicore/utils/geteditorsfromcontext~getEditorsFromContext}). */ getEditors(): Array; /** * Returns the editor that hosts markers for the given change, when known. */ getEditorForChange(changeId: string): Editor | undefined; getMarkerElementForChange(changeId: string): { modelRange: ModelRange; getElement: () => HTMLElement | null; } | null; /** * Returns the top-level DOM element that includes the change with the given id. */ getContentElementForChange(changeId: string): HTMLElement | null; /** * A shortcut method to clear active markers and set others as active. It also * checks what was the previous state to avoid unnecessary operations, so it should * be used in scenarios when there is a chance that something will trigger the same markers * activation multiple times (e.g. hovering over complex content with multiple markers * from the same change). */ reactivateMarkers(changeId: string): void; /** * Adds a 'ck-ai-review__change_active' class to all marker elements related to the given change ID, * within a limited range (usually top-level block element) in the editor content. * There can be multiple markers related to a single change (e.g. insertion and removal markers) split * into multiple spans in the view layer. */ setMarkersInElementAsActive(changeId: string): void; /** * Removes 'ck-ai-review__change_active' class from all active marker elements in the editor content. */ setAllMarkersAsInactive(): void; } export type AIDocumentDataElement = { /** * Editor instance this element's model belongs to. */ editor: Editor; /** * Name of the model root this element belongs to (e.g. `main`, `root1` in multi-root editors). */ rootName: string; /** * Path to the element in the model. */ path: Array; /** * Model document version when this element map was captured (per-editor). */ documentVersion: number; getContent: () => string; }; export type AIDocumentData = { /** * Single string passed to review API: roots serialized in model order, concatenated. */ content: string; elements: Map; /** * Every root this document data covers, in the natural document iteration order (editor order × * `editor.model.document.getRoots()` order). When * {@link module:ai/aireviewcore/aireviewcoreediting~AIReviewCoreEditing#getDocumentData} was called * without a `roots` filter, this is every non-`$graveyard` root across every editor in the context. * When called with a filter, this is exactly the roots that appear in it. */ roots: Array; }; export type AIDiffResult = { content: string; operations: Array; }; export type AIReviewCoreMarkerBaseData = { groupId: string; type: AIReviewCoreMarkerType; content?: { asString: string; asFragment: ModelDocumentFragment; }; }; export type AIReviewCoreMarkerInsertData = AIReviewCoreMarkerBaseData & { start: ModelPosition; end: ModelPosition; }; export type AIReviewCoreMarkerData = AIReviewCoreMarkerBaseData & { id: string; editor: Editor; }; export type AIReviewCoreMarkerType = "insert" | "remove-text" | "remove-only" | "remove-context"; export type AIReviewCoreChangeRange = { operations: Array; operationsIsolated: Array; groupOffset: number; }; export type AIReviewCoreChangeData = AIReviewCoreChangeRange & { content: () => string; context: () => string; wrapperTag?: string; }; export type AIReviewCoreChangeMarkerClickedEvent = { name: "changeMarkerClicked"; args: [{ markerId: string; changeId: string; markerElement: () => HTMLElement | null; domEvent: MouseEvent; }]; }; export type AIReviewCoreChangeMarkerRemovedEvent = { name: "changeMarkerRemoved"; args: [{ markerId: string; changeId: string; }]; }; export type AIReviewCoreChangeMarkerRestoredEvent = { name: "changeMarkerRestored"; args: [{ markerId: string; changeId: string; }]; }; export type AIReviewCoreChangeMarkerBlurredEvent = { name: "changeMarkerBlurred"; args: []; }; export type AIReviewCoreChangeMarkerHoveredEvent = { name: "changeMarkerHovered"; args: [{ changeId: string; markerElement: () => HTMLElement | null; domEvent: MouseEvent; }]; };