@skaile/forge-editor-compose 0.5.0 → 0.7.0

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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Changed
6
+
7
+ - The `slot` type sets `chrome.derived: 'open'` (both `slotChrome: 'plain'`, which now has a `chrome`, and `'template'`): slot blocks made from a template (blocks with a `derivation`) are open on load instead of collapsed to a faded preview, fold with a chevron and keep the folded state per document. Needs `@skaile/forge-editor` 6.8.0 (`BlockType.chrome.derived`). The next version jump of gutachten-webedit opens its blocks from the template too; this is intended.
8
+
9
+ ## 0.6.0 - 2026-11-05
10
+
11
+ ### Added
12
+
13
+ - `composeBlockTypes({ slotChrome: 'template' })`: the slot of a template document as a neutral gray card (`TEMPLATE_SLOT_TONE`) with a read-only field row under the title (identifier, `je`, `optional` when `ja`, `Auswahl`, and `KI: <first line of anweisung>`) and a body that folds per block or all at once. The default `'plain'` keeps today's slot for case and instance documents. `slotFieldLabels` replaces the German prefixes. The template look needs `@skaile/forge-editor` 6.7.0 (`BlockType.chrome.fields` / `collapsible`).
14
+
3
15
  ## 0.5.0 - 2026-11-05
4
16
 
5
17
  ### Changed
package/README.md CHANGED
@@ -133,6 +133,13 @@ Failures of `invoke` (a rejected promise) show the error's message (`data.messag
133
133
  - A unit `split` cannot assign still becomes an `example` proposal (empty `slot`): nothing of the corpus is lost, the person assigns it in the inspector. A unit a rule or classifier decided is never sent to the language model (`stage: 'label'` batches hold only undecided units).
134
134
  - `contextOf` must answer for every document an operation names: the document with the source (`split`, `facts`), the instance (`material`, `example`, `instance`) and the document open in the chat. `null` is a 404.
135
135
 
136
+ ## Versioning
137
+
138
+ 0.x: breaking changes ship as a plain minor bump, with no aliases and no deprecation layers; callers migrate in one step.
139
+
140
+ - Any change to the input of an operation (`ops.*`) and any breaking change in `@skaile/forge-compose` that reaches this package is listed in `CHANGELOG.md` under `### Breaking` with a migration line in the form „before → after“.
141
+ - New parameters go into an options object with a default value.
142
+
136
143
  ## Peers
137
144
 
138
145
  `@skaile/forge-editor` `^6.2.0` (6.2 adds `ToolContext.documentId`, which the chat tools read), `@tiptap/core`, `@tiptap/pm`, `vue` `^3.5`; dependencies `@skaile/forge-compose` and `zod`. Optional: `ai` `^7.0.124` (only `./server/ai-sdk` imports it), `@skaile/forge-app-actions` `^0.4.1` (only `./server/actions` imports it).
@@ -11,7 +11,18 @@ export interface ComposeBlockTypeOptions {
11
11
  }[];
12
12
  /** Display names of the four types; defaults are German. */
13
13
  labels?: Partial<Record<'slot' | 'source' | 'fact' | 'example', string>>;
14
+ /**
15
+ * Look of the slot. `'plain'` (default): the plain Typed block of a case or instance document; a slot made from a template is open
16
+ * on load and folds with a chevron (`chrome.derived: 'open'`). `'template'`: for template
17
+ * documents, a gray card (`chrome.tone`) with a read-only field row under the title (identifier, `je`, `optional`, `selector`,
18
+ * first line of `anweisung`; a click opens the details) and a chevron that folds the body (`chrome.collapsible`).
19
+ */
20
+ slotChrome?: 'template' | 'plain';
21
+ /** Prefixes of the template field row (`slotChrome: 'template'`); defaults are German: `je`, `optional`, `Auswahl`, `KI`. */
22
+ slotFieldLabels?: Partial<Record<'je' | 'optional' | 'selector' | 'anweisung', string>>;
14
23
  }
24
+ /** Neutral gray of the template slot card, light and dark (forge-editor's `--sk-muted` token). */
25
+ export declare const TEMPLATE_SLOT_TONE = "var(--sk-muted)";
15
26
  /** The four compose Block types. */
16
27
  export declare function composeBlockTypes(o?: ComposeBlockTypeOptions): BlockType[];
17
28
  /** `blockType` with the `ai-brief` capability's purpose (and, when given, instructions) replaced; the other brief settings stay. Pure. */
@@ -1,5 +1,47 @@
1
1
  import { KIND } from "@skaile/forge-compose/documents";
2
2
  //#region src/block-types.ts
3
+ /** Neutral gray of the template slot card, light and dark (forge-editor's `--sk-muted` token). */
4
+ const TEMPLATE_SLOT_TONE = "var(--sk-muted)";
5
+ /**
6
+ * The chrome of the plain slot: a slot made from a template carries a `derivation` (it is a Derived block), and a Derived block would
7
+ * show collapsed to a faded preview; the slot opens instead, so the text a case was built with is readable at once (`chrome.derived`).
8
+ */
9
+ const PLAIN_SLOT_CHROME = { derived: "open" };
10
+ /** The chrome of a template slot (`slotChrome: 'template'`) with the field row prefixes `labels`. */
11
+ function templateSlotChrome(labels = {}) {
12
+ const label = {
13
+ je: "je",
14
+ optional: "optional",
15
+ selector: "Auswahl",
16
+ anweisung: "KI",
17
+ ...labels
18
+ };
19
+ return {
20
+ ...PLAIN_SLOT_CHROME,
21
+ tone: TEMPLATE_SLOT_TONE,
22
+ collapsible: true,
23
+ fields: [
24
+ { key: "identifier" },
25
+ {
26
+ key: "je",
27
+ label: label.je
28
+ },
29
+ {
30
+ key: "optional",
31
+ label: label.optional,
32
+ flag: "ja"
33
+ },
34
+ {
35
+ key: "selector",
36
+ label: label.selector
37
+ },
38
+ {
39
+ key: "anweisung",
40
+ label: label.anweisung
41
+ }
42
+ ]
43
+ };
44
+ }
3
45
  /** `ja`/`nein` as a select: the library has no boolean field; unset means the per-field default (`optional` nein, `heading` ja). */
4
46
  const JA_NEIN = [{
5
47
  value: "ja",
@@ -101,7 +143,8 @@ function composeBlockTypes(o = {}) {
101
143
  id: "ai-brief",
102
144
  config: { ...SLOT_AI }
103
145
  }
104
- ]
146
+ ],
147
+ chrome: o.slotChrome === "template" ? templateSlotChrome(o.slotFieldLabels) : PLAIN_SLOT_CHROME
105
148
  },
106
149
  {
107
150
  id: KIND.source,
@@ -231,6 +274,6 @@ function withProperties(blockType, definitions) {
231
274
  };
232
275
  }
233
276
  //#endregion
234
- export { composeBlockTypes, withAiBrief, withProperties };
277
+ export { TEMPLATE_SLOT_TONE, composeBlockTypes, withAiBrief, withProperties };
235
278
 
236
279
  //# sourceMappingURL=block-types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"block-types.js","names":[],"sources":["../src/block-types.ts"],"sourcesContent":["// Block types of the compose documents. Pure data (type-only imports besides `KIND`), so seeds, tests and server code use the\n// same ids and properties as the editor. `section` is forge-editor's built-in container type and is not defined here.\n//\n// slot one text block of a template (and of an instance made from it); `identifier` is what facts and examples target\n// source a text the facts and examples come from (a letter, a chapter, a case file)\n// fact one short statement of a source, assigned to a slot\n// example one reusable text for a slot, tagged by case (`alter/*`, `icd/*`, …); a proposal until a person accepts it\nimport type { BlockType } from '@skaile/forge-editor/composites'\nimport type { PropertyDefinition } from '@skaile/forge-editor/core'\nimport { KIND } from '@skaile/forge-compose/documents'\n\nexport type ComposeKind = typeof KIND.slot | typeof KIND.source | typeof KIND.fact | typeof KIND.example\n\nexport interface ComposeBlockTypeOptions {\n /** The slot identifiers a fact or example can target: `value` = identifier, `label` = what the person sees. Without options `slot` is a text field. */\n slotOptions?: { value: string; label: string }[]\n /** Display names of the four types; defaults are German. */\n labels?: Partial<Record<'slot' | 'source' | 'fact' | 'example', string>>\n}\n\n/** `ja`/`nein` as a select: the library has no boolean field; unset means the per-field default (`optional` nein, `heading` ja). */\nconst JA_NEIN = [\n { value: 'ja', label: 'Ja' },\n { value: 'nein', label: 'Nein' },\n]\n\nconst BASKET = { id: 'basket' }\nconst TITLE_HIDDEN = { id: 'titleHidden' }\n\nconst DERIVATION: PropertyDefinition = { key: 'derivation', label: 'Herkunft', type: 'derivation' }\n\n/**\n * The slot type's AI brief. It has no `contentTemplate`: the editor leaves a `{key}` placeholder of the template literally in\n * the prompt when the property is empty, so a template of `{skelett}` would reach the model as the text „{skelett}“ for every\n * slot without a skeleton. The skeleton travels as an `aiBlockData` entry instead (`compose-material`), only when it is set.\n */\nconst SLOT_AI = {\n purpose: 'Ein Abschnitt eines Dokuments: sachlicher Fließtext, der nur aus den genannten Fakten entsteht; Beispiele sind Stilvorlage.',\n instructions: 'Schreibe sachlichen Fließtext nur aus den genannten Fakten. Beispiele sind Stilvorlage für Aufbau und Ton; erfinde nichts hinzu.',\n instructionsField: 'anweisung',\n}\n\n/** The four compose Block types. */\nexport function composeBlockTypes(o: ComposeBlockTypeOptions = {}): BlockType[] {\n const label = { slot: 'Abschnitt', source: 'Quelle', fact: 'Fakt', example: 'Beispiel', ...o.labels }\n const options = (o.slotOptions ?? []).filter(option => option.value !== '')\n\n // A select cannot hold an empty option (the Select component refuses an empty value): the placeholder shows while `slot` is unset.\n // A `slot` that is no longer in the template stays in the document but is not selectable.\n const slotTarget: PropertyDefinition = options.length > 0\n ? { key: 'slot', label: 'Slot (Bezeichner im Template)', type: 'select', options }\n : { key: 'slot', label: 'Slot (Bezeichner im Template)', type: 'text' }\n\n return [\n {\n id: KIND.slot,\n label: label.slot,\n icon: 'i-lucide-file-text',\n titlePlaceholder: label.slot,\n descriptionPlaceholder: 'Beschreibung: Zuordnung, Stichpunkte, Zweck der Prosa',\n capabilities: [\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n { key: 'identifier', label: 'Bezeichner (z. B. therapie/pflege)', type: 'text' },\n { key: 'optional', label: 'Optional (Slot darf in der Instanz entfallen)', type: 'select', options: JA_NEIN },\n { key: 'heading', label: 'Überschrift im Dokument', type: 'select', options: JA_NEIN },\n { key: 'anweisung', label: 'KI-Anweisung (ersetzt die Vorgabe)', type: 'textarea' },\n { key: 'skelett', label: 'Skelett (Zieltext als Markdown, {{…}} = freie Stellen)', type: 'textarea' },\n { key: 'selector', label: 'Auswahl (#muss ~bevorzugt -nicht)', type: 'text' },\n { key: 'je', label: 'Je Fall-Tag wiederholen (Facette, z. B. icd)', type: 'text' },\n ],\n },\n },\n { id: 'ai-brief', config: { ...SLOT_AI } },\n ],\n },\n {\n id: KIND.source,\n label: label.source,\n icon: 'i-lucide-file-search',\n titlePlaceholder: 'Titel der Quelle',\n descriptionPlaceholder: '',\n capabilities: [\n { id: 'container', config: {} },\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n { key: 'sourceType', label: 'Quelltyp', type: 'text' },\n { key: 'date', label: 'Datum', type: 'text' },\n ],\n },\n },\n ],\n chrome: { tags: false },\n },\n {\n id: KIND.fact,\n label: label.fact,\n icon: 'i-lucide-lightbulb',\n titlePlaceholder: '',\n descriptionPlaceholder: '',\n capabilities: [BASKET, TITLE_HIDDEN, { id: 'properties', config: { definitions: [slotTarget, DERIVATION] } }],\n chrome: { tags: false },\n },\n {\n id: KIND.example,\n label: label.example,\n icon: 'i-lucide-quote',\n titlePlaceholder: label.example,\n descriptionPlaceholder: '',\n capabilities: [\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n slotTarget,\n { key: 'status', label: 'Status', type: 'select', options: [\n { value: 'vorschlag', label: 'Vorschlag' },\n { value: 'geprüft', label: 'Geprüft' },\n ] },\n // Written by the split, shown as the proposal chip of the title row (`composeFeatures`) while `status` is `vorschlag`;\n // the properties form has no read-only field, so both are hidden from it and removed when the proposal is accepted.\n { key: 'confidence', label: 'Sicherheit', type: 'text', hidden: true },\n { key: 'origin', label: 'Herkunft der Zuordnung', type: 'text', hidden: true },\n DERIVATION,\n ],\n },\n },\n ],\n },\n ]\n}\n\n/** `blockType` with the `ai-brief` capability's purpose (and, when given, instructions) replaced; the other brief settings stay. Pure. */\nexport function withAiBrief(blockType: BlockType, brief: { purpose: string; instructions?: string }): BlockType {\n const config = (existing: unknown) => ({\n ...(existing && typeof existing === 'object' ? existing : {}),\n purpose: brief.purpose,\n ...(brief.instructions !== undefined && { instructions: brief.instructions }),\n })\n const listed = blockType.capabilities.some(capability => capability.id === 'ai-brief')\n return {\n ...blockType,\n capabilities: listed\n ? blockType.capabilities.map(capability => (capability.id === 'ai-brief' ? { ...capability, config: config(capability.config) } : capability))\n : [...blockType.capabilities, { id: 'ai-brief', config: config(undefined) }],\n }\n}\n\n/** `blockType` with `definitions` added to its `properties` capability; a definition whose key exists replaces it in place. Pure. */\nexport function withProperties(blockType: BlockType, definitions: readonly PropertyDefinition[]): BlockType {\n const merge = (existing: unknown): { definitions: PropertyDefinition[] } => {\n const current = (existing as { definitions?: readonly PropertyDefinition[] } | undefined)?.definitions ?? []\n const replaced = current.map(definition => definitions.find(added => added.key === definition.key) ?? definition)\n const added = definitions.filter(definition => !current.some(known => known.key === definition.key))\n return { definitions: [...replaced, ...added] }\n }\n const listed = blockType.capabilities.some(capability => capability.id === 'properties')\n return {\n ...blockType,\n capabilities: listed\n ? blockType.capabilities.map(capability => (capability.id === 'properties' ? { ...capability, config: merge(capability.config) } : capability))\n : [...blockType.capabilities, { id: 'properties', config: merge(undefined) }],\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"block-types.js","names":[],"sources":["../src/block-types.ts"],"sourcesContent":["// Block types of the compose documents. Pure data (type-only imports besides `KIND`), so seeds, tests and server code use the\n// same ids and properties as the editor. `section` is forge-editor's built-in container type and is not defined here.\n//\n// slot one text block of a template (and of an instance made from it); `identifier` is what facts and examples target\n// source a text the facts and examples come from (a letter, a chapter, a case file)\n// fact one short statement of a source, assigned to a slot\n// example one reusable text for a slot, tagged by case (`alter/*`, `icd/*`, …); a proposal until a person accepts it\nimport type { BlockType, BlockTypeChrome } from '@skaile/forge-editor/composites'\nimport type { PropertyDefinition } from '@skaile/forge-editor/core'\nimport { KIND } from '@skaile/forge-compose/documents'\n\nexport type ComposeKind = typeof KIND.slot | typeof KIND.source | typeof KIND.fact | typeof KIND.example\n\nexport interface ComposeBlockTypeOptions {\n /** The slot identifiers a fact or example can target: `value` = identifier, `label` = what the person sees. Without options `slot` is a text field. */\n slotOptions?: { value: string; label: string }[]\n /** Display names of the four types; defaults are German. */\n labels?: Partial<Record<'slot' | 'source' | 'fact' | 'example', string>>\n /**\n * Look of the slot. `'plain'` (default): the plain Typed block of a case or instance document; a slot made from a template is open\n * on load and folds with a chevron (`chrome.derived: 'open'`). `'template'`: for template\n * documents, a gray card (`chrome.tone`) with a read-only field row under the title (identifier, `je`, `optional`, `selector`,\n * first line of `anweisung`; a click opens the details) and a chevron that folds the body (`chrome.collapsible`).\n */\n slotChrome?: 'template' | 'plain'\n /** Prefixes of the template field row (`slotChrome: 'template'`); defaults are German: `je`, `optional`, `Auswahl`, `KI`. */\n slotFieldLabels?: Partial<Record<'je' | 'optional' | 'selector' | 'anweisung', string>>\n}\n\n/** Neutral gray of the template slot card, light and dark (forge-editor's `--sk-muted` token). */\nexport const TEMPLATE_SLOT_TONE = 'var(--sk-muted)'\n\n/**\n * The chrome of the plain slot: a slot made from a template carries a `derivation` (it is a Derived block), and a Derived block would\n * show collapsed to a faded preview; the slot opens instead, so the text a case was built with is readable at once (`chrome.derived`).\n */\nconst PLAIN_SLOT_CHROME: BlockTypeChrome = { derived: 'open' }\n\n/** The chrome of a template slot (`slotChrome: 'template'`) with the field row prefixes `labels`. */\nfunction templateSlotChrome(labels: ComposeBlockTypeOptions['slotFieldLabels'] = {}): BlockTypeChrome {\n const label = { je: 'je', optional: 'optional', selector: 'Auswahl', anweisung: 'KI', ...labels }\n return {\n ...PLAIN_SLOT_CHROME,\n tone: TEMPLATE_SLOT_TONE,\n collapsible: true,\n fields: [\n { key: 'identifier' },\n { key: 'je', label: label.je },\n { key: 'optional', label: label.optional, flag: 'ja' },\n { key: 'selector', label: label.selector },\n { key: 'anweisung', label: label.anweisung },\n ],\n }\n}\n\n/** `ja`/`nein` as a select: the library has no boolean field; unset means the per-field default (`optional` nein, `heading` ja). */\nconst JA_NEIN = [\n { value: 'ja', label: 'Ja' },\n { value: 'nein', label: 'Nein' },\n]\n\nconst BASKET = { id: 'basket' }\nconst TITLE_HIDDEN = { id: 'titleHidden' }\n\nconst DERIVATION: PropertyDefinition = { key: 'derivation', label: 'Herkunft', type: 'derivation' }\n\n/**\n * The slot type's AI brief. It has no `contentTemplate`: the editor leaves a `{key}` placeholder of the template literally in\n * the prompt when the property is empty, so a template of `{skelett}` would reach the model as the text „{skelett}“ for every\n * slot without a skeleton. The skeleton travels as an `aiBlockData` entry instead (`compose-material`), only when it is set.\n */\nconst SLOT_AI = {\n purpose: 'Ein Abschnitt eines Dokuments: sachlicher Fließtext, der nur aus den genannten Fakten entsteht; Beispiele sind Stilvorlage.',\n instructions: 'Schreibe sachlichen Fließtext nur aus den genannten Fakten. Beispiele sind Stilvorlage für Aufbau und Ton; erfinde nichts hinzu.',\n instructionsField: 'anweisung',\n}\n\n/** The four compose Block types. */\nexport function composeBlockTypes(o: ComposeBlockTypeOptions = {}): BlockType[] {\n const label = { slot: 'Abschnitt', source: 'Quelle', fact: 'Fakt', example: 'Beispiel', ...o.labels }\n const options = (o.slotOptions ?? []).filter(option => option.value !== '')\n\n // A select cannot hold an empty option (the Select component refuses an empty value): the placeholder shows while `slot` is unset.\n // A `slot` that is no longer in the template stays in the document but is not selectable.\n const slotTarget: PropertyDefinition = options.length > 0\n ? { key: 'slot', label: 'Slot (Bezeichner im Template)', type: 'select', options }\n : { key: 'slot', label: 'Slot (Bezeichner im Template)', type: 'text' }\n\n return [\n {\n id: KIND.slot,\n label: label.slot,\n icon: 'i-lucide-file-text',\n titlePlaceholder: label.slot,\n descriptionPlaceholder: 'Beschreibung: Zuordnung, Stichpunkte, Zweck der Prosa',\n capabilities: [\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n { key: 'identifier', label: 'Bezeichner (z. B. therapie/pflege)', type: 'text' },\n { key: 'optional', label: 'Optional (Slot darf in der Instanz entfallen)', type: 'select', options: JA_NEIN },\n { key: 'heading', label: 'Überschrift im Dokument', type: 'select', options: JA_NEIN },\n { key: 'anweisung', label: 'KI-Anweisung (ersetzt die Vorgabe)', type: 'textarea' },\n { key: 'skelett', label: 'Skelett (Zieltext als Markdown, {{…}} = freie Stellen)', type: 'textarea' },\n { key: 'selector', label: 'Auswahl (#muss ~bevorzugt -nicht)', type: 'text' },\n { key: 'je', label: 'Je Fall-Tag wiederholen (Facette, z. B. icd)', type: 'text' },\n ],\n },\n },\n { id: 'ai-brief', config: { ...SLOT_AI } },\n ],\n chrome: o.slotChrome === 'template' ? templateSlotChrome(o.slotFieldLabels) : PLAIN_SLOT_CHROME,\n },\n {\n id: KIND.source,\n label: label.source,\n icon: 'i-lucide-file-search',\n titlePlaceholder: 'Titel der Quelle',\n descriptionPlaceholder: '',\n capabilities: [\n { id: 'container', config: {} },\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n { key: 'sourceType', label: 'Quelltyp', type: 'text' },\n { key: 'date', label: 'Datum', type: 'text' },\n ],\n },\n },\n ],\n chrome: { tags: false },\n },\n {\n id: KIND.fact,\n label: label.fact,\n icon: 'i-lucide-lightbulb',\n titlePlaceholder: '',\n descriptionPlaceholder: '',\n capabilities: [BASKET, TITLE_HIDDEN, { id: 'properties', config: { definitions: [slotTarget, DERIVATION] } }],\n chrome: { tags: false },\n },\n {\n id: KIND.example,\n label: label.example,\n icon: 'i-lucide-quote',\n titlePlaceholder: label.example,\n descriptionPlaceholder: '',\n capabilities: [\n BASKET,\n TITLE_HIDDEN,\n {\n id: 'properties',\n config: {\n definitions: [\n slotTarget,\n { key: 'status', label: 'Status', type: 'select', options: [\n { value: 'vorschlag', label: 'Vorschlag' },\n { value: 'geprüft', label: 'Geprüft' },\n ] },\n // Written by the split, shown as the proposal chip of the title row (`composeFeatures`) while `status` is `vorschlag`;\n // the properties form has no read-only field, so both are hidden from it and removed when the proposal is accepted.\n { key: 'confidence', label: 'Sicherheit', type: 'text', hidden: true },\n { key: 'origin', label: 'Herkunft der Zuordnung', type: 'text', hidden: true },\n DERIVATION,\n ],\n },\n },\n ],\n },\n ]\n}\n\n/** `blockType` with the `ai-brief` capability's purpose (and, when given, instructions) replaced; the other brief settings stay. Pure. */\nexport function withAiBrief(blockType: BlockType, brief: { purpose: string; instructions?: string }): BlockType {\n const config = (existing: unknown) => ({\n ...(existing && typeof existing === 'object' ? existing : {}),\n purpose: brief.purpose,\n ...(brief.instructions !== undefined && { instructions: brief.instructions }),\n })\n const listed = blockType.capabilities.some(capability => capability.id === 'ai-brief')\n return {\n ...blockType,\n capabilities: listed\n ? blockType.capabilities.map(capability => (capability.id === 'ai-brief' ? { ...capability, config: config(capability.config) } : capability))\n : [...blockType.capabilities, { id: 'ai-brief', config: config(undefined) }],\n }\n}\n\n/** `blockType` with `definitions` added to its `properties` capability; a definition whose key exists replaces it in place. Pure. */\nexport function withProperties(blockType: BlockType, definitions: readonly PropertyDefinition[]): BlockType {\n const merge = (existing: unknown): { definitions: PropertyDefinition[] } => {\n const current = (existing as { definitions?: readonly PropertyDefinition[] } | undefined)?.definitions ?? []\n const replaced = current.map(definition => definitions.find(added => added.key === definition.key) ?? definition)\n const added = definitions.filter(definition => !current.some(known => known.key === definition.key))\n return { definitions: [...replaced, ...added] }\n }\n const listed = blockType.capabilities.some(capability => capability.id === 'properties')\n return {\n ...blockType,\n capabilities: listed\n ? blockType.capabilities.map(capability => (capability.id === 'properties' ? { ...capability, config: merge(capability.config) } : capability))\n : [...blockType.capabilities, { id: 'properties', config: merge(undefined) }],\n }\n}\n"],"mappings":";;;AA8BA,MAAa,qBAAqB;;;;;AAMlC,MAAM,oBAAqC,EAAE,SAAS,OAAO;;AAG7D,SAAS,mBAAmB,SAAqD,CAAC,GAAoB;CACpG,MAAM,QAAQ;EAAE,IAAI;EAAM,UAAU;EAAY,UAAU;EAAW,WAAW;EAAM,GAAG;CAAO;CAChG,OAAO;EACL,GAAG;EACH,MAAM;EACN,aAAa;EACb,QAAQ;GACN,EAAE,KAAK,aAAa;GACpB;IAAE,KAAK;IAAM,OAAO,MAAM;GAAG;GAC7B;IAAE,KAAK;IAAY,OAAO,MAAM;IAAU,MAAM;GAAK;GACrD;IAAE,KAAK;IAAY,OAAO,MAAM;GAAS;GACzC;IAAE,KAAK;IAAa,OAAO,MAAM;GAAU;EAC7C;CACF;AACF;;AAGA,MAAM,UAAU,CACd;CAAE,OAAO;CAAM,OAAO;AAAK,GAC3B;CAAE,OAAO;CAAQ,OAAO;AAAO,CACjC;AAEA,MAAM,SAAS,EAAE,IAAI,SAAS;AAC9B,MAAM,eAAe,EAAE,IAAI,cAAc;AAEzC,MAAM,aAAiC;CAAE,KAAK;CAAc,OAAO;CAAY,MAAM;AAAa;;;;;;AAOlG,MAAM,UAAU;CACd,SAAS;CACT,cAAc;CACd,mBAAmB;AACrB;;AAGA,SAAgB,kBAAkB,IAA6B,CAAC,GAAgB;CAC9E,MAAM,QAAQ;EAAE,MAAM;EAAa,QAAQ;EAAU,MAAM;EAAQ,SAAS;EAAY,GAAG,EAAE;CAAO;CACpG,MAAM,WAAW,EAAE,eAAe,CAAC,EAAA,CAAG,QAAO,WAAU,OAAO,UAAU,EAAE;CAI1E,MAAM,aAAiC,QAAQ,SAAS,IACpD;EAAE,KAAK;EAAQ,OAAO;EAAiC,MAAM;EAAU;CAAQ,IAC/E;EAAE,KAAK;EAAQ,OAAO;EAAiC,MAAM;CAAO;CAExE,OAAO;EACL;GACE,IAAI,KAAK;GACT,OAAO,MAAM;GACb,MAAM;GACN,kBAAkB,MAAM;GACxB,wBAAwB;GACxB,cAAc;IACZ;IACA;IACA;KACE,IAAI;KACJ,QAAQ,EACN,aAAa;MACX;OAAE,KAAK;OAAc,OAAO;OAAsC,MAAM;MAAO;MAC/E;OAAE,KAAK;OAAY,OAAO;OAAiD,MAAM;OAAU,SAAS;MAAQ;MAC5G;OAAE,KAAK;OAAW,OAAO;OAA2B,MAAM;OAAU,SAAS;MAAQ;MACrF;OAAE,KAAK;OAAa,OAAO;OAAsC,MAAM;MAAW;MAClF;OAAE,KAAK;OAAW,OAAO;OAA0D,MAAM;MAAW;MACpG;OAAE,KAAK;OAAY,OAAO;OAAqC,MAAM;MAAO;MAC5E;OAAE,KAAK;OAAM,OAAO;OAAgD,MAAM;MAAO;KACnF,EACF;IACF;IACA;KAAE,IAAI;KAAY,QAAQ,EAAE,GAAG,QAAQ;IAAE;GAC3C;GACA,QAAQ,EAAE,eAAe,aAAa,mBAAmB,EAAE,eAAe,IAAI;EAChF;EACA;GACE,IAAI,KAAK;GACT,OAAO,MAAM;GACb,MAAM;GACN,kBAAkB;GAClB,wBAAwB;GACxB,cAAc;IACZ;KAAE,IAAI;KAAa,QAAQ,CAAC;IAAE;IAC9B;IACA;IACA;KACE,IAAI;KACJ,QAAQ,EACN,aAAa,CACX;MAAE,KAAK;MAAc,OAAO;MAAY,MAAM;KAAO,GACrD;MAAE,KAAK;MAAQ,OAAO;MAAS,MAAM;KAAO,CAC9C,EACF;IACF;GACF;GACA,QAAQ,EAAE,MAAM,MAAM;EACxB;EACA;GACE,IAAI,KAAK;GACT,OAAO,MAAM;GACb,MAAM;GACN,kBAAkB;GAClB,wBAAwB;GACxB,cAAc;IAAC;IAAQ;IAAc;KAAE,IAAI;KAAc,QAAQ,EAAE,aAAa,CAAC,YAAY,UAAU,EAAE;IAAE;GAAC;GAC5G,QAAQ,EAAE,MAAM,MAAM;EACxB;EACA;GACE,IAAI,KAAK;GACT,OAAO,MAAM;GACb,MAAM;GACN,kBAAkB,MAAM;GACxB,wBAAwB;GACxB,cAAc;IACZ;IACA;IACA;KACE,IAAI;KACJ,QAAQ,EACN,aAAa;MACX;MACA;OAAE,KAAK;OAAU,OAAO;OAAU,MAAM;OAAU,SAAS,CACzD;QAAE,OAAO;QAAa,OAAO;OAAY,GACzC;QAAE,OAAO;QAAW,OAAO;OAAU,CACvC;MAAE;MAGF;OAAE,KAAK;OAAc,OAAO;OAAc,MAAM;OAAQ,QAAQ;MAAK;MACrE;OAAE,KAAK;OAAU,OAAO;OAA0B,MAAM;OAAQ,QAAQ;MAAK;MAC7E;KACF,EACF;IACF;GACF;EACF;CACF;AACF;;AAGA,SAAgB,YAAY,WAAsB,OAA8D;CAC9G,MAAM,UAAU,cAAuB;EACrC,GAAI,YAAY,OAAO,aAAa,WAAW,WAAW,CAAC;EAC3D,SAAS,MAAM;EACf,GAAI,MAAM,iBAAiB,KAAA,KAAa,EAAE,cAAc,MAAM,aAAa;CAC7E;CACA,MAAM,SAAS,UAAU,aAAa,MAAK,eAAc,WAAW,OAAO,UAAU;CACrF,OAAO;EACL,GAAG;EACH,cAAc,SACV,UAAU,aAAa,KAAI,eAAe,WAAW,OAAO,aAAa;GAAE,GAAG;GAAY,QAAQ,OAAO,WAAW,MAAM;EAAE,IAAI,UAAW,IAC3I,CAAC,GAAG,UAAU,cAAc;GAAE,IAAI;GAAY,QAAQ,OAAO,KAAA,CAAS;EAAE,CAAC;CAC/E;AACF;;AAGA,SAAgB,eAAe,WAAsB,aAAuD;CAC1G,MAAM,SAAS,aAA6D;EAC1E,MAAM,UAAW,UAA0E,eAAe,CAAC;EAC3G,MAAM,WAAW,QAAQ,KAAI,eAAc,YAAY,MAAK,UAAS,MAAM,QAAQ,WAAW,GAAG,KAAK,UAAU;EAChH,MAAM,QAAQ,YAAY,QAAO,eAAc,CAAC,QAAQ,MAAK,UAAS,MAAM,QAAQ,WAAW,GAAG,CAAC;EACnG,OAAO,EAAE,aAAa,CAAC,GAAG,UAAU,GAAG,KAAK,EAAE;CAChD;CACA,MAAM,SAAS,UAAU,aAAa,MAAK,eAAc,WAAW,OAAO,YAAY;CACvF,OAAO;EACL,GAAG;EACH,cAAc,SACV,UAAU,aAAa,KAAI,eAAe,WAAW,OAAO,eAAe;GAAE,GAAG;GAAY,QAAQ,MAAM,WAAW,MAAM;EAAE,IAAI,UAAW,IAC5I,CAAC,GAAG,UAAU,cAAc;GAAE,IAAI;GAAc,QAAQ,MAAM,KAAA,CAAS;EAAE,CAAC;CAChF;AACF"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { ComposeBlockTypeOptions, ComposeKind, composeBlockTypes, withAiBrief, withProperties } from "./block-types.js";
1
+ import { ComposeBlockTypeOptions, ComposeKind, TEMPLATE_SLOT_TONE, composeBlockTypes, withAiBrief, withProperties } from "./block-types.js";
2
2
  import { KIND } from "@skaile/forge-compose/documents";
3
- export { type ComposeBlockTypeOptions, type ComposeKind, KIND, composeBlockTypes, withAiBrief, withProperties };
3
+ export { type ComposeBlockTypeOptions, type ComposeKind, KIND, TEMPLATE_SLOT_TONE, composeBlockTypes, withAiBrief, withProperties };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { composeBlockTypes, withAiBrief, withProperties } from "./block-types.js";
1
+ import { TEMPLATE_SLOT_TONE, composeBlockTypes, withAiBrief, withProperties } from "./block-types.js";
2
2
  import { KIND } from "@skaile/forge-compose/documents";
3
- export { KIND, composeBlockTypes, withAiBrief, withProperties };
3
+ export { KIND, TEMPLATE_SLOT_TONE, composeBlockTypes, withAiBrief, withProperties };
@@ -71,6 +71,7 @@ const emptySlot = (identifier) => ({
71
71
  tags: [],
72
72
  meta: {},
73
73
  chapterId: null,
74
+ chapterTitle: "",
74
75
  node: {}
75
76
  });
76
77
  /** The model name recorded in the derivation of a proposal: what decided it. */
@@ -1 +1 @@
1
- {"version":3,"file":"operations.js","names":[],"sources":["../../src/server/operations.ts"],"sourcesContent":["// The domain operations of the compose editor: material, split, facts, example, instance and classify. One function each,\n// framework-free: routes, forge-app-actions definitions and chat tools are projections of them. Authorization is the host's\n// (its action `authorize` or route check runs before); the operations trust `user` for port reads only. Model access goes\n// through the host's `generate`; every failure the caller can act on is a `ComposeError`.\nimport type { JSONContent } from '@tiptap/core'\nimport type { EditorPorts } from '@skaile/forge-editor/server'\nimport { gate } from '@skaile/forge-compose'\nimport type { CaseProfile, Generate, Hierarchy, Selector } from '@skaile/forge-compose'\nimport {\n KIND, META, METHOD, STATUS, TYPED_BLOCK, blockIdOf, bodyOf, bodyText, compositeMeta, compositeTags, compositeTitle,\n compositesOfKind, exampleFromProposal, exampleFromSlot, examplesOf, factNode, factsOf, instanceFromTemplate, kindOf,\n materialFor, plainText, slotsOf, templateSlotOf,\n} from '@skaile/forge-compose/documents'\nimport type { DocNode, ExampleBlock, FactBlock, MaterialTarget, PreviousDocument, SlotBlock } from '@skaile/forge-compose/documents'\nimport {\n defaultPipeline, embeddingScorer, lengthScorer, proposals, scoreAll, segment, tagFitScorer,\n} from '@skaile/forge-compose/segment'\nimport type { Decision, Embedder, RuleSpec, Scorer, SegmentPipeline, Splitter } from '@skaile/forge-compose/segment'\nimport { catalogOf } from './catalog'\nimport { ComposeError } from './errors'\nimport { buildFactsPrompt, FactsReplySchema, parseFactsReply } from './facts-prompt'\nimport type { CatalogEntry, FactDraft, FactsReply } from './facts-prompt'\nimport type { ComposeInput, ComposeOutput } from './inputs'\n\n/** What the host knows about the case a document belongs to. */\nexport interface ComposeContext {\n case: CaseProfile\n /** Document that holds the slots. */\n templateId: string\n /** Documents whose `fact`/`source` blocks belong to the case. */\n sourceIds: string[]\n /** Documents searched for `example` blocks (style only). */\n exampleIds: string[] | 'all'\n /** Documents searched for snippet blocks (`snippetKind`: content to adopt). Default none. */\n snippetIds?: string[]\n /**\n * Earlier documents of the same person as the case, newest first: the block of the same slot and element in the first of\n * them that has one is the previous version (`Material.previous`, „Vorfassung – fortschreiben, nicht übernehmen“). Default\n * none. Never a person key: document ids only.\n */\n previousIds?: string[]\n}\n\nexport interface ComposeOptions {\n /** `documents` is required (`putDocument` for `example` and `instance`); `embeddings` is reused as the pipeline's and the scorers' `Embedder` when present. */\n ports: Pick<EditorPorts, 'documents'> & Partial<Pick<EditorPorts, 'embeddings' | 'index'>>\n /** The host's model seam; every model call of the operations goes through it. */\n generate: Generate\n /** The case of a document (a case document, a source of a case, an instance); `null` = unknown document, answered as `ComposeError(404)`. */\n contextOf(documentId: string, user: object): Promise<ComposeContext | null>\n /** The document „Als Beispiel übernehmen“ appends to; created when missing. */\n exampleLibraryId: string\n hierarchy?: Hierarchy\n /**\n * Merged over `defaultPipeline({ embedder: ports.embeddings, generate: llm ? generate : undefined, rules, tagRules })`: a\n * given `splitter`, `merge`, `slotLabelers`, `tagLabelers` or `resolve` replaces the default one (a host that passes\n * `slotLabelers` replaces the default list entirely; an explicit `merge: undefined` switches merging off).\n */\n segmentation?: Partial<SegmentPipeline> & {\n rules?: RuleSpec[]\n tagRules?: RuleSpec[]\n /** Default `true`: `llmLabeler({ generate })` as the last slot and tag labeler. */\n llm?: boolean\n /** The splitter for one source (by its `meta`, e.g. `sourceType`); `null` = the pipeline's. */\n splitterFor?(source: { meta: Record<string, unknown> }): Splitter | null\n }\n /**\n * Extra scorers of the `inhalt` signal, added to the built-in signals of `material` (`tags`, `element`, `fall`, `person`,\n * length); `compose_examples` ranks with `[tagFitScorer(hierarchy), ...scorers, lengthScorer]`. Default `[embeddingScorer]`,\n * which scores 0 without `ports.embeddings`.\n */\n scorers?: Scorer[]\n /** Block kind of the snippets in `snippetIds`; default `'snippet'`. */\n snippetKind?: string\n /**\n * Whether `documentId` belongs to the same person as the case of `caseDocumentId` (the `person` signal of `material`,\n * asked for each origin document of the library entries); absent = the signal is 0.\n */\n sameOwner?: (documentId: string, caseDocumentId: string, user: object) => Promise<boolean> | boolean\n /** System message of the `facts` operation; default `FACTS_SYSTEM` (domain-neutral). A host with a domain passes its own. */\n factsSystem?: string\n}\n\nexport interface ComposeOperations {\n /**\n * Facts, the previous version, ranked snippets and ranked examples for the `slot` block `block` of `document`\n * (`materialFor`): the block's identifier picks facts and library entries, its `meta.element` ranks by the `element` signal,\n * `document` by `fall`, the template variant it was made from (`templateSlotOf`) is the slot the injected scorers see; the\n * previous version comes from `ComposeContext.previousIds`.\n */\n material(input: ComposeInput<'material'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'material'>>\n split(input: ComposeInput<'split'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'split'>>\n facts(input: ComposeInput<'facts'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'facts'>>\n example(input: ComposeInput<'example'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'example'>>\n instance(input: ComposeInput<'instance'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'instance'>>\n /** Which slot and tags a text belongs to, by the configured labelers. Not an operation id: chat only, read only. */\n classify(input: { document: string; text: string }, user: object, signal?: AbortSignal): Promise<Decision>\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// Source text\n\nconst BOLD_HEADING_CHARS = 120\n\nconst isBold = (node: DocNode): boolean =>\n Array.isArray(node.marks) && node.marks.some(mark => typeof mark === 'object' && mark !== null && 'type' in mark && mark.type === 'bold')\n\n/** A one-line paragraph that is bold throughout: how sources written by the import mark their section headings. */\nfunction isHeadingLine(node: DocNode): boolean {\n const content = node.content ?? []\n return content.length > 0 && content.every(child => child.type === 'text' && isBold(child)) && plainText(node).trim().length <= BOLD_HEADING_CHARS\n}\n\nfunction blockMarkdown(node: DocNode): string {\n switch (node.type) {\n case 'heading': {\n const text = plainText(node).trim()\n const level = Math.min(3, Math.max(1, Number(node.attrs?.level) || 1))\n return text ? `${'#'.repeat(level)} ${text}` : ''\n }\n case 'paragraph': {\n const text = plainText(node).trim()\n return text && isHeadingLine(node) ? `## ${text}` : text\n }\n case 'bulletList':\n case 'orderedList':\n return (node.content ?? [])\n .map((item, index) => ({ text: plainText(item).replace(/\\s*\\n+\\s*/g, ' ').trim(), index }))\n .filter(item => item.text)\n .map(item => `${node.type === 'orderedList' ? `${item.index + 1}.` : '-'} ${item.text}`)\n .join('\\n')\n case TYPED_BLOCK:\n return markdownOf(bodyOf(node))\n default:\n return plainText(node).trim()\n }\n}\n\n/**\n * Blocks as the Markdown the splitters read: `heading` nodes and bold one-line paragraphs become `#` lines (so\n * `structuralSplitter` sees the sections), lists become list lines, everything else plain paragraphs separated by a blank line.\n */\nfunction markdownOf(nodes: readonly DocNode[]): string {\n return nodes.map(blockMarkdown).filter(text => text.trim()).join('\\n\\n')\n}\n\nasync function sha256Hex(text: string): Promise<string> {\n const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(text))\n return [...new Uint8Array(digest)].map(byte => byte.toString(16).padStart(2, '0')).join('')\n}\n\n/** The typed block with `id`, wherever it nests; `null` when there is none. */\nfunction findBlockNode(doc: DocNode, id: string): DocNode | null {\n const walk = (nodes: readonly DocNode[] | undefined): DocNode | null => {\n for (const node of nodes ?? []) {\n if (node.type === TYPED_BLOCK && blockIdOf(node) === id) return node\n const inner = walk(node.content)\n if (inner) return inner\n }\n return null\n }\n return walk(doc.content)\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// Support for the chat tools\n\n/** What `composeTools` reads besides the public operations; `composeOperations` registers it for the object it returns. */\nexport interface ComposeToolSupport {\n /** The content of a document as stored, `null` = no such document. */\n load(id: string): Promise<DocNode | null>\n /** The accepted examples of the case `document` belongs to, gated by `tags`, ranked by tag fit, the configured scorers and length, best first. */\n examples(\n input: { document: string; slot?: string; tags?: string[]; query?: string; limit?: number },\n user: object,\n signal?: AbortSignal,\n ): Promise<{ slot: string; tags: string[]; text: string; score: number; parts: Record<string, number> }[]>\n}\n\nconst support = new WeakMap<ComposeOperations, ComposeToolSupport>()\n\n/** The tool support of `ops`; `ops` must be what `composeOperations` returned. */\nexport function composeToolSupportOf(ops: ComposeOperations): ComposeToolSupport {\n const found = support.get(ops)\n if (!found) throw new Error('@skaile/forge-editor-compose: composeTools needs the object composeOperations returned')\n return found\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n\nconst asJson = (doc: DocNode): JSONContent => doc as JSONContent // DocNode is the structural twin of JSONContent\n\nconst emptySlot = (identifier: string): SlotBlock => ({\n id: '', identifier, title: identifier, description: '', tags: [], meta: {}, chapterId: null, node: {},\n})\n\n/** The model name recorded in the derivation of a proposal: what decided it. */\nconst modelOf = (origin: Decision['origin'], embedder: Embedder | undefined): string =>\n origin === 'embedding' && embedder ? embedder.model : (origin ?? 'none')\n\nexport function composeOperations(o: ComposeOptions): ComposeOperations {\n const { documents } = o.ports\n const h = o.hierarchy\n const scorers = o.scorers ?? [embeddingScorer]\n const snippetKind = o.snippetKind ?? 'snippet'\n\n const embedderFor = (user: object): Embedder | undefined => {\n const embeddings = o.ports.embeddings\n return embeddings && { model: embeddings.model, dim: embeddings.dim, embed: texts => embeddings.embed(texts, user) }\n }\n\n async function contextFor(documentId: string, user: object): Promise<ComposeContext> {\n const context = await o.contextOf(documentId, user)\n if (!context) throw new ComposeError(404, `Für das Dokument „${documentId}“ gibt es keinen Fall.`)\n return context\n }\n\n async function documentOf(id: string): Promise<DocNode> {\n const content = await documents.loadContent(id)\n if (!content) throw new ComposeError(404, `Das Dokument „${id}“ existiert nicht.`)\n return content\n }\n\n async function factsOfCase(context: ComposeContext): Promise<FactBlock[]> {\n const lists = await Promise.all(context.sourceIds.map(async id => factsOf(await documents.loadContent(id))))\n return lists.flat()\n }\n\n async function examplesOfCase(context: ComposeContext): Promise<ExampleBlock[]> {\n if (context.exampleIds === 'all') {\n const all = await documents.listContents()\n return all.flatMap(({ id, content }) => examplesOf(content, id))\n }\n const lists = await Promise.all(context.exampleIds.map(async id => examplesOf(await documents.loadContent(id), id)))\n return lists.flat()\n }\n\n async function snippetsOfCase(context: ComposeContext): Promise<ExampleBlock[]> {\n const lists = await Promise.all((context.snippetIds ?? []).map(async id => examplesOf(await documents.loadContent(id), id, snippetKind)))\n return lists.flat()\n }\n\n /** `o.sameOwner` asked once per origin document of `entries` for the case of `caseDocumentId`, as the synchronous predicate `materialFor` takes. */\n async function ownerCheckOf(\n entries: readonly ExampleBlock[],\n caseDocumentId: string,\n user: object,\n ): Promise<((documentId: string) => boolean) | undefined> {\n const sameOwner = o.sameOwner\n if (!sameOwner) return undefined\n const ids = [...new Set(entries.flatMap(entry => (entry.origin ? [entry.origin.document] : [])))]\n const answers = await Promise.all(ids.map(id => sameOwner(id, caseDocumentId, user)))\n const owned = new Set(ids.filter((_, index) => answers[index]))\n return documentId => owned.has(documentId)\n }\n\n /** The earlier documents of the case (`previousIds`) with their content, in the given order. */\n async function previousOfCase(context: ComposeContext): Promise<PreviousDocument[]> {\n return Promise.all((context.previousIds ?? []).map(async id => ({ id, content: await documents.loadContent(id) })))\n }\n\n /** The block `blockId` of `documentId` as a typed block of `kind`; `ComposeError(404)` otherwise. */\n async function blockOf(documentId: string, blockId: string, kind: string): Promise<DocNode> {\n const node = findBlockNode(await documentOf(documentId), blockId)\n if (!node) throw new ComposeError(404, `Der Block „${blockId}“ existiert im Dokument „${documentId}“ nicht.`)\n if (kindOf(node) !== kind) throw new ComposeError(404, `Der Block „${blockId}“ ist kein Block vom Typ „${kind}“.`)\n return node\n }\n\n /** The pipeline: the default of the host's embedder and `generate`, the `segmentation` fields merged over it. */\n function pipelineOf(embedder: Embedder | undefined, source?: { meta: Record<string, unknown> }): SegmentPipeline {\n const { rules, tagRules, llm, splitterFor, ...override } = o.segmentation ?? {}\n const base = defaultPipeline({\n ...(embedder && { embedder }),\n ...(llm !== false && { generate: o.generate }),\n ...(rules && { rules }),\n ...(tagRules && { tagRules }),\n })\n const merge = 'merge' in override ? override.merge : base.merge\n const resolveOptions = 'resolve' in override ? override.resolve : base.resolve\n return {\n splitter: (source && splitterFor?.(source)) || override.splitter || base.splitter,\n slotLabelers: override.slotLabelers ?? base.slotLabelers,\n tagLabelers: override.tagLabelers ?? base.tagLabelers,\n ...(merge && { merge }),\n ...(resolveOptions && { resolve: resolveOptions }),\n }\n }\n\n /** `example` appends read-modify-write one after another, so two adoptions at once keep both. */\n let appending: Promise<unknown> = Promise.resolve()\n const exclusive = <T>(run: () => Promise<T>): Promise<T> => {\n const result = appending.then(run)\n appending = result.catch(() => undefined)\n return result\n }\n\n const ops: ComposeOperations = {\n async material({ document, block }, user, signal) {\n const context = await contextFor(document, user)\n const node = await blockOf(document, block, KIND.slot)\n const [facts, examples, snippets, previous, template] = await Promise.all([\n factsOfCase(context),\n examplesOfCase(context),\n snippetsOfCase(context),\n previousOfCase(context),\n documents.loadContent(context.templateId),\n ])\n const meta = compositeMeta(node)\n const identifier = meta[META.identifier]\n const rawElement = meta[META.element]\n const element = typeof rawElement === 'string' ? rawElement.trim() : ''\n const slotBlock = template ? templateSlotOf(node, template) : undefined\n const target: MaterialTarget = {\n identifier: typeof identifier === 'string' ? identifier : '',\n ...(element ? { element } : {}),\n tags: compositeTags(node),\n document,\n ...(slotBlock && { slot: slotBlock }),\n }\n const embedder = embedderFor(user)\n const sameOwner = await ownerCheckOf([...examples, ...snippets], document, user)\n return materialFor(target, context.case, facts, { examples, snippets, previous }, {\n scorers,\n ...(h && { h }),\n ...(sameOwner && { sameOwner }),\n ctx: { ...(embedder && { embedder }), ...(signal && { signal }) },\n })\n },\n\n async split({ document, block }, user, signal) {\n const context = await contextFor(document, user)\n const source = await blockOf(document, block, KIND.source)\n const body = markdownOf(bodyOf(source))\n if (!body.trim()) throw new ComposeError(409, 'Die Quelle enthält keinen Text.')\n const [template, examples] = await Promise.all([documents.loadContent(context.templateId), examplesOfCase(context)])\n const embedder = embedderFor(user)\n const { units, decisions } = await segment(\n body,\n pipelineOf(embedder, { meta: compositeMeta(source) }),\n catalogOf(template, examples),\n { ...(embedder && { embedder }), ...(signal && { signal }) },\n )\n const inputHash = await sha256Hex(body)\n const now = new Date().toISOString()\n // A unit without slot stays a proposal (slot empty): nothing of the corpus is lost, the person assigns it.\n const nodes = proposals(units, decisions).map(proposal =>\n exampleFromProposal(proposal, {\n sourceBlockId: block,\n sourceDocumentId: document,\n caseTags: context.case.tags,\n model: modelOf(proposal.origin, embedder),\n now,\n inputHash,\n }),\n )\n return { nodes }\n },\n\n async facts({ document, block }, user, signal) {\n const context = await contextFor(document, user)\n const source = await blockOf(document, block, KIND.source)\n const body = markdownOf(bodyOf(source))\n if (!body.trim()) throw new ComposeError(409, 'Die Quelle enthält keinen Text.')\n const template = await documentOf(context.templateId)\n\n const chapters = new Map(compositesOfKind(template, KIND.section).map(section => [blockIdOf(section), compositeTitle(section)]))\n const seen = new Set<string>()\n const catalog: CatalogEntry[] = []\n for (const slot of slotsOf(template)) {\n if (!slot.identifier || seen.has(slot.identifier)) continue\n seen.add(slot.identifier)\n catalog.push({\n identifier: slot.identifier,\n chapter: (slot.chapterId && chapters.get(slot.chapterId)) || '',\n title: slot.title,\n description: slot.description,\n tags: slot.tags,\n })\n }\n\n const { system, prompt } = buildFactsPrompt(catalog, body, o.factsSystem)\n const reply = await o.generate<FactsReply>({ stage: 'facts', system, prompt, schema: FactsReplySchema, ...(signal && { signal }) })\n let drafts: FactDraft[]\n try {\n drafts = parseFactsReply(reply, seen)\n }\n catch (error) {\n throw new ComposeError(422, error instanceof Error ? error.message : 'Antwort des Modells unbrauchbar')\n }\n const inputHash = await sha256Hex(body)\n const now = new Date().toISOString()\n // The host's `generate` knows the model and logs it; it is not known here.\n const nodes = drafts.map(draft =>\n factNode({\n text: draft.text,\n slot: draft.slot,\n derivation: { from: { document, block }, method: METHOD.aiFact, createdAt: now, inputHash },\n }),\n )\n return { nodes }\n },\n\n async example({ document, block }, user) {\n const context = await contextFor(document, user)\n const slotNode = await blockOf(document, block, KIND.slot)\n const slot = slotsOf({ type: 'doc', content: [slotNode] })[0]\n if (!slot || !bodyText(slotNode).trim()) throw new ComposeError(409, 'Der Block hat keinen Text, der als Beispiel übernommen werden könnte.')\n if (!documents.putDocument) throw new ComposeError(503, 'Der Dokumentspeicher kann keine Dokumente schreiben.')\n const put = documents.putDocument.bind(documents)\n const node = exampleFromSlot(slot, {\n sourceDocumentId: document,\n caseTags: context.case.tags,\n now: new Date().toISOString(),\n })\n await exclusive(async () => {\n const library = (await documents.loadContent(o.exampleLibraryId)) ?? { type: 'doc', content: [] }\n await put(o.exampleLibraryId, asJson({ ...library, content: [...(library.content ?? []), node] }))\n })\n return { libraryId: o.exampleLibraryId, blockId: blockIdOf(node) }\n },\n\n async instance({ templateId, instanceId, force }, user) {\n if (!force && (await documents.loadContent(instanceId))) throw new ComposeError(409, 'Instanz existiert')\n const context = await contextFor(instanceId, user)\n const template = await documentOf(templateId)\n if (!documents.putDocument) throw new ComposeError(503, 'Der Dokumentspeicher kann keine Dokumente schreiben.')\n const { doc, chosen, dropped, unresolved, elements } = instanceFromTemplate(template, context.case, {\n templateId,\n now: new Date().toISOString(),\n ...(h && { h }),\n })\n await documents.putDocument(instanceId, asJson(doc))\n return { chosen, dropped, unresolved, elements }\n },\n\n async classify({ document, text }, user, signal) {\n if (!text.trim()) throw new ComposeError(400, 'Der Text ist leer.')\n const context = await contextFor(document, user)\n const [template, examples] = await Promise.all([documents.loadContent(context.templateId), examplesOfCase(context)])\n const embedder = embedderFor(user)\n // The whole text is one unit: no splitting, no merging, then the configured labelers and the resolver as in `split`.\n const single: Splitter = { name: 'single', split: () => [{ text, start: 0, end: text.length, level: 0, path: [] }] }\n const { merge: _merge, ...pipeline } = pipelineOf(embedder)\n const { decisions } = await segment(\n text,\n { ...pipeline, splitter: single },\n catalogOf(template, examples),\n { ...(embedder && { embedder }), ...(signal && { signal }) },\n )\n return decisions[0] ?? { unit: 0, slot: null, tags: [], confidence: 0, origin: null }\n },\n }\n\n support.set(ops, {\n load: id => documents.loadContent(id),\n\n async examples({ document, slot, tags, query, limit }, user, signal) {\n const context = await contextFor(document, user)\n const selector: Selector = { require: tags ?? [], prefer: [], exclude: [] }\n const candidates = (await examplesOfCase(context)).filter(\n example => example.status !== STATUS.proposal && (!slot || example.slot === slot) && gate(selector, example.tags, h),\n )\n if (candidates.length === 0) return []\n const template = await documents.loadContent(context.templateId)\n const slotBlock = slotsOf(template).find(candidate => candidate.identifier === slot) ?? emptySlot(slot ?? '')\n const facts: FactBlock[] = query?.trim() ? [{ id: '', slot: slotBlock.identifier, text: query.trim(), sourceTitle: '' }] : []\n // Asked for explicit tags, the case to measure against is those tags.\n const profile: CaseProfile = tags?.length ? { ...context.case, tags } : context.case\n const embedder = embedderFor(user)\n const scored = await scoreAll(candidates, { case: profile, slot: slotBlock, facts }, [tagFitScorer(h), ...scorers, lengthScorer], {\n ...(embedder && { embedder }),\n ...(signal && { signal }),\n })\n return candidates\n .map((example, index) => ({ example, index, result: scored[index] ?? { score: 0, parts: {} } }))\n .sort((a, b) => b.result.score - a.result.score || a.index - b.index)\n .slice(0, Math.max(1, limit ?? 5))\n .map(({ example, result }) => ({ slot: example.slot, tags: example.tags, text: example.text, score: result.score, parts: result.parts }))\n },\n })\n return ops\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"operations.js","names":[],"sources":["../../src/server/operations.ts"],"sourcesContent":["// The domain operations of the compose editor: material, split, facts, example, instance and classify. One function each,\n// framework-free: routes, forge-app-actions definitions and chat tools are projections of them. Authorization is the host's\n// (its action `authorize` or route check runs before); the operations trust `user` for port reads only. Model access goes\n// through the host's `generate`; every failure the caller can act on is a `ComposeError`.\nimport type { JSONContent } from '@tiptap/core'\nimport type { EditorPorts } from '@skaile/forge-editor/server'\nimport { gate } from '@skaile/forge-compose'\nimport type { CaseProfile, Generate, Hierarchy, Selector } from '@skaile/forge-compose'\nimport {\n KIND, META, METHOD, STATUS, TYPED_BLOCK, blockIdOf, bodyOf, bodyText, compositeMeta, compositeTags, compositeTitle,\n compositesOfKind, exampleFromProposal, exampleFromSlot, examplesOf, factNode, factsOf, instanceFromTemplate, kindOf,\n materialFor, plainText, slotsOf, templateSlotOf,\n} from '@skaile/forge-compose/documents'\nimport type { DocNode, ExampleBlock, FactBlock, MaterialTarget, PreviousDocument, SlotBlock } from '@skaile/forge-compose/documents'\nimport {\n defaultPipeline, embeddingScorer, lengthScorer, proposals, scoreAll, segment, tagFitScorer,\n} from '@skaile/forge-compose/segment'\nimport type { Decision, Embedder, RuleSpec, Scorer, SegmentPipeline, Splitter } from '@skaile/forge-compose/segment'\nimport { catalogOf } from './catalog'\nimport { ComposeError } from './errors'\nimport { buildFactsPrompt, FactsReplySchema, parseFactsReply } from './facts-prompt'\nimport type { CatalogEntry, FactDraft, FactsReply } from './facts-prompt'\nimport type { ComposeInput, ComposeOutput } from './inputs'\n\n/** What the host knows about the case a document belongs to. */\nexport interface ComposeContext {\n case: CaseProfile\n /** Document that holds the slots. */\n templateId: string\n /** Documents whose `fact`/`source` blocks belong to the case. */\n sourceIds: string[]\n /** Documents searched for `example` blocks (style only). */\n exampleIds: string[] | 'all'\n /** Documents searched for snippet blocks (`snippetKind`: content to adopt). Default none. */\n snippetIds?: string[]\n /**\n * Earlier documents of the same person as the case, newest first: the block of the same slot and element in the first of\n * them that has one is the previous version (`Material.previous`, „Vorfassung – fortschreiben, nicht übernehmen“). Default\n * none. Never a person key: document ids only.\n */\n previousIds?: string[]\n}\n\nexport interface ComposeOptions {\n /** `documents` is required (`putDocument` for `example` and `instance`); `embeddings` is reused as the pipeline's and the scorers' `Embedder` when present. */\n ports: Pick<EditorPorts, 'documents'> & Partial<Pick<EditorPorts, 'embeddings' | 'index'>>\n /** The host's model seam; every model call of the operations goes through it. */\n generate: Generate\n /** The case of a document (a case document, a source of a case, an instance); `null` = unknown document, answered as `ComposeError(404)`. */\n contextOf(documentId: string, user: object): Promise<ComposeContext | null>\n /** The document „Als Beispiel übernehmen“ appends to; created when missing. */\n exampleLibraryId: string\n hierarchy?: Hierarchy\n /**\n * Merged over `defaultPipeline({ embedder: ports.embeddings, generate: llm ? generate : undefined, rules, tagRules })`: a\n * given `splitter`, `merge`, `slotLabelers`, `tagLabelers` or `resolve` replaces the default one (a host that passes\n * `slotLabelers` replaces the default list entirely; an explicit `merge: undefined` switches merging off).\n */\n segmentation?: Partial<SegmentPipeline> & {\n rules?: RuleSpec[]\n tagRules?: RuleSpec[]\n /** Default `true`: `llmLabeler({ generate })` as the last slot and tag labeler. */\n llm?: boolean\n /** The splitter for one source (by its `meta`, e.g. `sourceType`); `null` = the pipeline's. */\n splitterFor?(source: { meta: Record<string, unknown> }): Splitter | null\n }\n /**\n * Extra scorers of the `inhalt` signal, added to the built-in signals of `material` (`tags`, `element`, `fall`, `person`,\n * length); `compose_examples` ranks with `[tagFitScorer(hierarchy), ...scorers, lengthScorer]`. Default `[embeddingScorer]`,\n * which scores 0 without `ports.embeddings`.\n */\n scorers?: Scorer[]\n /** Block kind of the snippets in `snippetIds`; default `'snippet'`. */\n snippetKind?: string\n /**\n * Whether `documentId` belongs to the same person as the case of `caseDocumentId` (the `person` signal of `material`,\n * asked for each origin document of the library entries); absent = the signal is 0.\n */\n sameOwner?: (documentId: string, caseDocumentId: string, user: object) => Promise<boolean> | boolean\n /** System message of the `facts` operation; default `FACTS_SYSTEM` (domain-neutral). A host with a domain passes its own. */\n factsSystem?: string\n}\n\nexport interface ComposeOperations {\n /**\n * Facts, the previous version, ranked snippets and ranked examples for the `slot` block `block` of `document`\n * (`materialFor`): the block's identifier picks facts and library entries, its `meta.element` ranks by the `element` signal,\n * `document` by `fall`, the template variant it was made from (`templateSlotOf`) is the slot the injected scorers see; the\n * previous version comes from `ComposeContext.previousIds`.\n */\n material(input: ComposeInput<'material'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'material'>>\n split(input: ComposeInput<'split'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'split'>>\n facts(input: ComposeInput<'facts'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'facts'>>\n example(input: ComposeInput<'example'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'example'>>\n instance(input: ComposeInput<'instance'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'instance'>>\n /** Which slot and tags a text belongs to, by the configured labelers. Not an operation id: chat only, read only. */\n classify(input: { document: string; text: string }, user: object, signal?: AbortSignal): Promise<Decision>\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// Source text\n\nconst BOLD_HEADING_CHARS = 120\n\nconst isBold = (node: DocNode): boolean =>\n Array.isArray(node.marks) && node.marks.some(mark => typeof mark === 'object' && mark !== null && 'type' in mark && mark.type === 'bold')\n\n/** A one-line paragraph that is bold throughout: how sources written by the import mark their section headings. */\nfunction isHeadingLine(node: DocNode): boolean {\n const content = node.content ?? []\n return content.length > 0 && content.every(child => child.type === 'text' && isBold(child)) && plainText(node).trim().length <= BOLD_HEADING_CHARS\n}\n\nfunction blockMarkdown(node: DocNode): string {\n switch (node.type) {\n case 'heading': {\n const text = plainText(node).trim()\n const level = Math.min(3, Math.max(1, Number(node.attrs?.level) || 1))\n return text ? `${'#'.repeat(level)} ${text}` : ''\n }\n case 'paragraph': {\n const text = plainText(node).trim()\n return text && isHeadingLine(node) ? `## ${text}` : text\n }\n case 'bulletList':\n case 'orderedList':\n return (node.content ?? [])\n .map((item, index) => ({ text: plainText(item).replace(/\\s*\\n+\\s*/g, ' ').trim(), index }))\n .filter(item => item.text)\n .map(item => `${node.type === 'orderedList' ? `${item.index + 1}.` : '-'} ${item.text}`)\n .join('\\n')\n case TYPED_BLOCK:\n return markdownOf(bodyOf(node))\n default:\n return plainText(node).trim()\n }\n}\n\n/**\n * Blocks as the Markdown the splitters read: `heading` nodes and bold one-line paragraphs become `#` lines (so\n * `structuralSplitter` sees the sections), lists become list lines, everything else plain paragraphs separated by a blank line.\n */\nfunction markdownOf(nodes: readonly DocNode[]): string {\n return nodes.map(blockMarkdown).filter(text => text.trim()).join('\\n\\n')\n}\n\nasync function sha256Hex(text: string): Promise<string> {\n const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(text))\n return [...new Uint8Array(digest)].map(byte => byte.toString(16).padStart(2, '0')).join('')\n}\n\n/** The typed block with `id`, wherever it nests; `null` when there is none. */\nfunction findBlockNode(doc: DocNode, id: string): DocNode | null {\n const walk = (nodes: readonly DocNode[] | undefined): DocNode | null => {\n for (const node of nodes ?? []) {\n if (node.type === TYPED_BLOCK && blockIdOf(node) === id) return node\n const inner = walk(node.content)\n if (inner) return inner\n }\n return null\n }\n return walk(doc.content)\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n// Support for the chat tools\n\n/** What `composeTools` reads besides the public operations; `composeOperations` registers it for the object it returns. */\nexport interface ComposeToolSupport {\n /** The content of a document as stored, `null` = no such document. */\n load(id: string): Promise<DocNode | null>\n /** The accepted examples of the case `document` belongs to, gated by `tags`, ranked by tag fit, the configured scorers and length, best first. */\n examples(\n input: { document: string; slot?: string; tags?: string[]; query?: string; limit?: number },\n user: object,\n signal?: AbortSignal,\n ): Promise<{ slot: string; tags: string[]; text: string; score: number; parts: Record<string, number> }[]>\n}\n\nconst support = new WeakMap<ComposeOperations, ComposeToolSupport>()\n\n/** The tool support of `ops`; `ops` must be what `composeOperations` returned. */\nexport function composeToolSupportOf(ops: ComposeOperations): ComposeToolSupport {\n const found = support.get(ops)\n if (!found) throw new Error('@skaile/forge-editor-compose: composeTools needs the object composeOperations returned')\n return found\n}\n\n// ---------------------------------------------------------------------------------------------------------------\n\nconst asJson = (doc: DocNode): JSONContent => doc as JSONContent // DocNode is the structural twin of JSONContent\n\nconst emptySlot = (identifier: string): SlotBlock => ({\n id: '', identifier, title: identifier, description: '', tags: [], meta: {}, chapterId: null, chapterTitle: '', node: {},\n})\n\n/** The model name recorded in the derivation of a proposal: what decided it. */\nconst modelOf = (origin: Decision['origin'], embedder: Embedder | undefined): string =>\n origin === 'embedding' && embedder ? embedder.model : (origin ?? 'none')\n\nexport function composeOperations(o: ComposeOptions): ComposeOperations {\n const { documents } = o.ports\n const h = o.hierarchy\n const scorers = o.scorers ?? [embeddingScorer]\n const snippetKind = o.snippetKind ?? 'snippet'\n\n const embedderFor = (user: object): Embedder | undefined => {\n const embeddings = o.ports.embeddings\n return embeddings && { model: embeddings.model, dim: embeddings.dim, embed: texts => embeddings.embed(texts, user) }\n }\n\n async function contextFor(documentId: string, user: object): Promise<ComposeContext> {\n const context = await o.contextOf(documentId, user)\n if (!context) throw new ComposeError(404, `Für das Dokument „${documentId}“ gibt es keinen Fall.`)\n return context\n }\n\n async function documentOf(id: string): Promise<DocNode> {\n const content = await documents.loadContent(id)\n if (!content) throw new ComposeError(404, `Das Dokument „${id}“ existiert nicht.`)\n return content\n }\n\n async function factsOfCase(context: ComposeContext): Promise<FactBlock[]> {\n const lists = await Promise.all(context.sourceIds.map(async id => factsOf(await documents.loadContent(id))))\n return lists.flat()\n }\n\n async function examplesOfCase(context: ComposeContext): Promise<ExampleBlock[]> {\n if (context.exampleIds === 'all') {\n const all = await documents.listContents()\n return all.flatMap(({ id, content }) => examplesOf(content, id))\n }\n const lists = await Promise.all(context.exampleIds.map(async id => examplesOf(await documents.loadContent(id), id)))\n return lists.flat()\n }\n\n async function snippetsOfCase(context: ComposeContext): Promise<ExampleBlock[]> {\n const lists = await Promise.all((context.snippetIds ?? []).map(async id => examplesOf(await documents.loadContent(id), id, snippetKind)))\n return lists.flat()\n }\n\n /** `o.sameOwner` asked once per origin document of `entries` for the case of `caseDocumentId`, as the synchronous predicate `materialFor` takes. */\n async function ownerCheckOf(\n entries: readonly ExampleBlock[],\n caseDocumentId: string,\n user: object,\n ): Promise<((documentId: string) => boolean) | undefined> {\n const sameOwner = o.sameOwner\n if (!sameOwner) return undefined\n const ids = [...new Set(entries.flatMap(entry => (entry.origin ? [entry.origin.document] : [])))]\n const answers = await Promise.all(ids.map(id => sameOwner(id, caseDocumentId, user)))\n const owned = new Set(ids.filter((_, index) => answers[index]))\n return documentId => owned.has(documentId)\n }\n\n /** The earlier documents of the case (`previousIds`) with their content, in the given order. */\n async function previousOfCase(context: ComposeContext): Promise<PreviousDocument[]> {\n return Promise.all((context.previousIds ?? []).map(async id => ({ id, content: await documents.loadContent(id) })))\n }\n\n /** The block `blockId` of `documentId` as a typed block of `kind`; `ComposeError(404)` otherwise. */\n async function blockOf(documentId: string, blockId: string, kind: string): Promise<DocNode> {\n const node = findBlockNode(await documentOf(documentId), blockId)\n if (!node) throw new ComposeError(404, `Der Block „${blockId}“ existiert im Dokument „${documentId}“ nicht.`)\n if (kindOf(node) !== kind) throw new ComposeError(404, `Der Block „${blockId}“ ist kein Block vom Typ „${kind}“.`)\n return node\n }\n\n /** The pipeline: the default of the host's embedder and `generate`, the `segmentation` fields merged over it. */\n function pipelineOf(embedder: Embedder | undefined, source?: { meta: Record<string, unknown> }): SegmentPipeline {\n const { rules, tagRules, llm, splitterFor, ...override } = o.segmentation ?? {}\n const base = defaultPipeline({\n ...(embedder && { embedder }),\n ...(llm !== false && { generate: o.generate }),\n ...(rules && { rules }),\n ...(tagRules && { tagRules }),\n })\n const merge = 'merge' in override ? override.merge : base.merge\n const resolveOptions = 'resolve' in override ? override.resolve : base.resolve\n return {\n splitter: (source && splitterFor?.(source)) || override.splitter || base.splitter,\n slotLabelers: override.slotLabelers ?? base.slotLabelers,\n tagLabelers: override.tagLabelers ?? base.tagLabelers,\n ...(merge && { merge }),\n ...(resolveOptions && { resolve: resolveOptions }),\n }\n }\n\n /** `example` appends read-modify-write one after another, so two adoptions at once keep both. */\n let appending: Promise<unknown> = Promise.resolve()\n const exclusive = <T>(run: () => Promise<T>): Promise<T> => {\n const result = appending.then(run)\n appending = result.catch(() => undefined)\n return result\n }\n\n const ops: ComposeOperations = {\n async material({ document, block }, user, signal) {\n const context = await contextFor(document, user)\n const node = await blockOf(document, block, KIND.slot)\n const [facts, examples, snippets, previous, template] = await Promise.all([\n factsOfCase(context),\n examplesOfCase(context),\n snippetsOfCase(context),\n previousOfCase(context),\n documents.loadContent(context.templateId),\n ])\n const meta = compositeMeta(node)\n const identifier = meta[META.identifier]\n const rawElement = meta[META.element]\n const element = typeof rawElement === 'string' ? rawElement.trim() : ''\n const slotBlock = template ? templateSlotOf(node, template) : undefined\n const target: MaterialTarget = {\n identifier: typeof identifier === 'string' ? identifier : '',\n ...(element ? { element } : {}),\n tags: compositeTags(node),\n document,\n ...(slotBlock && { slot: slotBlock }),\n }\n const embedder = embedderFor(user)\n const sameOwner = await ownerCheckOf([...examples, ...snippets], document, user)\n return materialFor(target, context.case, facts, { examples, snippets, previous }, {\n scorers,\n ...(h && { h }),\n ...(sameOwner && { sameOwner }),\n ctx: { ...(embedder && { embedder }), ...(signal && { signal }) },\n })\n },\n\n async split({ document, block }, user, signal) {\n const context = await contextFor(document, user)\n const source = await blockOf(document, block, KIND.source)\n const body = markdownOf(bodyOf(source))\n if (!body.trim()) throw new ComposeError(409, 'Die Quelle enthält keinen Text.')\n const [template, examples] = await Promise.all([documents.loadContent(context.templateId), examplesOfCase(context)])\n const embedder = embedderFor(user)\n const { units, decisions } = await segment(\n body,\n pipelineOf(embedder, { meta: compositeMeta(source) }),\n catalogOf(template, examples),\n { ...(embedder && { embedder }), ...(signal && { signal }) },\n )\n const inputHash = await sha256Hex(body)\n const now = new Date().toISOString()\n // A unit without slot stays a proposal (slot empty): nothing of the corpus is lost, the person assigns it.\n const nodes = proposals(units, decisions).map(proposal =>\n exampleFromProposal(proposal, {\n sourceBlockId: block,\n sourceDocumentId: document,\n caseTags: context.case.tags,\n model: modelOf(proposal.origin, embedder),\n now,\n inputHash,\n }),\n )\n return { nodes }\n },\n\n async facts({ document, block }, user, signal) {\n const context = await contextFor(document, user)\n const source = await blockOf(document, block, KIND.source)\n const body = markdownOf(bodyOf(source))\n if (!body.trim()) throw new ComposeError(409, 'Die Quelle enthält keinen Text.')\n const template = await documentOf(context.templateId)\n\n const chapters = new Map(compositesOfKind(template, KIND.section).map(section => [blockIdOf(section), compositeTitle(section)]))\n const seen = new Set<string>()\n const catalog: CatalogEntry[] = []\n for (const slot of slotsOf(template)) {\n if (!slot.identifier || seen.has(slot.identifier)) continue\n seen.add(slot.identifier)\n catalog.push({\n identifier: slot.identifier,\n chapter: (slot.chapterId && chapters.get(slot.chapterId)) || '',\n title: slot.title,\n description: slot.description,\n tags: slot.tags,\n })\n }\n\n const { system, prompt } = buildFactsPrompt(catalog, body, o.factsSystem)\n const reply = await o.generate<FactsReply>({ stage: 'facts', system, prompt, schema: FactsReplySchema, ...(signal && { signal }) })\n let drafts: FactDraft[]\n try {\n drafts = parseFactsReply(reply, seen)\n }\n catch (error) {\n throw new ComposeError(422, error instanceof Error ? error.message : 'Antwort des Modells unbrauchbar')\n }\n const inputHash = await sha256Hex(body)\n const now = new Date().toISOString()\n // The host's `generate` knows the model and logs it; it is not known here.\n const nodes = drafts.map(draft =>\n factNode({\n text: draft.text,\n slot: draft.slot,\n derivation: { from: { document, block }, method: METHOD.aiFact, createdAt: now, inputHash },\n }),\n )\n return { nodes }\n },\n\n async example({ document, block }, user) {\n const context = await contextFor(document, user)\n const slotNode = await blockOf(document, block, KIND.slot)\n const slot = slotsOf({ type: 'doc', content: [slotNode] })[0]\n if (!slot || !bodyText(slotNode).trim()) throw new ComposeError(409, 'Der Block hat keinen Text, der als Beispiel übernommen werden könnte.')\n if (!documents.putDocument) throw new ComposeError(503, 'Der Dokumentspeicher kann keine Dokumente schreiben.')\n const put = documents.putDocument.bind(documents)\n const node = exampleFromSlot(slot, {\n sourceDocumentId: document,\n caseTags: context.case.tags,\n now: new Date().toISOString(),\n })\n await exclusive(async () => {\n const library = (await documents.loadContent(o.exampleLibraryId)) ?? { type: 'doc', content: [] }\n await put(o.exampleLibraryId, asJson({ ...library, content: [...(library.content ?? []), node] }))\n })\n return { libraryId: o.exampleLibraryId, blockId: blockIdOf(node) }\n },\n\n async instance({ templateId, instanceId, force }, user) {\n if (!force && (await documents.loadContent(instanceId))) throw new ComposeError(409, 'Instanz existiert')\n const context = await contextFor(instanceId, user)\n const template = await documentOf(templateId)\n if (!documents.putDocument) throw new ComposeError(503, 'Der Dokumentspeicher kann keine Dokumente schreiben.')\n const { doc, chosen, dropped, unresolved, elements } = instanceFromTemplate(template, context.case, {\n templateId,\n now: new Date().toISOString(),\n ...(h && { h }),\n })\n await documents.putDocument(instanceId, asJson(doc))\n return { chosen, dropped, unresolved, elements }\n },\n\n async classify({ document, text }, user, signal) {\n if (!text.trim()) throw new ComposeError(400, 'Der Text ist leer.')\n const context = await contextFor(document, user)\n const [template, examples] = await Promise.all([documents.loadContent(context.templateId), examplesOfCase(context)])\n const embedder = embedderFor(user)\n // The whole text is one unit: no splitting, no merging, then the configured labelers and the resolver as in `split`.\n const single: Splitter = { name: 'single', split: () => [{ text, start: 0, end: text.length, level: 0, path: [] }] }\n const { merge: _merge, ...pipeline } = pipelineOf(embedder)\n const { decisions } = await segment(\n text,\n { ...pipeline, splitter: single },\n catalogOf(template, examples),\n { ...(embedder && { embedder }), ...(signal && { signal }) },\n )\n return decisions[0] ?? { unit: 0, slot: null, tags: [], confidence: 0, origin: null }\n },\n }\n\n support.set(ops, {\n load: id => documents.loadContent(id),\n\n async examples({ document, slot, tags, query, limit }, user, signal) {\n const context = await contextFor(document, user)\n const selector: Selector = { require: tags ?? [], prefer: [], exclude: [] }\n const candidates = (await examplesOfCase(context)).filter(\n example => example.status !== STATUS.proposal && (!slot || example.slot === slot) && gate(selector, example.tags, h),\n )\n if (candidates.length === 0) return []\n const template = await documents.loadContent(context.templateId)\n const slotBlock = slotsOf(template).find(candidate => candidate.identifier === slot) ?? emptySlot(slot ?? '')\n const facts: FactBlock[] = query?.trim() ? [{ id: '', slot: slotBlock.identifier, text: query.trim(), sourceTitle: '' }] : []\n // Asked for explicit tags, the case to measure against is those tags.\n const profile: CaseProfile = tags?.length ? { ...context.case, tags } : context.case\n const embedder = embedderFor(user)\n const scored = await scoreAll(candidates, { case: profile, slot: slotBlock, facts }, [tagFitScorer(h), ...scorers, lengthScorer], {\n ...(embedder && { embedder }),\n ...(signal && { signal }),\n })\n return candidates\n .map((example, index) => ({ example, index, result: scored[index] ?? { score: 0, parts: {} } }))\n .sort((a, b) => b.result.score - a.result.score || a.index - b.index)\n .slice(0, Math.max(1, limit ?? 5))\n .map(({ example, result }) => ({ slot: example.slot, tags: example.tags, text: example.text, score: result.score, parts: result.parts }))\n },\n })\n return ops\n}\n"],"mappings":";;;;;;;AAsGA,MAAM,qBAAqB;AAE3B,MAAM,UAAU,SACd,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAK,MAAM,MAAK,SAAQ,OAAO,SAAS,YAAY,SAAS,QAAQ,UAAU,QAAQ,KAAK,SAAS,MAAM;;AAG1I,SAAS,cAAc,MAAwB;CAC7C,MAAM,UAAU,KAAK,WAAW,CAAC;CACjC,OAAO,QAAQ,SAAS,KAAK,QAAQ,OAAM,UAAS,MAAM,SAAS,UAAU,OAAO,KAAK,CAAC,KAAK,UAAU,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU;AAClI;AAEA,SAAS,cAAc,MAAuB;CAC5C,QAAQ,KAAK,MAAb;EACE,KAAK,WAAW;GACd,MAAM,OAAO,UAAU,IAAI,CAAC,CAAC,KAAK;GAClC,MAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,OAAO,KAAK,OAAO,KAAK,KAAK,CAAC,CAAC;GACrE,OAAO,OAAO,GAAG,IAAI,OAAO,KAAK,EAAE,GAAG,SAAS;EACjD;EACA,KAAK,aAAa;GAChB,MAAM,OAAO,UAAU,IAAI,CAAC,CAAC,KAAK;GAClC,OAAO,QAAQ,cAAc,IAAI,IAAI,MAAM,SAAS;EACtD;EACA,KAAK;EACL,KAAK,eACH,QAAQ,KAAK,WAAW,CAAC,EAAA,CACtB,KAAK,MAAM,WAAW;GAAE,MAAM,UAAU,IAAI,CAAC,CAAC,QAAQ,cAAc,GAAG,CAAC,CAAC,KAAK;GAAG;EAAM,EAAE,CAAC,CAC1F,QAAO,SAAQ,KAAK,IAAI,CAAC,CACzB,KAAI,SAAQ,GAAG,KAAK,SAAS,gBAAgB,GAAG,KAAK,QAAQ,EAAE,KAAK,IAAI,GAAG,KAAK,MAAM,CAAC,CACvF,KAAK,IAAI;EACd,KAAK,aACH,OAAO,WAAW,OAAO,IAAI,CAAC;EAChC,SACE,OAAO,UAAU,IAAI,CAAC,CAAC,KAAK;CAChC;AACF;;;;;AAMA,SAAS,WAAW,OAAmC;CACrD,OAAO,MAAM,IAAI,aAAa,CAAC,CAAC,QAAO,SAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM;AACzE;AAEA,eAAe,UAAU,MAA+B;CACtD,MAAM,SAAS,MAAM,WAAW,OAAO,OAAO,OAAO,WAAW,IAAI,YAAY,CAAC,CAAC,OAAO,IAAI,CAAC;CAC9F,OAAO,CAAC,GAAG,IAAI,WAAW,MAAM,CAAC,CAAC,CAAC,KAAI,SAAQ,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE;AAC5F;;AAGA,SAAS,cAAc,KAAc,IAA4B;CAC/D,MAAM,QAAQ,UAA0D;EACtE,KAAK,MAAM,QAAQ,SAAS,CAAC,GAAG;GAC9B,IAAI,KAAK,SAAS,eAAe,UAAU,IAAI,MAAM,IAAI,OAAO;GAChE,MAAM,QAAQ,KAAK,KAAK,OAAO;GAC/B,IAAI,OAAO,OAAO;EACpB;EACA,OAAO;CACT;CACA,OAAO,KAAK,IAAI,OAAO;AACzB;AAiBA,MAAM,0BAAU,IAAI,QAA+C;;AAGnE,SAAgB,qBAAqB,KAA4C;CAC/E,MAAM,QAAQ,QAAQ,IAAI,GAAG;CAC7B,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,wFAAwF;CACpH,OAAO;AACT;AAIA,MAAM,UAAU,QAA8B;AAE9C,MAAM,aAAa,gBAAmC;CACpD,IAAI;CAAI;CAAY,OAAO;CAAY,aAAa;CAAI,MAAM,CAAC;CAAG,MAAM,CAAC;CAAG,WAAW;CAAM,cAAc;CAAI,MAAM,CAAC;AACxH;;AAGA,MAAM,WAAW,QAA4B,aAC3C,WAAW,eAAe,WAAW,SAAS,QAAS,UAAU;AAEnE,SAAgB,kBAAkB,GAAsC;CACtE,MAAM,EAAE,cAAc,EAAE;CACxB,MAAM,IAAI,EAAE;CACZ,MAAM,UAAU,EAAE,WAAW,CAAC,eAAe;CAC7C,MAAM,cAAc,EAAE,eAAe;CAErC,MAAM,eAAe,SAAuC;EAC1D,MAAM,aAAa,EAAE,MAAM;EAC3B,OAAO,cAAc;GAAE,OAAO,WAAW;GAAO,KAAK,WAAW;GAAK,QAAO,UAAS,WAAW,MAAM,OAAO,IAAI;EAAE;CACrH;CAEA,eAAe,WAAW,YAAoB,MAAuC;EACnF,MAAM,UAAU,MAAM,EAAE,UAAU,YAAY,IAAI;EAClD,IAAI,CAAC,SAAS,MAAM,IAAI,aAAa,KAAK,qBAAqB,WAAW,uBAAuB;EACjG,OAAO;CACT;CAEA,eAAe,WAAW,IAA8B;EACtD,MAAM,UAAU,MAAM,UAAU,YAAY,EAAE;EAC9C,IAAI,CAAC,SAAS,MAAM,IAAI,aAAa,KAAK,iBAAiB,GAAG,mBAAmB;EACjF,OAAO;CACT;CAEA,eAAe,YAAY,SAA+C;EAExE,QAAO,MADa,QAAQ,IAAI,QAAQ,UAAU,IAAI,OAAM,OAAM,QAAQ,MAAM,UAAU,YAAY,EAAE,CAAC,CAAC,CAAC,EAAA,CAC9F,KAAK;CACpB;CAEA,eAAe,eAAe,SAAkD;EAC9E,IAAI,QAAQ,eAAe,OAEzB,QAAO,MADW,UAAU,aAAa,EAAA,CAC9B,SAAS,EAAE,IAAI,cAAc,WAAW,SAAS,EAAE,CAAC;EAGjE,QAAO,MADa,QAAQ,IAAI,QAAQ,WAAW,IAAI,OAAM,OAAM,WAAW,MAAM,UAAU,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,EAAA,CACtG,KAAK;CACpB;CAEA,eAAe,eAAe,SAAkD;EAE9E,QAAO,MADa,QAAQ,KAAK,QAAQ,cAAc,CAAC,EAAA,CAAG,IAAI,OAAM,OAAM,WAAW,MAAM,UAAU,YAAY,EAAE,GAAG,IAAI,WAAW,CAAC,CAAC,EAAA,CAC3H,KAAK;CACpB;;CAGA,eAAe,aACb,SACA,gBACA,MACwD;EACxD,MAAM,YAAY,EAAE;EACpB,IAAI,CAAC,WAAW,OAAO,KAAA;EACvB,MAAM,MAAM,CAAC,GAAG,IAAI,IAAI,QAAQ,SAAQ,UAAU,MAAM,SAAS,CAAC,MAAM,OAAO,QAAQ,IAAI,CAAC,CAAE,CAAC,CAAC;EAChG,MAAM,UAAU,MAAM,QAAQ,IAAI,IAAI,KAAI,OAAM,UAAU,IAAI,gBAAgB,IAAI,CAAC,CAAC;EACpF,MAAM,QAAQ,IAAI,IAAI,IAAI,QAAQ,GAAG,UAAU,QAAQ,MAAM,CAAC;EAC9D,QAAO,eAAc,MAAM,IAAI,UAAU;CAC3C;;CAGA,eAAe,eAAe,SAAsD;EAClF,OAAO,QAAQ,KAAK,QAAQ,eAAe,CAAC,EAAA,CAAG,IAAI,OAAM,QAAO;GAAE;GAAI,SAAS,MAAM,UAAU,YAAY,EAAE;EAAE,EAAE,CAAC;CACpH;;CAGA,eAAe,QAAQ,YAAoB,SAAiB,MAAgC;EAC1F,MAAM,OAAO,cAAc,MAAM,WAAW,UAAU,GAAG,OAAO;EAChE,IAAI,CAAC,MAAM,MAAM,IAAI,aAAa,KAAK,cAAc,QAAQ,2BAA2B,WAAW,SAAS;EAC5G,IAAI,OAAO,IAAI,MAAM,MAAM,MAAM,IAAI,aAAa,KAAK,cAAc,QAAQ,4BAA4B,KAAK,GAAG;EACjH,OAAO;CACT;;CAGA,SAAS,WAAW,UAAgC,QAA6D;EAC/G,MAAM,EAAE,OAAO,UAAU,KAAK,aAAa,GAAG,aAAa,EAAE,gBAAgB,CAAC;EAC9E,MAAM,OAAO,gBAAgB;GAC3B,GAAI,YAAY,EAAE,SAAS;GAC3B,GAAI,QAAQ,SAAS,EAAE,UAAU,EAAE,SAAS;GAC5C,GAAI,SAAS,EAAE,MAAM;GACrB,GAAI,YAAY,EAAE,SAAS;EAC7B,CAAC;EACD,MAAM,QAAQ,WAAW,WAAW,SAAS,QAAQ,KAAK;EAC1D,MAAM,iBAAiB,aAAa,WAAW,SAAS,UAAU,KAAK;EACvE,OAAO;GACL,UAAW,UAAU,cAAc,MAAM,KAAM,SAAS,YAAY,KAAK;GACzE,cAAc,SAAS,gBAAgB,KAAK;GAC5C,aAAa,SAAS,eAAe,KAAK;GAC1C,GAAI,SAAS,EAAE,MAAM;GACrB,GAAI,kBAAkB,EAAE,SAAS,eAAe;EAClD;CACF;;CAGA,IAAI,YAA8B,QAAQ,QAAQ;CAClD,MAAM,aAAgB,QAAsC;EAC1D,MAAM,SAAS,UAAU,KAAK,GAAG;EACjC,YAAY,OAAO,YAAY,KAAA,CAAS;EACxC,OAAO;CACT;CAEA,MAAM,MAAyB;EAC7B,MAAM,SAAS,EAAE,UAAU,SAAS,MAAM,QAAQ;GAChD,MAAM,UAAU,MAAM,WAAW,UAAU,IAAI;GAC/C,MAAM,OAAO,MAAM,QAAQ,UAAU,OAAO,KAAK,IAAI;GACrD,MAAM,CAAC,OAAO,UAAU,UAAU,UAAU,YAAY,MAAM,QAAQ,IAAI;IACxE,YAAY,OAAO;IACnB,eAAe,OAAO;IACtB,eAAe,OAAO;IACtB,eAAe,OAAO;IACtB,UAAU,YAAY,QAAQ,UAAU;GAC1C,CAAC;GACD,MAAM,OAAO,cAAc,IAAI;GAC/B,MAAM,aAAa,KAAK,KAAK;GAC7B,MAAM,aAAa,KAAK,KAAK;GAC7B,MAAM,UAAU,OAAO,eAAe,WAAW,WAAW,KAAK,IAAI;GACrE,MAAM,YAAY,WAAW,eAAe,MAAM,QAAQ,IAAI,KAAA;GAC9D,MAAM,SAAyB;IAC7B,YAAY,OAAO,eAAe,WAAW,aAAa;IAC1D,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;IAC7B,MAAM,cAAc,IAAI;IACxB;IACA,GAAI,aAAa,EAAE,MAAM,UAAU;GACrC;GACA,MAAM,WAAW,YAAY,IAAI;GACjC,MAAM,YAAY,MAAM,aAAa,CAAC,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,IAAI;GAC/E,OAAO,YAAY,QAAQ,QAAQ,MAAM,OAAO;IAAE;IAAU;IAAU;GAAS,GAAG;IAChF;IACA,GAAI,KAAK,EAAE,EAAE;IACb,GAAI,aAAa,EAAE,UAAU;IAC7B,KAAK;KAAE,GAAI,YAAY,EAAE,SAAS;KAAI,GAAI,UAAU,EAAE,OAAO;IAAG;GAClE,CAAC;EACH;EAEA,MAAM,MAAM,EAAE,UAAU,SAAS,MAAM,QAAQ;GAC7C,MAAM,UAAU,MAAM,WAAW,UAAU,IAAI;GAC/C,MAAM,SAAS,MAAM,QAAQ,UAAU,OAAO,KAAK,MAAM;GACzD,MAAM,OAAO,WAAW,OAAO,MAAM,CAAC;GACtC,IAAI,CAAC,KAAK,KAAK,GAAG,MAAM,IAAI,aAAa,KAAK,iCAAiC;GAC/E,MAAM,CAAC,UAAU,YAAY,MAAM,QAAQ,IAAI,CAAC,UAAU,YAAY,QAAQ,UAAU,GAAG,eAAe,OAAO,CAAC,CAAC;GACnH,MAAM,WAAW,YAAY,IAAI;GACjC,MAAM,EAAE,OAAO,cAAc,MAAM,QACjC,MACA,WAAW,UAAU,EAAE,MAAM,cAAc,MAAM,EAAE,CAAC,GACpD,UAAU,UAAU,QAAQ,GAC5B;IAAE,GAAI,YAAY,EAAE,SAAS;IAAI,GAAI,UAAU,EAAE,OAAO;GAAG,CAC7D;GACA,MAAM,YAAY,MAAM,UAAU,IAAI;GACtC,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;GAYnC,OAAO,EAAE,OAVK,UAAU,OAAO,SAAS,CAAC,CAAC,KAAI,aAC5C,oBAAoB,UAAU;IAC5B,eAAe;IACf,kBAAkB;IAClB,UAAU,QAAQ,KAAK;IACvB,OAAO,QAAQ,SAAS,QAAQ,QAAQ;IACxC;IACA;GACF,CAAC,CAEU,EAAE;EACjB;EAEA,MAAM,MAAM,EAAE,UAAU,SAAS,MAAM,QAAQ;GAC7C,MAAM,UAAU,MAAM,WAAW,UAAU,IAAI;GAC/C,MAAM,SAAS,MAAM,QAAQ,UAAU,OAAO,KAAK,MAAM;GACzD,MAAM,OAAO,WAAW,OAAO,MAAM,CAAC;GACtC,IAAI,CAAC,KAAK,KAAK,GAAG,MAAM,IAAI,aAAa,KAAK,iCAAiC;GAC/E,MAAM,WAAW,MAAM,WAAW,QAAQ,UAAU;GAEpD,MAAM,WAAW,IAAI,IAAI,iBAAiB,UAAU,KAAK,OAAO,CAAC,CAAC,KAAI,YAAW,CAAC,UAAU,OAAO,GAAG,eAAe,OAAO,CAAC,CAAC,CAAC;GAC/H,MAAM,uBAAO,IAAI,IAAY;GAC7B,MAAM,UAA0B,CAAC;GACjC,KAAK,MAAM,QAAQ,QAAQ,QAAQ,GAAG;IACpC,IAAI,CAAC,KAAK,cAAc,KAAK,IAAI,KAAK,UAAU,GAAG;IACnD,KAAK,IAAI,KAAK,UAAU;IACxB,QAAQ,KAAK;KACX,YAAY,KAAK;KACjB,SAAU,KAAK,aAAa,SAAS,IAAI,KAAK,SAAS,KAAM;KAC7D,OAAO,KAAK;KACZ,aAAa,KAAK;KAClB,MAAM,KAAK;IACb,CAAC;GACH;GAEA,MAAM,EAAE,QAAQ,WAAW,iBAAiB,SAAS,MAAM,EAAE,WAAW;GACxE,MAAM,QAAQ,MAAM,EAAE,SAAqB;IAAE,OAAO;IAAS;IAAQ;IAAQ,QAAQ;IAAkB,GAAI,UAAU,EAAE,OAAO;GAAG,CAAC;GAClI,IAAI;GACJ,IAAI;IACF,SAAS,gBAAgB,OAAO,IAAI;GACtC,SACO,OAAO;IACZ,MAAM,IAAI,aAAa,KAAK,iBAAiB,QAAQ,MAAM,UAAU,iCAAiC;GACxG;GACA,MAAM,YAAY,MAAM,UAAU,IAAI;GACtC,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;GASnC,OAAO,EAAE,OAPK,OAAO,KAAI,UACvB,SAAS;IACP,MAAM,MAAM;IACZ,MAAM,MAAM;IACZ,YAAY;KAAE,MAAM;MAAE;MAAU;KAAM;KAAG,QAAQ,OAAO;KAAQ,WAAW;KAAK;IAAU;GAC5F,CAAC,CAEU,EAAE;EACjB;EAEA,MAAM,QAAQ,EAAE,UAAU,SAAS,MAAM;GACvC,MAAM,UAAU,MAAM,WAAW,UAAU,IAAI;GAC/C,MAAM,WAAW,MAAM,QAAQ,UAAU,OAAO,KAAK,IAAI;GACzD,MAAM,OAAO,QAAQ;IAAE,MAAM;IAAO,SAAS,CAAC,QAAQ;GAAE,CAAC,CAAC,CAAC;GAC3D,IAAI,CAAC,QAAQ,CAAC,SAAS,QAAQ,CAAC,CAAC,KAAK,GAAG,MAAM,IAAI,aAAa,KAAK,uEAAuE;GAC5I,IAAI,CAAC,UAAU,aAAa,MAAM,IAAI,aAAa,KAAK,sDAAsD;GAC9G,MAAM,MAAM,UAAU,YAAY,KAAK,SAAS;GAChD,MAAM,OAAO,gBAAgB,MAAM;IACjC,kBAAkB;IAClB,UAAU,QAAQ,KAAK;IACvB,sBAAK,IAAI,KAAK,EAAA,CAAE,YAAY;GAC9B,CAAC;GACD,MAAM,UAAU,YAAY;IAC1B,MAAM,UAAW,MAAM,UAAU,YAAY,EAAE,gBAAgB,KAAM;KAAE,MAAM;KAAO,SAAS,CAAC;IAAE;IAChG,MAAM,IAAI,EAAE,kBAAkB,OAAO;KAAE,GAAG;KAAS,SAAS,CAAC,GAAI,QAAQ,WAAW,CAAC,GAAI,IAAI;IAAE,CAAC,CAAC;GACnG,CAAC;GACD,OAAO;IAAE,WAAW,EAAE;IAAkB,SAAS,UAAU,IAAI;GAAE;EACnE;EAEA,MAAM,SAAS,EAAE,YAAY,YAAY,SAAS,MAAM;GACtD,IAAI,CAAC,SAAU,MAAM,UAAU,YAAY,UAAU,GAAI,MAAM,IAAI,aAAa,KAAK,mBAAmB;GACxG,MAAM,UAAU,MAAM,WAAW,YAAY,IAAI;GACjD,MAAM,WAAW,MAAM,WAAW,UAAU;GAC5C,IAAI,CAAC,UAAU,aAAa,MAAM,IAAI,aAAa,KAAK,sDAAsD;GAC9G,MAAM,EAAE,KAAK,QAAQ,SAAS,YAAY,aAAa,qBAAqB,UAAU,QAAQ,MAAM;IAClG;IACA,sBAAK,IAAI,KAAK,EAAA,CAAE,YAAY;IAC5B,GAAI,KAAK,EAAE,EAAE;GACf,CAAC;GACD,MAAM,UAAU,YAAY,YAAY,OAAO,GAAG,CAAC;GACnD,OAAO;IAAE;IAAQ;IAAS;IAAY;GAAS;EACjD;EAEA,MAAM,SAAS,EAAE,UAAU,QAAQ,MAAM,QAAQ;GAC/C,IAAI,CAAC,KAAK,KAAK,GAAG,MAAM,IAAI,aAAa,KAAK,oBAAoB;GAClE,MAAM,UAAU,MAAM,WAAW,UAAU,IAAI;GAC/C,MAAM,CAAC,UAAU,YAAY,MAAM,QAAQ,IAAI,CAAC,UAAU,YAAY,QAAQ,UAAU,GAAG,eAAe,OAAO,CAAC,CAAC;GACnH,MAAM,WAAW,YAAY,IAAI;GAEjC,MAAM,SAAmB;IAAE,MAAM;IAAU,aAAa,CAAC;KAAE;KAAM,OAAO;KAAG,KAAK,KAAK;KAAQ,OAAO;KAAG,MAAM,CAAC;IAAE,CAAC;GAAE;GACnH,MAAM,EAAE,OAAO,QAAQ,GAAG,aAAa,WAAW,QAAQ;GAC1D,MAAM,EAAE,cAAc,MAAM,QAC1B,MACA;IAAE,GAAG;IAAU,UAAU;GAAO,GAChC,UAAU,UAAU,QAAQ,GAC5B;IAAE,GAAI,YAAY,EAAE,SAAS;IAAI,GAAI,UAAU,EAAE,OAAO;GAAG,CAC7D;GACA,OAAO,UAAU,MAAM;IAAE,MAAM;IAAG,MAAM;IAAM,MAAM,CAAC;IAAG,YAAY;IAAG,QAAQ;GAAK;EACtF;CACF;CAEA,QAAQ,IAAI,KAAK;EACf,OAAM,OAAM,UAAU,YAAY,EAAE;EAEpC,MAAM,SAAS,EAAE,UAAU,MAAM,MAAM,OAAO,SAAS,MAAM,QAAQ;GACnE,MAAM,UAAU,MAAM,WAAW,UAAU,IAAI;GAC/C,MAAM,WAAqB;IAAE,SAAS,QAAQ,CAAC;IAAG,QAAQ,CAAC;IAAG,SAAS,CAAC;GAAE;GAC1E,MAAM,cAAc,MAAM,eAAe,OAAO,EAAA,CAAG,QACjD,YAAW,QAAQ,WAAW,OAAO,aAAa,CAAC,QAAQ,QAAQ,SAAS,SAAS,KAAK,UAAU,QAAQ,MAAM,CAAC,CACrH;GACA,IAAI,WAAW,WAAW,GAAG,OAAO,CAAC;GACrC,MAAM,WAAW,MAAM,UAAU,YAAY,QAAQ,UAAU;GAC/D,MAAM,YAAY,QAAQ,QAAQ,CAAC,CAAC,MAAK,cAAa,UAAU,eAAe,IAAI,KAAK,UAAU,QAAQ,EAAE;GAC5G,MAAM,QAAqB,OAAO,KAAK,IAAI,CAAC;IAAE,IAAI;IAAI,MAAM,UAAU;IAAY,MAAM,MAAM,KAAK;IAAG,aAAa;GAAG,CAAC,IAAI,CAAC;GAE5H,MAAM,UAAuB,MAAM,SAAS;IAAE,GAAG,QAAQ;IAAM;GAAK,IAAI,QAAQ;GAChF,MAAM,WAAW,YAAY,IAAI;GACjC,MAAM,SAAS,MAAM,SAAS,YAAY;IAAE,MAAM;IAAS,MAAM;IAAW;GAAM,GAAG;IAAC,aAAa,CAAC;IAAG,GAAG;IAAS;GAAY,GAAG;IAChI,GAAI,YAAY,EAAE,SAAS;IAC3B,GAAI,UAAU,EAAE,OAAO;GACzB,CAAC;GACD,OAAO,WACJ,KAAK,SAAS,WAAW;IAAE;IAAS;IAAO,QAAQ,OAAO,UAAU;KAAE,OAAO;KAAG,OAAO,CAAC;IAAE;GAAE,EAAE,CAAC,CAC/F,MAAM,GAAG,MAAM,EAAE,OAAO,QAAQ,EAAE,OAAO,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,CACpE,MAAM,GAAG,KAAK,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CACjC,KAAK,EAAE,SAAS,cAAc;IAAE,MAAM,QAAQ;IAAM,MAAM,QAAQ;IAAM,MAAM,QAAQ;IAAM,OAAO,OAAO;IAAO,OAAO,OAAO;GAAM,EAAE;EAC5I;CACF,CAAC;CACD,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skaile/forge-editor-compose",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "description": "Corpus to composed text in @skaile/forge-editor: the Block types slot, source, fact and example, the editor features that split sources into examples, extract facts, fill slots with ranked material and adopt examples, and the operation input contract shared with the host's routes.",
6
6
  "license": "MIT",
@@ -50,11 +50,11 @@
50
50
  }
51
51
  },
52
52
  "dependencies": {
53
- "@skaile/forge-compose": "^0.7.0",
53
+ "@skaile/forge-compose": "^0.9.0",
54
54
  "zod": "^4.5.4"
55
55
  },
56
56
  "peerDependencies": {
57
- "@skaile/forge-editor": "^6.2.0",
57
+ "@skaile/forge-editor": "^6.8.0",
58
58
  "@skaile/forge-app-actions": "^0.4.1",
59
59
  "@tiptap/core": "^3.31.3",
60
60
  "@tiptap/pm": "^3.31.3",