@skaile/forge-editor 5.0.1 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.0.2 - 2026-10-26
4
+
5
+ ### Added
6
+
7
+ - `DocumentChatOptions.review`: the review the document chat proposes to (default: its own). A host that also runs `documentActions` passes the same `AgentOpReview` to both, so the operation IDs come from one counter and `reset()` forgets both sets of proposals.
8
+
9
+ ### Fixed
10
+
11
+ - Operation IDs of the document chat could collide with those of `documentActions` on a shared review: the chat took its starting number when the request went out and advanced the counter only after the stream ended, so an answer arriving after a `doc.propose_ops` call overwrote that call's `op<n>` state and target. The chat now renumbers the answer's operations from the review's counter when the `ops` event arrives (past the IDs of the change set, which keeps the server's), and advances the counter then.
12
+ - `doc.propose_ops` result: `outcomes` is one `{ op, type, target, outcome }` per valid operation (it was a bare list of outcome codes, without the `op<n>` IDs and ordered only by the valid operations, so the model could not tell which of its operations was meant), and `proposed` counts only the operations waiting for the user (`pending`), not every valid one. With nothing pending the `hint` no longer tells the model the user reviews proposals. The input schema is as loose as the server's `edit_document` tool (`type` a string, no `enum`, no count or length limit), so a bad `type`, a 51st operation or a Markdown that is only too long because of an echoed `[id] ` prefix is a problem of that operation from `checkAgentOps` and the other operations of the call still go through.
13
+
3
14
  ## 5.0.1 - 2026-10-26
4
15
 
5
16
  ### Fixed
@@ -19,9 +19,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
19
19
  onSave?: ((text: string) => any) | undefined;
20
20
  onReady?: ((view: EditorView) => any) | undefined;
21
21
  }>, {
22
+ modelValue: string;
22
23
  editable: boolean;
23
24
  wordWrap: boolean;
24
- modelValue: string;
25
25
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
26
26
  declare const _default: typeof __VLS_export;
27
27
  //#endregion
@@ -40,9 +40,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
40
40
  onSave?: ((text: string) => any) | undefined;
41
41
  onReady?: ((target: DocumentSurfaceTarget) => any) | undefined;
42
42
  }>, {
43
+ modelValue: string;
43
44
  editable: boolean;
44
45
  wordWrap: boolean;
45
- modelValue: string;
46
46
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
47
47
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
48
48
  declare const _default: typeof __VLS_export;
@@ -25,8 +25,10 @@ export interface DocumentActionsOptions {
25
25
  * - `doc.propose_ops` checks the operations against the block IDs of that text, the `scope` and the `targets`
26
26
  * (`checkAgentOps`), gives the valid ones `op<n>` IDs from `review.nextOp()` and shows them as review hunks
27
27
  * (`review.propose`). It never throws on a bad operation: the result lists the `problems` (`unknown`, `scope`, `field`,
28
- * `duplicate`, …) and the model sends corrected operations in its next call. No approval dialog: the in-document
29
- * review is the approval.
28
+ * `limit`, `duplicate`, …; `index` is the 1-based position in the input) and the model sends corrected operations in its
29
+ * next call. The result's `outcomes` has one `{ op, type, target, outcome }` per valid operation, in input order (`op` is
30
+ * its `op<n>` ID, `outcome` `pending` while it waits for the user, else why nothing is shown), and `proposed` counts the
31
+ * `pending` ones. No approval dialog: the in-document review is the approval.
30
32
  */
31
33
  export declare function documentActions({ editor, review, scope, targets }: DocumentActionsOptions): ForgeAction[];
32
34
  //#endregion
@@ -1,6 +1,6 @@
1
1
  import { AGENT_OP_TYPES } from "../../core/agent-op-types.js";
2
2
  import { readBlockLines } from "./protocol/block-lines.js";
3
- import { AGENT_LIMITS, checkAgentOps } from "./protocol/agent-ops.js";
3
+ import { agentOpTarget, checkAgentOps } from "./protocol/agent-ops.js";
4
4
  import { documentChatText } from "./document-chat.js";
5
5
  import { z } from "zod";
6
6
  import { defineForgeAction } from "@skaile/forge-app-actions/client-capabilities";
@@ -15,14 +15,18 @@ import { defineForgeAction } from "@skaile/forge-app-actions/client-capabilities
15
15
  */
16
16
  const READ_DESCRIPTION = "Liefert das aktuelle Dokument, eine Zeile pro Block, beginnend mit der Block-ID.";
17
17
  const PROPOSE_DESCRIPTION = "Schlägt Änderungen am Dokument vor. Jede Operation wird der Nutzerin bzw. dem Nutzer im Dokument zur Prüfung angezeigt.";
18
- /** The operation list of `doc.propose_ops`: the shape and limits of the editor's `edit_document` tool. Block rules (IDs, scope, targets) are checked by `checkAgentOps`. */
19
- const proposeInput = z.object({ ops: z.array(z.object({
20
- type: z.enum(AGENT_OP_TYPES).describe("update: Blockinhalt ersetzen; delete: Block löschen; add: Blöcke einfügen; convert: Blocktyp ändern."),
18
+ /**
19
+ * The operation list of `doc.propose_ops`: the shape of the editor's `edit_document` tool, and as loose as the server's
20
+ * (`agentTools` in `server/handlers/ai.ts`): the type, the limits and the block rules (IDs, scope, targets) are not checked
21
+ * here but by `checkAgentOps`, so one bad operation (or a 51st) becomes a problem of its own, not a refusal of the whole call.
22
+ */
23
+ const proposeInput = z.object({ ops: z.array(z.looseObject({
24
+ type: z.string().describe(`${AGENT_OP_TYPES.join(" | ")}. update: Blockinhalt ersetzen; delete: Block löschen; add: Blöcke einfügen; convert: Blocktyp ändern.`),
21
25
  id: z.string().optional().describe("update/delete/convert: ID des Blocks aus dem Dokument."),
22
26
  afterId: z.string().nullable().optional().describe("add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null."),
23
- markdown: z.string().max(AGENT_LIMITS.markdown).optional().describe("update/add: der neue Inhalt als Markdown, ohne Block-ID."),
27
+ markdown: z.string().optional().describe("update/add: der neue Inhalt als Markdown, ohne Block-ID."),
24
28
  to: z.string().optional().describe("convert: der neue Blocktyp, einer der erlaubten Werte (z. B. paragraph, heading2, composite:task).")
25
- })).max(AGENT_LIMITS.ops).describe("Die Operationen in Dokumentreihenfolge.") });
29
+ })).describe("Die Operationen in Dokumentreihenfolge.") });
26
30
  /**
27
31
  * `doc.read` and `doc.propose_ops` as forge actions (capability names `app.doc.read`, `app.doc.propose_ops`; audience
28
32
  * `llm`, so neither shows in a command palette).
@@ -31,8 +35,10 @@ const proposeInput = z.object({ ops: z.array(z.object({
31
35
  * - `doc.propose_ops` checks the operations against the block IDs of that text, the `scope` and the `targets`
32
36
  * (`checkAgentOps`), gives the valid ones `op<n>` IDs from `review.nextOp()` and shows them as review hunks
33
37
  * (`review.propose`). It never throws on a bad operation: the result lists the `problems` (`unknown`, `scope`, `field`,
34
- * `duplicate`, …) and the model sends corrected operations in its next call. No approval dialog: the in-document
35
- * review is the approval.
38
+ * `limit`, `duplicate`, …; `index` is the 1-based position in the input) and the model sends corrected operations in its
39
+ * next call. The result's `outcomes` has one `{ op, type, target, outcome }` per valid operation, in input order (`op` is
40
+ * its `op<n>` ID, `outcome` `pending` while it waits for the user, else why nothing is shown), and `proposed` counts the
41
+ * `pending` ones. No approval dialog: the in-document review is the approval.
36
42
  */
37
43
  function documentActions({ editor, review, scope = () => null, targets = [] }) {
38
44
  const current = () => {
@@ -68,11 +74,17 @@ function documentActions({ editor, review, scope = () => null, targets = [] }) {
68
74
  }));
69
75
  review.advanceOp(ops.length);
70
76
  const outcomes = review.propose(instance, ops, editScope);
77
+ const proposed = outcomes.filter((outcome) => outcome === "pending").length;
71
78
  return {
72
- proposed: outcomes.length,
73
- outcomes,
79
+ proposed,
80
+ outcomes: ops.map((op, index) => ({
81
+ op: op.op,
82
+ type: op.type,
83
+ target: agentOpTarget(op),
84
+ outcome: outcomes[index]
85
+ })),
74
86
  problems: checked.problems,
75
- hint: "Die Nutzerin bzw. der Nutzer prüft die Vorschläge im Dokument."
87
+ hint: proposed ? "Die Nutzerin bzw. der Nutzer prüft die Vorschläge im Dokument." : "Es wird nichts zur Prüfung angezeigt; prüfe die Ergebnisse und Probleme."
76
88
  };
77
89
  }
78
90
  })];
@@ -1 +1 @@
1
- {"version":3,"file":"actions.js","names":[],"sources":["../../../src/features/ai/actions.ts"],"sourcesContent":["// i18n-ignore-file: model-facing tool descriptions and results (German; the model's language is not the editor's)\n/**\n * The document chat as two forge actions, for a host whose chat lives elsewhere (a Nuxt app with its own AI SDK route\n * and `@skaile/forge-app-actions/chat`): the model reads the document as block lines and proposes edit operations,\n * which the user reviews in the document. The server-side `edit_document` loop of `server/handlers/ai.ts` does the same\n * with a server-run tool; here the tool runs in the browser, next to the editor.\n *\n * Needs the optional peer `@skaile/forge-app-actions` and the `AiReview` extension in the editor.\n */\nimport { defineForgeAction, type ForgeAction } from '@skaile/forge-app-actions/client-capabilities'\nimport type { Editor } from '@tiptap/core'\nimport { z } from 'zod'\nimport type { AgentOpReview } from './agent-op-review'\nimport type { AgentScope } from './agent-ops'\nimport { documentChatText } from './document-chat'\nimport { AGENT_LIMITS, AGENT_OP_TYPES, checkAgentOps, type AgentOp, readBlockLines } from './protocol'\n\nexport interface DocumentActionsOptions {\n /** The editor the actions work on, read on every call; `undefined` (not mounted yet) makes the actions fail with `no editor`. */\n editor: () => Editor | undefined\n /**\n * The review that shows the proposals (`createAgentOpReview`); its decisions stay with the host. Operation IDs\n * (`op1`, `op2`, …) come from its counter (`nextOp`/`advanceOp`), so the numbering continues across turns and across\n * every other source that proposes to the same review (the editor's own document chat).\n */\n review: AgentOpReview\n /** The edit scope of the next proposal: the selected blocks (`selectionScope(editor.state)`); `null`: the whole document. Default: the whole document. */\n scope?: () => AgentScope | null\n /** Block types `convert` may turn a block into; a `convert` to any other type is a `target` problem. Default: none. */\n targets?: readonly string[]\n}\n\nconst READ_DESCRIPTION = 'Liefert das aktuelle Dokument, eine Zeile pro Block, beginnend mit der Block-ID.'\n\nconst PROPOSE_DESCRIPTION = 'Schlägt Änderungen am Dokument vor. Jede Operation wird der Nutzerin bzw. dem Nutzer im Dokument zur Prüfung angezeigt.'\n\n/** The operation list of `doc.propose_ops`: the shape and limits of the editor's `edit_document` tool. Block rules (IDs, scope, targets) are checked by `checkAgentOps`. */\nconst proposeInput = z.object({\n ops: z.array(z.object({\n type: z.enum(AGENT_OP_TYPES).describe('update: Blockinhalt ersetzen; delete: Block löschen; add: Blöcke einfügen; convert: Blocktyp ändern.'),\n id: z.string().optional().describe('update/delete/convert: ID des Blocks aus dem Dokument.'),\n afterId: z.string().nullable().optional().describe('add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null.'),\n markdown: z.string().max(AGENT_LIMITS.markdown).optional().describe('update/add: der neue Inhalt als Markdown, ohne Block-ID.'),\n to: z.string().optional().describe('convert: der neue Blocktyp, einer der erlaubten Werte (z. B. paragraph, heading2, composite:task).'),\n })).max(AGENT_LIMITS.ops).describe('Die Operationen in Dokumentreihenfolge.'),\n})\n\n/**\n * `doc.read` and `doc.propose_ops` as forge actions (capability names `app.doc.read`, `app.doc.propose_ops`; audience\n * `llm`, so neither shows in a command palette).\n *\n * - `doc.read` returns `documentChatText(editor)`: one line per block, `[<block id>] <Markdown>`.\n * - `doc.propose_ops` checks the operations against the block IDs of that text, the `scope` and the `targets`\n * (`checkAgentOps`), gives the valid ones `op<n>` IDs from `review.nextOp()` and shows them as review hunks\n * (`review.propose`). It never throws on a bad operation: the result lists the `problems` (`unknown`, `scope`, `field`,\n * `duplicate`, …) and the model sends corrected operations in its next call. No approval dialog: the in-document\n * review is the approval.\n */\nexport function documentActions({ editor, review, scope = () => null, targets = [] }: DocumentActionsOptions): ForgeAction[] {\n const current = (): Editor => {\n const instance = editor()\n if (!instance || instance.isDestroyed) throw new Error('no editor')\n return instance\n }\n\n const read = defineForgeAction({\n id: 'doc.read',\n label: 'Dokument lesen',\n description: READ_DESCRIPTION,\n audience: ['llm'],\n run: () => documentChatText(current()),\n })\n\n const proposeOps = defineForgeAction({\n id: 'doc.propose_ops',\n label: 'Änderungen am Dokument vorschlagen',\n description: PROPOSE_DESCRIPTION,\n input: proposeInput,\n audience: ['llm'],\n requiresApproval: false,\n run: (input) => {\n const instance = current()\n const editScope = scope()\n const checked = checkAgentOps(input.ops, {\n lines: readBlockLines(documentChatText(instance)),\n allowed: editScope ? new Set(editScope.ids) : null,\n targets: new Set(targets),\n })\n const first = review.nextOp()\n const ops: AgentOp[] = checked.valid.map((op, index) => ({ ...op, op: `op${first + index}` }))\n review.advanceOp(ops.length)\n const outcomes = review.propose(instance, ops, editScope)\n return {\n proposed: outcomes.length,\n outcomes,\n problems: checked.problems,\n hint: 'Die Nutzerin bzw. der Nutzer prüft die Vorschläge im Dokument.',\n }\n },\n })\n\n return [read, proposeOps]\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgCA,MAAM,mBAAmB;AAEzB,MAAM,sBAAsB;;AAG5B,MAAM,eAAe,EAAE,OAAO,EAC5B,KAAK,EAAE,MAAM,EAAE,OAAO;CACpB,MAAM,EAAE,KAAK,cAAc,CAAC,CAAC,SAAS,sGAAsG;CAC5I,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,wDAAwD;CAC3F,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,kGAAkG;CACrJ,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,aAAa,QAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,0DAA0D;CAC9H,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,oGAAoG;AACzI,CAAC,CAAC,CAAC,CAAC,IAAI,aAAa,GAAG,CAAC,CAAC,SAAS,yCAAyC,EAC9E,CAAC;;;;;;;;;;;;AAaD,SAAgB,gBAAgB,EAAE,QAAQ,QAAQ,cAAc,MAAM,UAAU,CAAC,KAA4C;CAC3H,MAAM,gBAAwB;EAC5B,MAAM,WAAW,OAAO;EACxB,IAAI,CAAC,YAAY,SAAS,aAAa,MAAM,IAAI,MAAM,WAAW;EAClE,OAAO;CACT;CAsCA,OAAO,CApCM,kBAAkB;EAC7B,IAAI;EACJ,OAAO;EACP,aAAa;EACb,UAAU,CAAC,KAAK;EAChB,WAAW,iBAAiB,QAAQ,CAAC;CACvC,CA8BW,GA5BQ,kBAAkB;EACnC,IAAI;EACJ,OAAO;EACP,aAAa;EACb,OAAO;EACP,UAAU,CAAC,KAAK;EAChB,kBAAkB;EAClB,MAAM,UAAU;GACd,MAAM,WAAW,QAAQ;GACzB,MAAM,YAAY,MAAM;GACxB,MAAM,UAAU,cAAc,MAAM,KAAK;IACvC,OAAO,eAAe,iBAAiB,QAAQ,CAAC;IAChD,SAAS,YAAY,IAAI,IAAI,UAAU,GAAG,IAAI;IAC9C,SAAS,IAAI,IAAI,OAAO;GAC1B,CAAC;GACD,MAAM,QAAQ,OAAO,OAAO;GAC5B,MAAM,MAAiB,QAAQ,MAAM,KAAK,IAAI,WAAW;IAAE,GAAG;IAAI,IAAI,KAAK,QAAQ;GAAQ,EAAE;GAC7F,OAAO,UAAU,IAAI,MAAM;GAC3B,MAAM,WAAW,OAAO,QAAQ,UAAU,KAAK,SAAS;GACxD,OAAO;IACL,UAAU,SAAS;IACnB;IACA,UAAU,QAAQ;IAClB,MAAM;GACR;EACF;CACF,CAEuB,CAAC;AAC1B"}
1
+ {"version":3,"file":"actions.js","names":[],"sources":["../../../src/features/ai/actions.ts"],"sourcesContent":["// i18n-ignore-file: model-facing tool descriptions and results (German; the model's language is not the editor's)\n/**\n * The document chat as two forge actions, for a host whose chat lives elsewhere (a Nuxt app with its own AI SDK route\n * and `@skaile/forge-app-actions/chat`): the model reads the document as block lines and proposes edit operations,\n * which the user reviews in the document. The server-side `edit_document` loop of `server/handlers/ai.ts` does the same\n * with a server-run tool; here the tool runs in the browser, next to the editor.\n *\n * Needs the optional peer `@skaile/forge-app-actions` and the `AiReview` extension in the editor.\n */\nimport { defineForgeAction, type ForgeAction } from '@skaile/forge-app-actions/client-capabilities'\nimport type { Editor } from '@tiptap/core'\nimport { z } from 'zod'\nimport type { AgentOpReview } from './agent-op-review'\nimport type { AgentScope } from './agent-ops'\nimport { documentChatText } from './document-chat'\nimport { AGENT_OP_TYPES, agentOpTarget, checkAgentOps, type AgentOp, readBlockLines } from './protocol'\n\nexport interface DocumentActionsOptions {\n /** The editor the actions work on, read on every call; `undefined` (not mounted yet) makes the actions fail with `no editor`. */\n editor: () => Editor | undefined\n /**\n * The review that shows the proposals (`createAgentOpReview`); its decisions stay with the host. Operation IDs\n * (`op1`, `op2`, …) come from its counter (`nextOp`/`advanceOp`), so the numbering continues across turns and across\n * every other source that proposes to the same review (the editor's own document chat).\n */\n review: AgentOpReview\n /** The edit scope of the next proposal: the selected blocks (`selectionScope(editor.state)`); `null`: the whole document. Default: the whole document. */\n scope?: () => AgentScope | null\n /** Block types `convert` may turn a block into; a `convert` to any other type is a `target` problem. Default: none. */\n targets?: readonly string[]\n}\n\nconst READ_DESCRIPTION = 'Liefert das aktuelle Dokument, eine Zeile pro Block, beginnend mit der Block-ID.'\n\nconst PROPOSE_DESCRIPTION = 'Schlägt Änderungen am Dokument vor. Jede Operation wird der Nutzerin bzw. dem Nutzer im Dokument zur Prüfung angezeigt.'\n\n/**\n * The operation list of `doc.propose_ops`: the shape of the editor's `edit_document` tool, and as loose as the server's\n * (`agentTools` in `server/handlers/ai.ts`): the type, the limits and the block rules (IDs, scope, targets) are not checked\n * here but by `checkAgentOps`, so one bad operation (or a 51st) becomes a problem of its own, not a refusal of the whole call.\n */\nconst proposeInput = z.object({\n ops: z.array(z.looseObject({\n type: z.string().describe(`${AGENT_OP_TYPES.join(' | ')}. update: Blockinhalt ersetzen; delete: Block löschen; add: Blöcke einfügen; convert: Blocktyp ändern.`),\n id: z.string().optional().describe('update/delete/convert: ID des Blocks aus dem Dokument.'),\n afterId: z.string().nullable().optional().describe('add (Pflicht): ID des Blocks, nach dem eingefügt wird; für den Dokumentanfang ausdrücklich null.'),\n markdown: z.string().optional().describe('update/add: der neue Inhalt als Markdown, ohne Block-ID.'),\n to: z.string().optional().describe('convert: der neue Blocktyp, einer der erlaubten Werte (z. B. paragraph, heading2, composite:task).'),\n })).describe('Die Operationen in Dokumentreihenfolge.'),\n})\n\n/**\n * `doc.read` and `doc.propose_ops` as forge actions (capability names `app.doc.read`, `app.doc.propose_ops`; audience\n * `llm`, so neither shows in a command palette).\n *\n * - `doc.read` returns `documentChatText(editor)`: one line per block, `[<block id>] <Markdown>`.\n * - `doc.propose_ops` checks the operations against the block IDs of that text, the `scope` and the `targets`\n * (`checkAgentOps`), gives the valid ones `op<n>` IDs from `review.nextOp()` and shows them as review hunks\n * (`review.propose`). It never throws on a bad operation: the result lists the `problems` (`unknown`, `scope`, `field`,\n * `limit`, `duplicate`, …; `index` is the 1-based position in the input) and the model sends corrected operations in its\n * next call. The result's `outcomes` has one `{ op, type, target, outcome }` per valid operation, in input order (`op` is\n * its `op<n>` ID, `outcome` `pending` while it waits for the user, else why nothing is shown), and `proposed` counts the\n * `pending` ones. No approval dialog: the in-document review is the approval.\n */\nexport function documentActions({ editor, review, scope = () => null, targets = [] }: DocumentActionsOptions): ForgeAction[] {\n const current = (): Editor => {\n const instance = editor()\n if (!instance || instance.isDestroyed) throw new Error('no editor')\n return instance\n }\n\n const read = defineForgeAction({\n id: 'doc.read',\n label: 'Dokument lesen',\n description: READ_DESCRIPTION,\n audience: ['llm'],\n run: () => documentChatText(current()),\n })\n\n const proposeOps = defineForgeAction({\n id: 'doc.propose_ops',\n label: 'Änderungen am Dokument vorschlagen',\n description: PROPOSE_DESCRIPTION,\n input: proposeInput,\n audience: ['llm'],\n requiresApproval: false,\n run: (input) => {\n const instance = current()\n const editScope = scope()\n const checked = checkAgentOps(input.ops, {\n lines: readBlockLines(documentChatText(instance)),\n allowed: editScope ? new Set(editScope.ids) : null,\n targets: new Set(targets),\n })\n const first = review.nextOp()\n const ops: AgentOp[] = checked.valid.map((op, index) => ({ ...op, op: `op${first + index}` }))\n review.advanceOp(ops.length)\n const outcomes = review.propose(instance, ops, editScope)\n const proposed = outcomes.filter(outcome => outcome === 'pending').length\n return {\n proposed,\n outcomes: ops.map((op, index) => ({ op: op.op, type: op.type, target: agentOpTarget(op), outcome: outcomes[index] })),\n problems: checked.problems,\n hint: proposed\n ? 'Die Nutzerin bzw. der Nutzer prüft die Vorschläge im Dokument.'\n : 'Es wird nichts zur Prüfung angezeigt; prüfe die Ergebnisse und Probleme.',\n }\n },\n })\n\n return [read, proposeOps]\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgCA,MAAM,mBAAmB;AAEzB,MAAM,sBAAsB;;;;;;AAO5B,MAAM,eAAe,EAAE,OAAO,EAC5B,KAAK,EAAE,MAAM,EAAE,YAAY;CACzB,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS,GAAG,eAAe,KAAK,KAAK,EAAE,uGAAuG;CAC/J,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,wDAAwD;CAC3F,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,kGAAkG;CACrJ,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,0DAA0D;CACnG,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,oGAAoG;AACzI,CAAC,CAAC,CAAC,CAAC,SAAS,yCAAyC,EACxD,CAAC;;;;;;;;;;;;;;AAeD,SAAgB,gBAAgB,EAAE,QAAQ,QAAQ,cAAc,MAAM,UAAU,CAAC,KAA4C;CAC3H,MAAM,gBAAwB;EAC5B,MAAM,WAAW,OAAO;EACxB,IAAI,CAAC,YAAY,SAAS,aAAa,MAAM,IAAI,MAAM,WAAW;EAClE,OAAO;CACT;CAyCA,OAAO,CAvCM,kBAAkB;EAC7B,IAAI;EACJ,OAAO;EACP,aAAa;EACb,UAAU,CAAC,KAAK;EAChB,WAAW,iBAAiB,QAAQ,CAAC;CACvC,CAiCW,GA/BQ,kBAAkB;EACnC,IAAI;EACJ,OAAO;EACP,aAAa;EACb,OAAO;EACP,UAAU,CAAC,KAAK;EAChB,kBAAkB;EAClB,MAAM,UAAU;GACd,MAAM,WAAW,QAAQ;GACzB,MAAM,YAAY,MAAM;GACxB,MAAM,UAAU,cAAc,MAAM,KAAK;IACvC,OAAO,eAAe,iBAAiB,QAAQ,CAAC;IAChD,SAAS,YAAY,IAAI,IAAI,UAAU,GAAG,IAAI;IAC9C,SAAS,IAAI,IAAI,OAAO;GAC1B,CAAC;GACD,MAAM,QAAQ,OAAO,OAAO;GAC5B,MAAM,MAAiB,QAAQ,MAAM,KAAK,IAAI,WAAW;IAAE,GAAG;IAAI,IAAI,KAAK,QAAQ;GAAQ,EAAE;GAC7F,OAAO,UAAU,IAAI,MAAM;GAC3B,MAAM,WAAW,OAAO,QAAQ,UAAU,KAAK,SAAS;GACxD,MAAM,WAAW,SAAS,QAAO,YAAW,YAAY,SAAS,CAAC,CAAC;GACnE,OAAO;IACL;IACA,UAAU,IAAI,KAAK,IAAI,WAAW;KAAE,IAAI,GAAG;KAAI,MAAM,GAAG;KAAM,QAAQ,cAAc,EAAE;KAAG,SAAS,SAAS;IAAO,EAAE;IACpH,UAAU,QAAQ;IAClB,MAAM,WACF,mEACA;GACN;EACF;CACF,CAEuB,CAAC;AAC1B"}
@@ -12,7 +12,7 @@ import { DocumentChatMessage } from "./protocol/chat.js";
12
12
  import { AiTask } from "./protocol/tasks.js";
13
13
  import "./protocol/index.js";
14
14
  import { AgentScope } from "./agent-ops.js";
15
- import { AgentOpReviewState } from "./agent-op-review.js";
15
+ import { AgentOpReview, AgentOpReviewState } from "./agent-op-review.js";
16
16
  import { AiTarget } from "./ai-apply.js";
17
17
  import { Editor } from "@tiptap/core";
18
18
  //#region src/features/ai/document-chat-model.d.ts
@@ -66,6 +66,13 @@ export interface DocumentChatOptions {
66
66
  * Documents are still stored by the server, but nobody here shows or reports them.
67
67
  */
68
68
  changeSets?: ChangeSetFeed;
69
+ /**
70
+ * The review that shows the proposed operations; default: the chat's own. Pass the one `documentActions` proposes to when a
71
+ * host chat and this one work on the same document: operation IDs (`op<n>`) then come from one counter, an answer's
72
+ * operations are renumbered when they arrive (another proposer may have taken the IDs the request was sent with), and
73
+ * `reset()` forgets the operations of both.
74
+ */
75
+ review?: AgentOpReview;
69
76
  }
70
77
  export interface DocumentChat {
71
78
  state: ReadonlyStore<DocumentChatState>;
@@ -94,6 +101,6 @@ export interface DocumentChat {
94
101
  * message, together with the document as it is at that moment and the edit scope. Proposed edit operations open an
95
102
  * in-document review; their decisions go to the model with the next message.
96
103
  */
97
- export declare function createDocumentChat({ ai, modelRef, notify, t, changeSets }: DocumentChatOptions): DocumentChat;
104
+ export declare function createDocumentChat({ ai, modelRef, notify, t, changeSets, review }: DocumentChatOptions): DocumentChat;
98
105
  //#endregion
99
106
  //# sourceMappingURL=document-chat-model.d.ts.map
@@ -22,12 +22,11 @@ const EMPTY = {
22
22
  * message, together with the document as it is at that moment and the edit scope. Proposed edit operations open an
23
23
  * in-document review; their decisions go to the model with the next message.
24
24
  */
25
- function createDocumentChat({ ai, modelRef, notify, t, changeSets }) {
25
+ function createDocumentChat({ ai, modelRef, notify, t, changeSets, review = createAgentOpReview({
26
+ notify: silentNotifier,
27
+ t
28
+ }) }) {
26
29
  const store = createStore(EMPTY);
27
- const review = createAgentOpReview({
28
- notify: silentNotifier,
29
- t
30
- });
31
30
  let controller = null;
32
31
  const patch = (changes) => store.set({
33
32
  ...store.get(),
@@ -104,12 +103,29 @@ function createDocumentChat({ ai, modelRef, notify, t, changeSets }) {
104
103
  });
105
104
  const received = {};
106
105
  const openDocumentId = documentIdOf(editor);
106
+ const sentNextOp = review.nextOp();
107
+ const numbered = (proposal, changeSetOps) => {
108
+ const first = review.nextOp();
109
+ if (first === sentNextOp) {
110
+ review.advanceOp(proposal.ops.length + changeSetOps);
111
+ return proposal;
112
+ }
113
+ const start = Math.max(first, sentNextOp + proposal.ops.length + changeSetOps);
114
+ review.advanceOp(start + proposal.ops.length - first);
115
+ return {
116
+ ...proposal,
117
+ ops: proposal.ops.map((op, index) => ({
118
+ ...op,
119
+ op: `op${start + index}`
120
+ }))
121
+ };
122
+ };
107
123
  const body = {
108
124
  messages: sent,
109
125
  document: documentChatText(editor),
110
126
  ...openDocumentId && { documentId: openDocumentId },
111
127
  scope: scope?.ids ?? null,
112
- nextOp: review.nextOp(),
128
+ nextOp: sentNextOp,
113
129
  model: modelRef("generate"),
114
130
  blockTypes,
115
131
  blocks,
@@ -127,7 +143,7 @@ function createDocumentChat({ ai, modelRef, notify, t, changeSets }) {
127
143
  },
128
144
  onEvent: (event, data) => {
129
145
  if (controller !== own) return;
130
- if (event === "ops") received.proposal = data;
146
+ if (event === "ops") received.proposal = numbered(data, received.changeSet?.hunks.length ?? 0);
131
147
  else if (event === "change-set") received.changeSet = data;
132
148
  else if (event === "tool") answer = replaceTurn(answer, {
133
149
  ...answer,
@@ -153,7 +169,6 @@ function createDocumentChat({ ai, modelRef, notify, t, changeSets }) {
153
169
  problems: dropped,
154
170
  ...changeSetOps.length && { changeSetOps }
155
171
  });
156
- review.advanceOp(proposed.length + changeSetOps.length);
157
172
  if (proposed.length || dropped.length) {
158
173
  if (!editor.isDestroyed) review.propose(editor, proposed, scope);
159
174
  }
@@ -1 +1 @@
1
- {"version":3,"file":"document-chat-model.js","names":[],"sources":["../../../src/features/ai/document-chat-model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { EditorBackend } from '../../core/backend'\nimport { documentIdOf } from '../../core/block-transfer'\nimport type { ChangeSet, ChangeSetFeed } from '../../core/change-set'\nimport { aiContextOf, CORE_TARGET_IDS, convertTargetId, conversionsOf, parseConversionTarget, TYPED_BLOCK } from '../../core'\nimport { type Notifier, silentNotifier } from '../../core/notify'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport type { Translate } from '../../i18n'\nimport type { ModelRole } from '../../settings'\nimport { type AgentOpReviewState, createAgentOpReview, NO_AGENT_OPS } from './agent-op-review'\nimport type { AgentScope } from './agent-ops'\nimport type { AiTarget } from './ai-apply'\nimport { postAiSse } from './ai-sse'\nimport { documentChatText } from './document-chat'\nimport { type AgentOp, type AgentOpProblem, CHAT_LIMITS, type AiTask, type ChatToolCall, type DocumentChatMessage, type ModelRef } from './protocol'\nimport { notifyAiRateLimited } from './rate-limit'\n\n/**\n * A block generation run from the AI sidebar's quick actions, shown as an assistant turn. Client only: it never goes\n * to the `chat` stream. `markdown` is the streamed result without the task heading (shown with `withTaskHeading`, applied\n * as `taskResultMarkdown`), `target` the apply target captured when it started; only a result that streamed to the end\n * (`done`) can be applied.\n */\nexport interface DocumentChatGeneration {\n task: AiTask\n /** The instruction of a task with `needsPrompt` (empty otherwise). */\n prompt: string\n markdown: string\n target: AiTarget\n done: boolean\n}\n\n/**\n * A chat turn as shown: an assistant turn also keeps the problems of the operations the server dropped, or is a block\n * generation of the sidebar (`generation`).\n */\nexport type DocumentChatTurn =\n | Extract<DocumentChatMessage, { role: 'user' }>\n | (Extract<DocumentChatMessage, { role: 'assistant' }> & {\n problems?: AgentOpProblem[]\n /** The operations aimed at other Documents: they are in the change set panel, not in `ops` (the in-document review), and replay to the model with `ops`. */\n changeSetOps?: AgentOp[]\n generation?: DocumentChatGeneration\n })\n\n/** Snapshot of a conversation; every change replaces `messages`/`ops` (never mutated in place). */\nexport interface DocumentChatState {\n messages: readonly DocumentChatTurn[]\n /** Operation states by operation id. */\n ops: AgentOpReviewState['ops']\n streaming: boolean\n error: string | null\n}\n\nexport interface DocumentChatOptions {\n /** `EditorContext.backend.ai`; without it every question ends in the error state (501), like a server without AI. */\n ai: EditorBackend['ai']\n modelRef: (slot: ModelRole) => ModelRef\n /** Receives the 429 notice. */\n notify: Notifier\n /** The app's translator (`EditorContext.t`): error messages, the 429 notice and the snippets of proposed operations. */\n t: Translate\n /**\n * The change set panel's model (`createChangeSetModel`): receives the change set an answer proposed for other Documents, and\n * hands back the user's decisions on its hunks for the next message. Without it the model's operations aimed at other\n * Documents are still stored by the server, but nobody here shows or reports them.\n */\n changeSets?: ChangeSetFeed\n}\n\nexport interface DocumentChat {\n state: ReadonlyStore<DocumentChatState>\n /**\n * Sends `message` about the editor's current document; `scope` limits proposed edits to the selected blocks (null:\n * whole document). Returns false when it was not answered (error, rate limit, cancelled before any answer): it is\n * then removed again so the caller can restore the input.\n */\n ask: (message: string, editor: Editor, scope: AgentScope | null) => Promise<boolean>\n abort: () => void\n reset: () => void\n /** Accepts or rejects the open hunks of operation `id`. */\n decide: (editor: Editor, id: string, accept: boolean) => void\n /** Scrolls to operation `op`: its first open hunk (selected for accept/reject), else its target block. */\n reveal: (editor: Editor, op: AgentOp) => boolean\n /** Appends the assistant turn of a block generation. `generation` keeps its identity in `state`. */\n addGeneration: (generation: DocumentChatGeneration) => void\n /** Updates `generation` in place (same object) and publishes a new `messages` array. */\n updateGeneration: (generation: DocumentChatGeneration, patch: Partial<Pick<DocumentChatGeneration, 'markdown' | 'done'>>) => void\n /** Removes the turn of `generation`. */\n removeGeneration: (generation: DocumentChatGeneration) => void\n clearError: () => void\n}\n\nconst EMPTY: DocumentChatState = { messages: [], ops: NO_AGENT_OPS, streaming: false, error: null }\n\n/**\n * Document chat state: the conversation lives only here (client side) and goes to the backend's `chat` stream in full with every\n * message, together with the document as it is at that moment and the edit scope. Proposed edit operations open an\n * in-document review; their decisions go to the model with the next message.\n */\nexport function createDocumentChat({ ai, modelRef, notify, t, changeSets }: DocumentChatOptions): DocumentChat {\n const store = createStore<DocumentChatState>(EMPTY)\n const review = createAgentOpReview({ notify: silentNotifier, t })\n let controller: AbortController | null = null\n\n const patch = (changes: Partial<DocumentChatState>) => store.set({ ...store.get(), ...changes })\n\n function abort() {\n controller?.abort()\n }\n\n function reset() {\n abort()\n controller = null\n store.set(EMPTY)\n review.reset()\n changeSets?.reset()\n }\n\n /** Replaces the turn `turn` (by identity) with `next`; returns `next`. */\n function replaceTurn<T extends DocumentChatTurn>(turn: DocumentChatTurn, next: T): T {\n const { messages } = store.get()\n const index = messages.indexOf(turn)\n if (index !== -1) patch({ messages: messages.map((m, i) => (i === index ? next : m)) })\n return next\n }\n\n // The proposed operations live in the review (the chat shows their outcomes inline, so it needs no extra notices); its state is the chat's `ops`.\n review.state.subscribe(({ ops }) => {\n if (ops !== store.get().ops) patch({ ops })\n })\n\n async function ask(message: string, editor: Editor, scope: AgentScope | null): Promise<boolean> {\n if (store.get().streaming || !message.trim()) return false\n if (scope && !scope.ids.length) {\n patch({ error: t('ai.chat.emptyScope') })\n return false\n }\n // The block model of the system prompt: the editor's `aiContext` Block types and host blocks, and what `convert` may turn blocks into.\n // Read before any state changes: a merge error in the contributions throws here and must not leave the chat streaming.\n const { blockTypes, blocks, structure } = aiContextOf(editor)\n const providers = conversionsOf(editor)\n const targets = [\n ...CORE_TARGET_IDS,\n ...blockTypes.map(blockType => convertTargetId({ type: TYPED_BLOCK, blockType: blockType.id })),\n ...blocks.filter(block => parseConversionTarget(providers, block.id) !== null).map(block => block.id),\n ]\n const own = new AbortController()\n controller = own\n const feedback = [...review.unreported(), ...(changeSets?.unreported() ?? [])]\n const userTurn: DocumentChatTurn = { role: 'user', content: message.trim(), ...(feedback.length ? { feedback } : {}) }\n let answer: Extract<DocumentChatTurn, { role: 'assistant' }> = { role: 'assistant', content: '' }\n // The newest chat turns (block generations are the sidebar's own); the request must start with a user message.\n const sent: DocumentChatMessage[] = [...store.get().messages, userTurn].filter(turn => turn.role === 'user' || !turn.generation).slice(-CHAT_LIMITS.messages).map(turn => turn.role === 'user'\n ? { role: 'user', content: turn.content.slice(0, CHAT_LIMITS.message), ...(turn.feedback && { feedback: turn.feedback }) }\n : { role: 'assistant', content: turn.content.slice(0, CHAT_LIMITS.message), ...(turn.ops?.length || turn.changeSetOps?.length ? { ops: [...(turn.ops ?? []), ...(turn.changeSetOps ?? [])] } : {}), ...(turn.dropped ? { dropped: turn.dropped } : {}), ...(turn.tools?.length ? { tools: turn.tools.slice(-CHAT_LIMITS.tools) } : {}) })\n if (sent[0]?.role === 'assistant') sent.shift()\n patch({ error: null, streaming: true, messages: [...store.get().messages, userTurn, answer] })\n const received: { proposal?: { ops: AgentOp[], dropped: AgentOpProblem[] }, changeSet?: ChangeSet } = {}\n const openDocumentId = documentIdOf(editor)\n const body = { messages: sent, document: documentChatText(editor), ...(openDocumentId && { documentId: openDocumentId }), scope: scope?.ids ?? null, nextOp: review.nextOp(), model: modelRef('generate'), blockTypes, blocks, targets, structure }\n const outcome = await postAiSse(ai, 'chat', body, {\n signal: own.signal,\n t,\n onDelta: (text) => {\n if (controller === own) answer = replaceTurn(answer, { ...answer, content: answer.content + text })\n },\n onEvent: (event, data) => {\n if (controller !== own) return\n if (event === 'ops') received.proposal = data as typeof received.proposal\n // Operations aimed at other Documents: stored by the server as a change set.\n else if (event === 'change-set') received.changeSet = data as ChangeSet\n // A read-only tool call the server ran: kept on the turn, so the next message replays it to the model.\n else if (event === 'tool') answer = replaceTurn(answer, { ...answer, tools: [...(answer.tools ?? []), data as ChatToolCall] })\n },\n })\n if (controller !== own) return false // superseded by reset()\n controller = null\n patch({ streaming: false })\n const { ops: proposed = [], dropped = [] } = outcome.status === 'done' ? received.proposal ?? {} : {}\n if (outcome.status === 'done' || (outcome.status === 'aborted' && answer.content)) {\n const reported = feedback.filter(({ outcome: sentOutcome }) => sentOutcome !== 'pending').map(({ op }) => op)\n review.markReported(reported)\n changeSets?.markReported(reported)\n const changeSetOps = received.changeSet?.hunks.map(({ op }) => op) ?? []\n if (received.changeSet) changeSets?.receive(received.changeSet)\n if (proposed.length || dropped.length || changeSetOps.length) {\n answer = replaceTurn(answer, { ...answer, ops: proposed, dropped: dropped.length, problems: dropped, ...(changeSetOps.length && { changeSetOps }) })\n // One numbering for both kinds of operations, so decisions on either are told apart by `op<n>`.\n review.advanceOp(proposed.length + changeSetOps.length)\n if (proposed.length || dropped.length) {\n if (!editor.isDestroyed) review.propose(editor, proposed, scope)\n }\n }\n return true\n }\n // Drop the unanswered turn: a later message should not carry a dangling one in its history.\n patch({ messages: store.get().messages.slice(0, -2) })\n if (outcome.status === 'rate-limited') notifyAiRateLimited(notify, t)\n else if (outcome.status === 'error') patch({ error: outcome.message })\n return false\n }\n\n const decide = review.decide\n const reveal = review.reveal\n\n function addGeneration(generation: DocumentChatGeneration) {\n patch({ error: null, messages: [...store.get().messages, { role: 'assistant', content: '', generation }] })\n }\n\n function updateGeneration(generation: DocumentChatGeneration, changes: Partial<Pick<DocumentChatGeneration, 'markdown' | 'done'>>) {\n Object.assign(generation, changes)\n const { messages } = store.get()\n patch({ messages: messages.map(turn => turn.role === 'assistant' && turn.generation === generation ? { ...turn } : turn) })\n }\n\n function removeGeneration(generation: DocumentChatGeneration) {\n const { messages } = store.get()\n const index = messages.findIndex(turn => turn.role === 'assistant' && turn.generation === generation)\n if (index !== -1) patch({ messages: messages.filter((_, i) => i !== index) })\n }\n\n function clearError() {\n if (store.get().error !== null) patch({ error: null })\n }\n\n return { state: store, ask, abort, reset, decide, reveal, addGeneration, updateGeneration, removeGeneration, clearError }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"document-chat-model.js","names":[],"sources":["../../../src/features/ai/document-chat-model.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { EditorBackend } from '../../core/backend'\nimport { documentIdOf } from '../../core/block-transfer'\nimport type { ChangeSet, ChangeSetFeed } from '../../core/change-set'\nimport { aiContextOf, CORE_TARGET_IDS, convertTargetId, conversionsOf, parseConversionTarget, TYPED_BLOCK } from '../../core'\nimport { type Notifier, silentNotifier } from '../../core/notify'\nimport { createStore, type ReadonlyStore } from '../../core/store'\nimport type { Translate } from '../../i18n'\nimport type { ModelRole } from '../../settings'\nimport { type AgentOpReview, type AgentOpReviewState, createAgentOpReview, NO_AGENT_OPS } from './agent-op-review'\nimport type { AgentScope } from './agent-ops'\nimport type { AiTarget } from './ai-apply'\nimport { postAiSse } from './ai-sse'\nimport { documentChatText } from './document-chat'\nimport { type AgentOp, type AgentOpProblem, CHAT_LIMITS, type AiTask, type ChatToolCall, type DocumentChatMessage, type ModelRef } from './protocol'\nimport { notifyAiRateLimited } from './rate-limit'\n\n/**\n * A block generation run from the AI sidebar's quick actions, shown as an assistant turn. Client only: it never goes\n * to the `chat` stream. `markdown` is the streamed result without the task heading (shown with `withTaskHeading`, applied\n * as `taskResultMarkdown`), `target` the apply target captured when it started; only a result that streamed to the end\n * (`done`) can be applied.\n */\nexport interface DocumentChatGeneration {\n task: AiTask\n /** The instruction of a task with `needsPrompt` (empty otherwise). */\n prompt: string\n markdown: string\n target: AiTarget\n done: boolean\n}\n\n/**\n * A chat turn as shown: an assistant turn also keeps the problems of the operations the server dropped, or is a block\n * generation of the sidebar (`generation`).\n */\nexport type DocumentChatTurn =\n | Extract<DocumentChatMessage, { role: 'user' }>\n | (Extract<DocumentChatMessage, { role: 'assistant' }> & {\n problems?: AgentOpProblem[]\n /** The operations aimed at other Documents: they are in the change set panel, not in `ops` (the in-document review), and replay to the model with `ops`. */\n changeSetOps?: AgentOp[]\n generation?: DocumentChatGeneration\n })\n\n/** Snapshot of a conversation; every change replaces `messages`/`ops` (never mutated in place). */\nexport interface DocumentChatState {\n messages: readonly DocumentChatTurn[]\n /** Operation states by operation id. */\n ops: AgentOpReviewState['ops']\n streaming: boolean\n error: string | null\n}\n\nexport interface DocumentChatOptions {\n /** `EditorContext.backend.ai`; without it every question ends in the error state (501), like a server without AI. */\n ai: EditorBackend['ai']\n modelRef: (slot: ModelRole) => ModelRef\n /** Receives the 429 notice. */\n notify: Notifier\n /** The app's translator (`EditorContext.t`): error messages, the 429 notice and the snippets of proposed operations. */\n t: Translate\n /**\n * The change set panel's model (`createChangeSetModel`): receives the change set an answer proposed for other Documents, and\n * hands back the user's decisions on its hunks for the next message. Without it the model's operations aimed at other\n * Documents are still stored by the server, but nobody here shows or reports them.\n */\n changeSets?: ChangeSetFeed\n /**\n * The review that shows the proposed operations; default: the chat's own. Pass the one `documentActions` proposes to when a\n * host chat and this one work on the same document: operation IDs (`op<n>`) then come from one counter, an answer's\n * operations are renumbered when they arrive (another proposer may have taken the IDs the request was sent with), and\n * `reset()` forgets the operations of both.\n */\n review?: AgentOpReview\n}\n\nexport interface DocumentChat {\n state: ReadonlyStore<DocumentChatState>\n /**\n * Sends `message` about the editor's current document; `scope` limits proposed edits to the selected blocks (null:\n * whole document). Returns false when it was not answered (error, rate limit, cancelled before any answer): it is\n * then removed again so the caller can restore the input.\n */\n ask: (message: string, editor: Editor, scope: AgentScope | null) => Promise<boolean>\n abort: () => void\n reset: () => void\n /** Accepts or rejects the open hunks of operation `id`. */\n decide: (editor: Editor, id: string, accept: boolean) => void\n /** Scrolls to operation `op`: its first open hunk (selected for accept/reject), else its target block. */\n reveal: (editor: Editor, op: AgentOp) => boolean\n /** Appends the assistant turn of a block generation. `generation` keeps its identity in `state`. */\n addGeneration: (generation: DocumentChatGeneration) => void\n /** Updates `generation` in place (same object) and publishes a new `messages` array. */\n updateGeneration: (generation: DocumentChatGeneration, patch: Partial<Pick<DocumentChatGeneration, 'markdown' | 'done'>>) => void\n /** Removes the turn of `generation`. */\n removeGeneration: (generation: DocumentChatGeneration) => void\n clearError: () => void\n}\n\nconst EMPTY: DocumentChatState = { messages: [], ops: NO_AGENT_OPS, streaming: false, error: null }\n\n/**\n * Document chat state: the conversation lives only here (client side) and goes to the backend's `chat` stream in full with every\n * message, together with the document as it is at that moment and the edit scope. Proposed edit operations open an\n * in-document review; their decisions go to the model with the next message.\n */\nexport function createDocumentChat({ ai, modelRef, notify, t, changeSets, review = createAgentOpReview({ notify: silentNotifier, t }) }: DocumentChatOptions): DocumentChat {\n const store = createStore<DocumentChatState>(EMPTY)\n let controller: AbortController | null = null\n\n const patch = (changes: Partial<DocumentChatState>) => store.set({ ...store.get(), ...changes })\n\n function abort() {\n controller?.abort()\n }\n\n function reset() {\n abort()\n controller = null\n store.set(EMPTY)\n review.reset()\n changeSets?.reset()\n }\n\n /** Replaces the turn `turn` (by identity) with `next`; returns `next`. */\n function replaceTurn<T extends DocumentChatTurn>(turn: DocumentChatTurn, next: T): T {\n const { messages } = store.get()\n const index = messages.indexOf(turn)\n if (index !== -1) patch({ messages: messages.map((m, i) => (i === index ? next : m)) })\n return next\n }\n\n // The proposed operations live in the review (the chat shows their outcomes inline, so it needs no extra notices); its state is the chat's `ops`.\n review.state.subscribe(({ ops }) => {\n if (ops !== store.get().ops) patch({ ops })\n })\n\n async function ask(message: string, editor: Editor, scope: AgentScope | null): Promise<boolean> {\n if (store.get().streaming || !message.trim()) return false\n if (scope && !scope.ids.length) {\n patch({ error: t('ai.chat.emptyScope') })\n return false\n }\n // The block model of the system prompt: the editor's `aiContext` Block types and host blocks, and what `convert` may turn blocks into.\n // Read before any state changes: a merge error in the contributions throws here and must not leave the chat streaming.\n const { blockTypes, blocks, structure } = aiContextOf(editor)\n const providers = conversionsOf(editor)\n const targets = [\n ...CORE_TARGET_IDS,\n ...blockTypes.map(blockType => convertTargetId({ type: TYPED_BLOCK, blockType: blockType.id })),\n ...blocks.filter(block => parseConversionTarget(providers, block.id) !== null).map(block => block.id),\n ]\n const own = new AbortController()\n controller = own\n const feedback = [...review.unreported(), ...(changeSets?.unreported() ?? [])]\n const userTurn: DocumentChatTurn = { role: 'user', content: message.trim(), ...(feedback.length ? { feedback } : {}) }\n let answer: Extract<DocumentChatTurn, { role: 'assistant' }> = { role: 'assistant', content: '' }\n // The newest chat turns (block generations are the sidebar's own); the request must start with a user message.\n const sent: DocumentChatMessage[] = [...store.get().messages, userTurn].filter(turn => turn.role === 'user' || !turn.generation).slice(-CHAT_LIMITS.messages).map(turn => turn.role === 'user'\n ? { role: 'user', content: turn.content.slice(0, CHAT_LIMITS.message), ...(turn.feedback && { feedback: turn.feedback }) }\n : { role: 'assistant', content: turn.content.slice(0, CHAT_LIMITS.message), ...(turn.ops?.length || turn.changeSetOps?.length ? { ops: [...(turn.ops ?? []), ...(turn.changeSetOps ?? [])] } : {}), ...(turn.dropped ? { dropped: turn.dropped } : {}), ...(turn.tools?.length ? { tools: turn.tools.slice(-CHAT_LIMITS.tools) } : {}) })\n if (sent[0]?.role === 'assistant') sent.shift()\n patch({ error: null, streaming: true, messages: [...store.get().messages, userTurn, answer] })\n const received: { proposal?: { ops: AgentOp[], dropped: AgentOpProblem[] }, changeSet?: ChangeSet } = {}\n const openDocumentId = documentIdOf(editor)\n const sentNextOp = review.nextOp()\n // The server numbers the operations from `sentNextOp`; another source proposing to the same review meanwhile (a host chat's `doc.propose_ops`) may have taken those IDs by the time they arrive, so they are renumbered then.\n const numbered = (proposal: NonNullable<typeof received.proposal>, changeSetOps: number): NonNullable<typeof received.proposal> => {\n const first = review.nextOp()\n if (first === sentNextOp) {\n review.advanceOp(proposal.ops.length + changeSetOps)\n return proposal\n }\n // Past the IDs the server gave (the change set's operations keep theirs: its store decides by them).\n const start = Math.max(first, sentNextOp + proposal.ops.length + changeSetOps)\n review.advanceOp(start + proposal.ops.length - first)\n return { ...proposal, ops: proposal.ops.map((op, index) => ({ ...op, op: `op${start + index}` })) }\n }\n const body = { messages: sent, document: documentChatText(editor), ...(openDocumentId && { documentId: openDocumentId }), scope: scope?.ids ?? null, nextOp: sentNextOp, model: modelRef('generate'), blockTypes, blocks, targets, structure }\n const outcome = await postAiSse(ai, 'chat', body, {\n signal: own.signal,\n t,\n onDelta: (text) => {\n if (controller === own) answer = replaceTurn(answer, { ...answer, content: answer.content + text })\n },\n onEvent: (event, data) => {\n if (controller !== own) return\n if (event === 'ops') received.proposal = numbered(data as NonNullable<typeof received.proposal>, received.changeSet?.hunks.length ?? 0)\n // Operations aimed at other Documents: stored by the server as a change set.\n else if (event === 'change-set') received.changeSet = data as ChangeSet\n // A read-only tool call the server ran: kept on the turn, so the next message replays it to the model.\n else if (event === 'tool') answer = replaceTurn(answer, { ...answer, tools: [...(answer.tools ?? []), data as ChatToolCall] })\n },\n })\n if (controller !== own) return false // superseded by reset()\n controller = null\n patch({ streaming: false })\n const { ops: proposed = [], dropped = [] } = outcome.status === 'done' ? received.proposal ?? {} : {}\n if (outcome.status === 'done' || (outcome.status === 'aborted' && answer.content)) {\n const reported = feedback.filter(({ outcome: sentOutcome }) => sentOutcome !== 'pending').map(({ op }) => op)\n review.markReported(reported)\n changeSets?.markReported(reported)\n const changeSetOps = received.changeSet?.hunks.map(({ op }) => op) ?? []\n if (received.changeSet) changeSets?.receive(received.changeSet)\n if (proposed.length || dropped.length || changeSetOps.length) {\n answer = replaceTurn(answer, { ...answer, ops: proposed, dropped: dropped.length, problems: dropped, ...(changeSetOps.length && { changeSetOps }) })\n // The operations got their IDs when the `ops` event arrived (`numbered`), so decisions on either kind are told apart by `op<n>`.\n if (proposed.length || dropped.length) {\n if (!editor.isDestroyed) review.propose(editor, proposed, scope)\n }\n }\n return true\n }\n // Drop the unanswered turn: a later message should not carry a dangling one in its history.\n patch({ messages: store.get().messages.slice(0, -2) })\n if (outcome.status === 'rate-limited') notifyAiRateLimited(notify, t)\n else if (outcome.status === 'error') patch({ error: outcome.message })\n return false\n }\n\n const decide = review.decide\n const reveal = review.reveal\n\n function addGeneration(generation: DocumentChatGeneration) {\n patch({ error: null, messages: [...store.get().messages, { role: 'assistant', content: '', generation }] })\n }\n\n function updateGeneration(generation: DocumentChatGeneration, changes: Partial<Pick<DocumentChatGeneration, 'markdown' | 'done'>>) {\n Object.assign(generation, changes)\n const { messages } = store.get()\n patch({ messages: messages.map(turn => turn.role === 'assistant' && turn.generation === generation ? { ...turn } : turn) })\n }\n\n function removeGeneration(generation: DocumentChatGeneration) {\n const { messages } = store.get()\n const index = messages.findIndex(turn => turn.role === 'assistant' && turn.generation === generation)\n if (index !== -1) patch({ messages: messages.filter((_, i) => i !== index) })\n }\n\n function clearError() {\n if (store.get().error !== null) patch({ error: null })\n }\n\n return { state: store, ask, abort, reset, decide, reveal, addGeneration, updateGeneration, removeGeneration, clearError }\n}\n"],"mappings":";;;;;;;;;;;;;AAoGA,MAAM,QAA2B;CAAE,UAAU,CAAC;CAAG,KAAK;CAAc,WAAW;CAAO,OAAO;AAAK;;;;;;AAOlG,SAAgB,mBAAmB,EAAE,IAAI,UAAU,QAAQ,GAAG,YAAY,SAAS,oBAAoB;CAAE,QAAQ;CAAgB;AAAE,CAAC,KAAwC;CAC1K,MAAM,QAAQ,YAA+B,KAAK;CAClD,IAAI,aAAqC;CAEzC,MAAM,SAAS,YAAwC,MAAM,IAAI;EAAE,GAAG,MAAM,IAAI;EAAG,GAAG;CAAQ,CAAC;CAE/F,SAAS,QAAQ;EACf,YAAY,MAAM;CACpB;CAEA,SAAS,QAAQ;EACf,MAAM;EACN,aAAa;EACb,MAAM,IAAI,KAAK;EACf,OAAO,MAAM;EACb,YAAY,MAAM;CACpB;;CAGA,SAAS,YAAwC,MAAwB,MAAY;EACnF,MAAM,EAAE,aAAa,MAAM,IAAI;EAC/B,MAAM,QAAQ,SAAS,QAAQ,IAAI;EACnC,IAAI,UAAU,IAAI,MAAM,EAAE,UAAU,SAAS,KAAK,GAAG,MAAO,MAAM,QAAQ,OAAO,CAAE,EAAE,CAAC;EACtF,OAAO;CACT;CAGA,OAAO,MAAM,WAAW,EAAE,UAAU;EAClC,IAAI,QAAQ,MAAM,IAAI,CAAC,CAAC,KAAK,MAAM,EAAE,IAAI,CAAC;CAC5C,CAAC;CAED,eAAe,IAAI,SAAiB,QAAgB,OAA4C;EAC9F,IAAI,MAAM,IAAI,CAAC,CAAC,aAAa,CAAC,QAAQ,KAAK,GAAG,OAAO;EACrD,IAAI,SAAS,CAAC,MAAM,IAAI,QAAQ;GAC9B,MAAM,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;GACxC,OAAO;EACT;EAGA,MAAM,EAAE,YAAY,QAAQ,cAAc,YAAY,MAAM;EAC5D,MAAM,YAAY,cAAc,MAAM;EACtC,MAAM,UAAU;GACd,GAAG;GACH,GAAG,WAAW,KAAI,cAAa,gBAAgB;IAAE,MAAM;IAAa,WAAW,UAAU;GAAG,CAAC,CAAC;GAC9F,GAAG,OAAO,QAAO,UAAS,sBAAsB,WAAW,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC,KAAI,UAAS,MAAM,EAAE;EACtG;EACA,MAAM,MAAM,IAAI,gBAAgB;EAChC,aAAa;EACb,MAAM,WAAW,CAAC,GAAG,OAAO,WAAW,GAAG,GAAI,YAAY,WAAW,KAAK,CAAC,CAAE;EAC7E,MAAM,WAA6B;GAAE,MAAM;GAAQ,SAAS,QAAQ,KAAK;GAAG,GAAI,SAAS,SAAS,EAAE,SAAS,IAAI,CAAC;EAAG;EACrH,IAAI,SAA2D;GAAE,MAAM;GAAa,SAAS;EAAG;EAEhG,MAAM,OAA8B,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC,UAAU,QAAQ,CAAC,CAAC,QAAO,SAAQ,KAAK,SAAS,UAAU,CAAC,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC,YAAY,QAAQ,CAAC,CAAC,KAAI,SAAQ,KAAK,SAAS,SACpL;GAAE,MAAM;GAAQ,SAAS,KAAK,QAAQ,MAAM,GAAG,YAAY,OAAO;GAAG,GAAI,KAAK,YAAY,EAAE,UAAU,KAAK,SAAS;EAAG,IACvH;GAAE,MAAM;GAAa,SAAS,KAAK,QAAQ,MAAM,GAAG,YAAY,OAAO;GAAG,GAAI,KAAK,KAAK,UAAU,KAAK,cAAc,SAAS,EAAE,KAAK,CAAC,GAAI,KAAK,OAAO,CAAC,GAAI,GAAI,KAAK,gBAAgB,CAAC,CAAE,EAAE,IAAI,CAAC;GAAI,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;GAAI,GAAI,KAAK,OAAO,SAAS,EAAE,OAAO,KAAK,MAAM,MAAM,CAAC,YAAY,KAAK,EAAE,IAAI,CAAC;EAAG,CAAC;EAC1U,IAAI,KAAK,EAAE,EAAE,SAAS,aAAa,KAAK,MAAM;EAC9C,MAAM;GAAE,OAAO;GAAM,WAAW;GAAM,UAAU;IAAC,GAAG,MAAM,IAAI,CAAC,CAAC;IAAU;IAAU;GAAM;EAAE,CAAC;EAC7F,MAAM,WAAgG,CAAC;EACvG,MAAM,iBAAiB,aAAa,MAAM;EAC1C,MAAM,aAAa,OAAO,OAAO;EAEjC,MAAM,YAAY,UAAiD,iBAAgE;GACjI,MAAM,QAAQ,OAAO,OAAO;GAC5B,IAAI,UAAU,YAAY;IACxB,OAAO,UAAU,SAAS,IAAI,SAAS,YAAY;IACnD,OAAO;GACT;GAEA,MAAM,QAAQ,KAAK,IAAI,OAAO,aAAa,SAAS,IAAI,SAAS,YAAY;GAC7E,OAAO,UAAU,QAAQ,SAAS,IAAI,SAAS,KAAK;GACpD,OAAO;IAAE,GAAG;IAAU,KAAK,SAAS,IAAI,KAAK,IAAI,WAAW;KAAE,GAAG;KAAI,IAAI,KAAK,QAAQ;IAAQ,EAAE;GAAE;EACpG;EACA,MAAM,OAAO;GAAE,UAAU;GAAM,UAAU,iBAAiB,MAAM;GAAG,GAAI,kBAAkB,EAAE,YAAY,eAAe;GAAI,OAAO,OAAO,OAAO;GAAM,QAAQ;GAAY,OAAO,SAAS,UAAU;GAAG;GAAY;GAAQ;GAAS;EAAU;EAC7O,MAAM,UAAU,MAAM,UAAU,IAAI,QAAQ,MAAM;GAChD,QAAQ,IAAI;GACZ;GACA,UAAU,SAAS;IACjB,IAAI,eAAe,KAAK,SAAS,YAAY,QAAQ;KAAE,GAAG;KAAQ,SAAS,OAAO,UAAU;IAAK,CAAC;GACpG;GACA,UAAU,OAAO,SAAS;IACxB,IAAI,eAAe,KAAK;IACxB,IAAI,UAAU,OAAO,SAAS,WAAW,SAAS,MAA+C,SAAS,WAAW,MAAM,UAAU,CAAC;SAEjI,IAAI,UAAU,cAAc,SAAS,YAAY;SAEjD,IAAI,UAAU,QAAQ,SAAS,YAAY,QAAQ;KAAE,GAAG;KAAQ,OAAO,CAAC,GAAI,OAAO,SAAS,CAAC,GAAI,IAAoB;IAAE,CAAC;GAC/H;EACF,CAAC;EACD,IAAI,eAAe,KAAK,OAAO;EAC/B,aAAa;EACb,MAAM,EAAE,WAAW,MAAM,CAAC;EAC1B,MAAM,EAAE,KAAK,WAAW,CAAC,GAAG,UAAU,CAAC,MAAM,QAAQ,WAAW,SAAS,SAAS,YAAY,CAAC,IAAI,CAAC;EACpG,IAAI,QAAQ,WAAW,UAAW,QAAQ,WAAW,aAAa,OAAO,SAAU;GACjF,MAAM,WAAW,SAAS,QAAQ,EAAE,SAAS,kBAAkB,gBAAgB,SAAS,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE;GAC5G,OAAO,aAAa,QAAQ;GAC5B,YAAY,aAAa,QAAQ;GACjC,MAAM,eAAe,SAAS,WAAW,MAAM,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC;GACvE,IAAI,SAAS,WAAW,YAAY,QAAQ,SAAS,SAAS;GAC9D,IAAI,SAAS,UAAU,QAAQ,UAAU,aAAa,QAAQ;IAC5D,SAAS,YAAY,QAAQ;KAAE,GAAG;KAAQ,KAAK;KAAU,SAAS,QAAQ;KAAQ,UAAU;KAAS,GAAI,aAAa,UAAU,EAAE,aAAa;IAAG,CAAC;IAEnJ,IAAI,SAAS,UAAU,QAAQ,QACzB;SAAA,CAAC,OAAO,aAAa,OAAO,QAAQ,QAAQ,UAAU,KAAK;IAAA;GAEnE;GACA,OAAO;EACT;EAEA,MAAM,EAAE,UAAU,MAAM,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,EAAE,EAAE,CAAC;EACrD,IAAI,QAAQ,WAAW,gBAAgB,oBAAoB,QAAQ,CAAC;OAC/D,IAAI,QAAQ,WAAW,SAAS,MAAM,EAAE,OAAO,QAAQ,QAAQ,CAAC;EACrE,OAAO;CACT;CAEA,MAAM,SAAS,OAAO;CACtB,MAAM,SAAS,OAAO;CAEtB,SAAS,cAAc,YAAoC;EACzD,MAAM;GAAE,OAAO;GAAM,UAAU,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC,UAAU;IAAE,MAAM;IAAa,SAAS;IAAI;GAAW,CAAC;EAAE,CAAC;CAC5G;CAEA,SAAS,iBAAiB,YAAoC,SAAqE;EACjI,OAAO,OAAO,YAAY,OAAO;EACjC,MAAM,EAAE,aAAa,MAAM,IAAI;EAC/B,MAAM,EAAE,UAAU,SAAS,KAAI,SAAQ,KAAK,SAAS,eAAe,KAAK,eAAe,aAAa,EAAE,GAAG,KAAK,IAAI,IAAI,EAAE,CAAC;CAC5H;CAEA,SAAS,iBAAiB,YAAoC;EAC5D,MAAM,EAAE,aAAa,MAAM,IAAI;EAC/B,MAAM,QAAQ,SAAS,WAAU,SAAQ,KAAK,SAAS,eAAe,KAAK,eAAe,UAAU;EACpG,IAAI,UAAU,IAAI,MAAM,EAAE,UAAU,SAAS,QAAQ,GAAG,MAAM,MAAM,KAAK,EAAE,CAAC;CAC9E;CAEA,SAAS,aAAa;EACpB,IAAI,MAAM,IAAI,CAAC,CAAC,UAAU,MAAM,MAAM,EAAE,OAAO,KAAK,CAAC;CACvD;CAEA,OAAO;EAAE,OAAO;EAAO;EAAK;EAAO;EAAO;EAAQ;EAAQ;EAAe;EAAkB;EAAkB;CAAW;AAC1H"}
@@ -17,8 +17,8 @@ export type ChatStream = (messages: ChatMessage[], signal: AbortSignal) => Async
17
17
  export declare const generateBodySchema: z.ZodObject<{
18
18
  task: z.ZodEnum<{
19
19
  custom: "custom";
20
- continue: "continue";
21
20
  generate: "generate";
21
+ continue: "continue";
22
22
  reformulate: "reformulate";
23
23
  "transcript-summary": "transcript-summary";
24
24
  "transcript-minutes": "transcript-minutes";
@@ -43,26 +43,26 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
43
43
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
44
44
  "update:modelValue": (markdown: string) => any;
45
45
  ready: (editor: Editor) => any;
46
- settingsOpen: (tab?: string | undefined) => any;
47
46
  "update:sidebarOpen": (value: boolean) => any;
48
47
  "update:commentsOpen": (value: boolean) => any;
49
- frontmatter: (frontmatter: string | null) => any;
50
48
  metadataCreate: (list: string, item: MetadataItem) => any;
51
49
  contentError: (error: Error) => any;
50
+ settingsOpen: (tab?: string | undefined) => any;
51
+ frontmatter: (frontmatter: string | null) => any;
52
52
  diagnostics: (diagnostics: MarkdownDiagnostic[]) => any;
53
53
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
54
54
  "onUpdate:modelValue"?: ((markdown: string) => any) | undefined;
55
55
  onReady?: ((editor: Editor) => any) | undefined;
56
- onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
57
56
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
58
57
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
59
- onFrontmatter?: ((frontmatter: string | null) => any) | undefined;
60
58
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
61
59
  onContentError?: ((error: Error) => any) | undefined;
60
+ onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
61
+ onFrontmatter?: ((frontmatter: string | null) => any) | undefined;
62
62
  onDiagnostics?: ((diagnostics: MarkdownDiagnostic[]) => any) | undefined;
63
63
  }>, {
64
- editable: boolean;
65
64
  modelValue: string;
65
+ editable: boolean;
66
66
  settingsUi: "internal" | "external";
67
67
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
68
68
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -148,8 +148,8 @@ export declare const extractBodySchema: z.ZodObject<{
148
148
  documentId: z.ZodString;
149
149
  name: z.ZodOptional<z.ZodString>;
150
150
  method: z.ZodEnum<{
151
- description: "description";
152
151
  transcription: "transcription";
152
+ description: "description";
153
153
  bibliography: "bibliography";
154
154
  auto: "auto";
155
155
  "pdf-text": "pdf-text";
@@ -84,19 +84,19 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
84
84
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
85
85
  "update:modelValue": (content: JSONContent) => any;
86
86
  ready: (editor: Editor) => any;
87
- settingsOpen: (tab?: string | undefined) => any;
88
87
  "update:sidebarOpen": (value: boolean) => any;
89
88
  "update:commentsOpen": (value: boolean) => any;
90
89
  metadataCreate: (list: string, item: MetadataItem) => any;
91
90
  contentError: (error: Error) => any;
91
+ settingsOpen: (tab?: string | undefined) => any;
92
92
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
93
93
  "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
94
94
  onReady?: ((editor: Editor) => any) | undefined;
95
- onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
96
95
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
97
96
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
98
97
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
99
98
  onContentError?: ((error: Error) => any) | undefined;
99
+ onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
100
100
  }>, {
101
101
  content: ContentLevel | readonly FeatureId[];
102
102
  ai: boolean;
@@ -43,19 +43,19 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
43
43
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
44
44
  "update:modelValue": (content: JSONContent) => any;
45
45
  ready: (editor: Editor) => any;
46
- settingsOpen: (tab?: string | undefined) => any;
47
46
  "update:sidebarOpen": (value: boolean) => any;
48
47
  "update:commentsOpen": (value: boolean) => any;
49
48
  metadataCreate: (list: string, item: MetadataItem) => any;
50
49
  contentError: (error: Error) => any;
50
+ settingsOpen: (tab?: string | undefined) => any;
51
51
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
52
52
  "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
53
53
  onReady?: ((editor: Editor) => any) | undefined;
54
- onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
55
54
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
56
55
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
57
56
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
58
57
  onContentError?: ((error: Error) => any) | undefined;
58
+ onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
59
59
  }>, {
60
60
  editable: boolean;
61
61
  settingsUi: "internal" | "external";
@@ -45,19 +45,19 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
45
45
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
46
46
  "update:modelValue": (content: JSONContent) => any;
47
47
  ready: (editor: Editor) => any;
48
- settingsOpen: (tab?: string | undefined) => any;
49
48
  "update:sidebarOpen": (value: boolean) => any;
50
49
  "update:commentsOpen": (value: boolean) => any;
51
50
  metadataCreate: (list: string, item: MetadataItem) => any;
52
51
  contentError: (error: Error) => any;
52
+ settingsOpen: (tab?: string | undefined) => any;
53
53
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
54
54
  "onUpdate:modelValue"?: ((content: JSONContent) => any) | undefined;
55
55
  onReady?: ((editor: Editor) => any) | undefined;
56
- onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
57
56
  "onUpdate:sidebarOpen"?: ((value: boolean) => any) | undefined;
58
57
  "onUpdate:commentsOpen"?: ((value: boolean) => any) | undefined;
59
58
  onMetadataCreate?: ((list: string, item: MetadataItem) => any) | undefined;
60
59
  onContentError?: ((error: Error) => any) | undefined;
60
+ onSettingsOpen?: ((tab?: string | undefined) => any) | undefined;
61
61
  }>, {
62
62
  editable: boolean;
63
63
  settingsUi: "internal" | "external";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skaile/forge-editor",
3
- "version": "5.0.1",
3
+ "version": "5.0.2",
4
4
  "type": "module",
5
5
  "description": "Vue and React editor library on TipTap v3 (Vue: Nuxt UI; React: shadcn/ui): framework-free features with Vue and React bindings, simple and advanced presets, tree-shakeable entries. Built ESM with type declarations.",
6
6
  "license": "MIT",