@skaile/forge-editor-compose 0.2.0 → 0.3.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 +7 -0
- package/README.md +5 -3
- package/dist/server/actions.js +1 -1
- package/dist/server/actions.js.map +1 -1
- package/dist/server/inputs.d.ts +1 -1
- package/dist/server/inputs.js +1 -1
- package/dist/server/inputs.js.map +1 -1
- package/dist/server/operations.d.ts +18 -4
- package/dist/server/operations.js +38 -15
- package/dist/server/operations.js.map +1 -1
- package/dist/server/tools.d.ts +1 -1
- package/dist/server/tools.js +11 -4
- package/dist/server/tools.js.map +1 -1
- package/dist/vue/features.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 2026-11-05
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Breaking:** `material` returns `{ facts, snippets, examples }`: library entries from `ComposeContext.snippetIds` (kind `ComposeOptions.snippetKind`, default `snippet`) come back as Bausteine, filtered by `nur`/`nie` and ranked by the weighted signals of forge-compose 0.3.0 (`parts` per entry). `ComposeOptions.sameOwner` feeds the `person` signal. The default `scorers` are now the `inhalt` scorers only (`embeddingScorer`); tag fit and length are built in. `compose_material` answers `fakten`, `bausteine`, `beispiele` with their parts.
|
|
8
|
+
- `example` records element, tags and origin of the block.
|
|
9
|
+
|
|
3
10
|
## 0.2.0 - 2026-11-05
|
|
4
11
|
|
|
5
12
|
### Changed
|
package/README.md
CHANGED
|
@@ -64,8 +64,10 @@ const ops = composeOperations({
|
|
|
64
64
|
hierarchy: icdHierarchy,
|
|
65
65
|
async contextOf(documentId, user) { // null = unknown document → ComposeError(404)
|
|
66
66
|
const c = await caseOf(documentId, user)
|
|
67
|
-
return c && { case: { tags: c.tags, data: c.data }, templateId: c.templateId, sourceIds: c.sourceIds, exampleIds: ['beispiele'] }
|
|
67
|
+
return c && { case: { tags: c.tags, data: c.data }, templateId: c.templateId, sourceIds: c.sourceIds, exampleIds: ['beispiele'], snippetIds: ['bausteine'] }
|
|
68
68
|
},
|
|
69
|
+
snippetKind: 'baustein', // block kind of the snippets in `snippetIds`; default 'snippet'
|
|
70
|
+
// sameOwner: (documentId, user) => … // optional: the `person` signal (same person as the case)
|
|
69
71
|
segmentation: {
|
|
70
72
|
rules: [{ pattern: /^Therapie/i, id: 'therapie' }], // heading → slot (confidence 1, origin `rule`)
|
|
71
73
|
tagRules: [], // text → tag, e.g. `icdAnchorRule`
|
|
@@ -92,7 +94,7 @@ export default defineEventHandler(async event => {
|
|
|
92
94
|
})
|
|
93
95
|
```
|
|
94
96
|
|
|
95
|
-
**Operations** – `material` (`{ document, block }`: the `slot` block `block` of the instance `document
|
|
97
|
+
**Operations** – `material` (`{ document, block }`: the `slot` block `block` of the instance `document` → `materialFor` of forge-compose: the facts of the case's sources for its identifier, the best snippets (`snippetIds`, blocks of `snippetKind`: content to adopt) and examples (`exampleIds`: style only), each with `score` and `parts`. Library entries are filtered (same slot, accepted, their `nur`/`nie` condition against the case tags ∪ the block's `meta.element`) and ranked by the weighted signals `tags`, `element` (the block's `meta.element`), `fall` (made from `document`), `person` (`sameOwner`, asked per origin document), length and the `inhalt` scorers `scorers`, default `[embeddingScorer]` (0 without `ports.embeddings`); the template variant the block was made from (`templateSlotOf`, by its `derivation`) is the slot those scorers see; a block that is missing or no `slot` is a 404), `split` (source block → `example` proposals with `status: 'vorschlag'`, `confidence`, `origin`, derivation `ai:split` with the SHA-256 of the body), `facts` (one `generate` call with `stage: 'facts'`; a fact for a slot the template does not have keeps an empty `slot`), `example` (adopt an instance slot into the library with the case tags ∪ its tags and element; 409 without text), `instance` (409 „Instanz existiert“ unless `force`), `classify` (slot and tags of one text by the configured labelers; chat only). Each answers `ComposeOutput<op>`; failures the caller can act on are `ComposeError` (`status` 400, 404, 409, 422 or 503, `statusCode` the same, so forge-app-actions and h3 map it).
|
|
96
98
|
|
|
97
99
|
**Learning** – `catalogOf(template, examples, vocabulary?)` builds the slot and tag catalogues the labelers read; the `example` blocks with a status other than `vorschlag` are their examples (newest first), so each acceptance improves the next assignment without a training step. `FACTS_SYSTEM` is the default system message of `facts`; `factsSystem` replaces it for a domain.
|
|
98
100
|
|
|
@@ -113,7 +115,7 @@ serverActionRegistry.registerAll(actions)
|
|
|
113
115
|
|
|
114
116
|
## Features
|
|
115
117
|
|
|
116
|
-
- **`compose-material`** – „Block füllen (KI)“ on a `slot` with an identifier asks `material` with the block id and hands the facts
|
|
118
|
+
- **`compose-material`** – „Block füllen (KI)“ on a `slot` with an identifier asks `material` with the block id and hands the facts, the best-ranked snippets (`Baustein n – übernehmen und anpassen`) and examples (`Beispiel n – nur Stil`) to the model (`aiBlockData`, at most 10 entries). The block must be stored: the server reads it from the saved document.
|
|
117
119
|
- **`compose-split`** – „In Beispiele zerlegen (KI)“ on a `source` asks `split` and inserts the returned `example` proposals right after the source in one transaction (one undo step). A proposal shows a chip `Vorschlag 0.82 · embed` (confidence · origin) in its title row until „Vorschläge annehmen“ on the source sets `status: 'geprüft'` on the proposals that follow it and came from it, and removes `confidence` and `origin`.
|
|
118
120
|
- **`compose-facts`** – „Stichpunkte erzeugen (KI)“ on a `source` asks `facts` and inserts the `fact` blocks after the source (and after the facts that already follow it).
|
|
119
121
|
- **`compose-example`** – „Als Beispiel übernehmen“ on a `slot` of an instance (one with `derivation.method === 'copy'`) asks `example`; the host appends it to its example library.
|
package/dist/server/actions.js
CHANGED
|
@@ -11,7 +11,7 @@ function composeActions(define, ops, o) {
|
|
|
11
11
|
return [
|
|
12
12
|
define({
|
|
13
13
|
id: "compose.material",
|
|
14
|
-
description: "Material eines Slots: Fakten und
|
|
14
|
+
description: "Material eines Slots: Fakten, passende Bausteine und Beispiele",
|
|
15
15
|
input: composeInputs.material,
|
|
16
16
|
kind: "query",
|
|
17
17
|
audience,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.js","names":[],"sources":["../../src/server/actions.ts"],"sourcesContent":["// The forge-app-actions projection of the compose operations, for hosts with an action catalogue. Registers nothing and mounts no\n// route: the host puts the returned actions into its catalogue (`serverActionRegistry.registerAll`) or behind its own route.\nimport type {\n DefineServerActionArgs,\n ServerActionAudience,\n ServerActionContext,\n createServerActionDefiner,\n} from '@skaile/forge-app-actions/server'\nimport type { z } from 'zod'\nimport { composeInputs } from './inputs'\nimport type { ComposeOperations } from './operations'\n\n/** The app's action definer, `createServerActionDefiner<P, D>()`. */\nexport type ComposeActionDefiner<P, D> = ReturnType<typeof createServerActionDefiner<P, D>>\n\nexport interface ComposeActionOptions<P, D> {\n /** The host's authorization, run by each action after its input parsed and before the operation. Throw to deny; an error with a numeric `statusCode` keeps its status. */\n authorize: NonNullable<DefineServerActionArgs<string, z.ZodType, P, D, unknown>['authorize']>\n /** The `user` the operations read ports with, derived from the action's context (usually its `principal`). */\n userOf(ctx: ServerActionContext<P, D>): object\n /** Who is offered the actions. Default `['user']`. */\n audience?: readonly ServerActionAudience[]\n}\n\n/**\n * The five compose operations as server actions `compose.material`, `compose.split`, `compose.facts`, `compose.example` and\n * `compose.instance`. `define` is the app's `createServerActionDefiner<P, D>()`. A `ComposeError` thrown by an operation carries\n * `statusCode`, so `invokeAction` answers its status and message.\n */\nexport function composeActions<P, D>(\n define: ComposeActionDefiner<P, D>,\n ops: ComposeOperations,\n o: ComposeActionOptions<P, D>,\n) {\n const audience = o.audience ?? ['user']\n const { authorize, userOf } = o\n return [\n define({\n id: 'compose.material',\n description: 'Material eines Slots: Fakten und
|
|
1
|
+
{"version":3,"file":"actions.js","names":[],"sources":["../../src/server/actions.ts"],"sourcesContent":["// The forge-app-actions projection of the compose operations, for hosts with an action catalogue. Registers nothing and mounts no\n// route: the host puts the returned actions into its catalogue (`serverActionRegistry.registerAll`) or behind its own route.\nimport type {\n DefineServerActionArgs,\n ServerActionAudience,\n ServerActionContext,\n createServerActionDefiner,\n} from '@skaile/forge-app-actions/server'\nimport type { z } from 'zod'\nimport { composeInputs } from './inputs'\nimport type { ComposeOperations } from './operations'\n\n/** The app's action definer, `createServerActionDefiner<P, D>()`. */\nexport type ComposeActionDefiner<P, D> = ReturnType<typeof createServerActionDefiner<P, D>>\n\nexport interface ComposeActionOptions<P, D> {\n /** The host's authorization, run by each action after its input parsed and before the operation. Throw to deny; an error with a numeric `statusCode` keeps its status. */\n authorize: NonNullable<DefineServerActionArgs<string, z.ZodType, P, D, unknown>['authorize']>\n /** The `user` the operations read ports with, derived from the action's context (usually its `principal`). */\n userOf(ctx: ServerActionContext<P, D>): object\n /** Who is offered the actions. Default `['user']`. */\n audience?: readonly ServerActionAudience[]\n}\n\n/**\n * The five compose operations as server actions `compose.material`, `compose.split`, `compose.facts`, `compose.example` and\n * `compose.instance`. `define` is the app's `createServerActionDefiner<P, D>()`. A `ComposeError` thrown by an operation carries\n * `statusCode`, so `invokeAction` answers its status and message.\n */\nexport function composeActions<P, D>(\n define: ComposeActionDefiner<P, D>,\n ops: ComposeOperations,\n o: ComposeActionOptions<P, D>,\n) {\n const audience = o.audience ?? ['user']\n const { authorize, userOf } = o\n return [\n define({\n id: 'compose.material',\n description: 'Material eines Slots: Fakten, passende Bausteine und Beispiele',\n input: composeInputs.material,\n kind: 'query',\n audience,\n authorize,\n run: (input, ctx) => ops.material(input, userOf(ctx)),\n }),\n define({\n id: 'compose.split',\n description: 'Quelle in Beispielvorschläge zerlegen',\n input: composeInputs.split,\n kind: 'effect',\n audience,\n authorize,\n run: (input, ctx) => ops.split(input, userOf(ctx)),\n }),\n define({\n id: 'compose.facts',\n description: 'Stichpunkte aus einer Quelle erzeugen',\n input: composeInputs.facts,\n kind: 'effect',\n audience,\n authorize,\n run: (input, ctx) => ops.facts(input, userOf(ctx)),\n }),\n define({\n id: 'compose.example',\n description: 'Slot als Beispiel in die Bibliothek übernehmen',\n input: composeInputs.example,\n kind: 'effect',\n audience,\n authorize,\n run: (input, ctx) => ops.example(input, userOf(ctx)),\n }),\n define({\n id: 'compose.instance',\n description: 'Instanz aus einer Vorlage anlegen',\n input: composeInputs.instance,\n kind: 'effect',\n audience,\n authorize,\n run: (input, ctx) => ops.instance(input, userOf(ctx)),\n }),\n ] as const\n}\n"],"mappings":";;;;;;;AA6BA,SAAgB,eACd,QACA,KACA,GACA;CACA,MAAM,WAAW,EAAE,YAAY,CAAC,MAAM;CACtC,MAAM,EAAE,WAAW,WAAW;CAC9B,OAAO;EACL,OAAO;GACL,IAAI;GACJ,aAAa;GACb,OAAO,cAAc;GACrB,MAAM;GACN;GACA;GACA,MAAM,OAAO,QAAQ,IAAI,SAAS,OAAO,OAAO,GAAG,CAAC;EACtD,CAAC;EACD,OAAO;GACL,IAAI;GACJ,aAAa;GACb,OAAO,cAAc;GACrB,MAAM;GACN;GACA;GACA,MAAM,OAAO,QAAQ,IAAI,MAAM,OAAO,OAAO,GAAG,CAAC;EACnD,CAAC;EACD,OAAO;GACL,IAAI;GACJ,aAAa;GACb,OAAO,cAAc;GACrB,MAAM;GACN;GACA;GACA,MAAM,OAAO,QAAQ,IAAI,MAAM,OAAO,OAAO,GAAG,CAAC;EACnD,CAAC;EACD,OAAO;GACL,IAAI;GACJ,aAAa;GACb,OAAO,cAAc;GACrB,MAAM;GACN;GACA;GACA,MAAM,OAAO,QAAQ,IAAI,QAAQ,OAAO,OAAO,GAAG,CAAC;EACrD,CAAC;EACD,OAAO;GACL,IAAI;GACJ,aAAa;GACb,OAAO,cAAc;GACrB,MAAM;GACN;GACA;GACA,MAAM,OAAO,QAAQ,IAAI,SAAS,OAAO,OAAO,GAAG,CAAC;EACtD,CAAC;CACH;AACF"}
|
package/dist/server/inputs.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare const COMPOSE_OPERATIONS: readonly ["material", "split", "facts",
|
|
|
5
5
|
export type ComposeOperationId = (typeof COMPOSE_OPERATIONS)[number];
|
|
6
6
|
/** The request body of each operation. `document` is the document that holds the block; `block` addresses inside it. */
|
|
7
7
|
export declare const composeInputs: {
|
|
8
|
-
/** Facts and ranked examples for the `slot` block `block` of the case the document belongs to. */
|
|
8
|
+
/** Facts, ranked snippets and ranked examples for the `slot` block `block` of the case the document belongs to. */
|
|
9
9
|
material: z.ZodObject<{
|
|
10
10
|
document: z.ZodString;
|
|
11
11
|
block: z.ZodString;
|
package/dist/server/inputs.js
CHANGED
|
@@ -10,7 +10,7 @@ const COMPOSE_OPERATIONS = [
|
|
|
10
10
|
const id = z.string().trim().min(1);
|
|
11
11
|
/** The request body of each operation. `document` is the document that holds the block; `block` addresses inside it. */
|
|
12
12
|
const composeInputs = {
|
|
13
|
-
/** Facts and ranked examples for the `slot` block `block` of the case the document belongs to. */
|
|
13
|
+
/** Facts, ranked snippets and ranked examples for the `slot` block `block` of the case the document belongs to. */
|
|
14
14
|
material: z.object({
|
|
15
15
|
document: id,
|
|
16
16
|
block: id
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inputs.js","names":[],"sources":["../../src/server/inputs.ts"],"sourcesContent":["// The operation contract of the compose editor features: which operations exist, what each takes and answers. Free of the AI SDK,\n// h3 and forge-app-actions, so the browser (`./vue`), a route and an action definition share one description.\nimport type { DocNode, InstanceReport, Material } from '@skaile/forge-compose/documents'\nimport { z } from 'zod'\n\nexport const COMPOSE_OPERATIONS = ['material', 'split', 'facts', 'example', 'instance'] as const\n\nexport type ComposeOperationId = (typeof COMPOSE_OPERATIONS)[number]\n\nconst id = z.string().trim().min(1)\n\n/** The request body of each operation. `document` is the document that holds the block; `block` addresses inside it. */\nexport const composeInputs = {\n /** Facts and ranked examples for the `slot` block `block` of the case the document belongs to. */\n material: z.object({ document: id, block: id }),\n /** Split a `source` block into `example` proposals. */\n split: z.object({ document: id, block: id }),\n /** Extract `fact` blocks from a `source` block. */\n facts: z.object({ document: id, block: id }),\n /** Append the `slot` block of an instance to the example library. */\n example: z.object({ document: id, block: id }),\n /** Create the instance document `instanceId` from the template `templateId`; `force` replaces an existing one. */\n instance: z.object({ templateId: id, instanceId: id, force: z.boolean().optional() }),\n} satisfies Record<ComposeOperationId, z.ZodType>\n\nexport type ComposeInput<K extends ComposeOperationId> = z.infer<(typeof composeInputs)[K]>\n\n/** The answer of each operation. Node lists are JSON of the document schema, ready to insert. */\nexport interface ComposeOutputs {\n material: Material\n split: { nodes: DocNode[] }\n facts: { nodes: DocNode[] }\n example: { libraryId: string; blockId: string }\n instance: InstanceReport\n}\n\nexport type ComposeOutput<K extends ComposeOperationId> = ComposeOutputs[K]\n\n/** Whether `value` names an operation (a route validates its path parameter with it). */\nexport const isComposeOperationId = (value: unknown): value is ComposeOperationId =>\n typeof value === 'string' && (COMPOSE_OPERATIONS as readonly string[]).includes(value)\n"],"mappings":";;AAKA,MAAa,qBAAqB;CAAC;CAAY;CAAS;CAAS;CAAW;AAAU;AAItF,MAAM,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;;AAGlC,MAAa,gBAAgB;;CAE3B,UAAU,EAAE,OAAO;EAAE,UAAU;EAAI,OAAO;CAAG,CAAC;;CAE9C,OAAO,EAAE,OAAO;EAAE,UAAU;EAAI,OAAO;CAAG,CAAC;;CAE3C,OAAO,EAAE,OAAO;EAAE,UAAU;EAAI,OAAO;CAAG,CAAC;;CAE3C,SAAS,EAAE,OAAO;EAAE,UAAU;EAAI,OAAO;CAAG,CAAC;;CAE7C,UAAU,EAAE,OAAO;EAAE,YAAY;EAAI,YAAY;EAAI,OAAO,EAAE,QAAQ,CAAC,CAAC,SAAS;CAAE,CAAC;AACtF;;AAgBA,MAAa,wBAAwB,UACnC,OAAO,UAAU,YAAa,mBAAyC,SAAS,KAAK"}
|
|
1
|
+
{"version":3,"file":"inputs.js","names":[],"sources":["../../src/server/inputs.ts"],"sourcesContent":["// The operation contract of the compose editor features: which operations exist, what each takes and answers. Free of the AI SDK,\n// h3 and forge-app-actions, so the browser (`./vue`), a route and an action definition share one description.\nimport type { DocNode, InstanceReport, Material } from '@skaile/forge-compose/documents'\nimport { z } from 'zod'\n\nexport const COMPOSE_OPERATIONS = ['material', 'split', 'facts', 'example', 'instance'] as const\n\nexport type ComposeOperationId = (typeof COMPOSE_OPERATIONS)[number]\n\nconst id = z.string().trim().min(1)\n\n/** The request body of each operation. `document` is the document that holds the block; `block` addresses inside it. */\nexport const composeInputs = {\n /** Facts, ranked snippets and ranked examples for the `slot` block `block` of the case the document belongs to. */\n material: z.object({ document: id, block: id }),\n /** Split a `source` block into `example` proposals. */\n split: z.object({ document: id, block: id }),\n /** Extract `fact` blocks from a `source` block. */\n facts: z.object({ document: id, block: id }),\n /** Append the `slot` block of an instance to the example library. */\n example: z.object({ document: id, block: id }),\n /** Create the instance document `instanceId` from the template `templateId`; `force` replaces an existing one. */\n instance: z.object({ templateId: id, instanceId: id, force: z.boolean().optional() }),\n} satisfies Record<ComposeOperationId, z.ZodType>\n\nexport type ComposeInput<K extends ComposeOperationId> = z.infer<(typeof composeInputs)[K]>\n\n/** The answer of each operation. Node lists are JSON of the document schema, ready to insert. */\nexport interface ComposeOutputs {\n material: Material\n split: { nodes: DocNode[] }\n facts: { nodes: DocNode[] }\n example: { libraryId: string; blockId: string }\n instance: InstanceReport\n}\n\nexport type ComposeOutput<K extends ComposeOperationId> = ComposeOutputs[K]\n\n/** Whether `value` names an operation (a route validates its path parameter with it). */\nexport const isComposeOperationId = (value: unknown): value is ComposeOperationId =>\n typeof value === 'string' && (COMPOSE_OPERATIONS as readonly string[]).includes(value)\n"],"mappings":";;AAKA,MAAa,qBAAqB;CAAC;CAAY;CAAS;CAAS;CAAW;AAAU;AAItF,MAAM,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;;AAGlC,MAAa,gBAAgB;;CAE3B,UAAU,EAAE,OAAO;EAAE,UAAU;EAAI,OAAO;CAAG,CAAC;;CAE9C,OAAO,EAAE,OAAO;EAAE,UAAU;EAAI,OAAO;CAAG,CAAC;;CAE3C,OAAO,EAAE,OAAO;EAAE,UAAU;EAAI,OAAO;CAAG,CAAC;;CAE3C,SAAS,EAAE,OAAO;EAAE,UAAU;EAAI,OAAO;CAAG,CAAC;;CAE7C,UAAU,EAAE,OAAO;EAAE,YAAY;EAAI,YAAY;EAAI,OAAO,EAAE,QAAQ,CAAC,CAAC,SAAS;CAAE,CAAC;AACtF;;AAgBA,MAAa,wBAAwB,UACnC,OAAO,UAAU,YAAa,mBAAyC,SAAS,KAAK"}
|
|
@@ -11,8 +11,10 @@ export interface ComposeContext {
|
|
|
11
11
|
templateId: string;
|
|
12
12
|
/** Documents whose `fact`/`source` blocks belong to the case. */
|
|
13
13
|
sourceIds: string[];
|
|
14
|
-
/** Documents searched for `example` blocks. */
|
|
14
|
+
/** Documents searched for `example` blocks (style only). */
|
|
15
15
|
exampleIds: string[] | 'all';
|
|
16
|
+
/** Documents searched for snippet blocks (`snippetKind`: content to adopt). Default none. */
|
|
17
|
+
snippetIds?: string[];
|
|
16
18
|
}
|
|
17
19
|
export interface ComposeOptions {
|
|
18
20
|
/** `documents` is required (`putDocument` for `example` and `instance`); `embeddings` is reused as the pipeline's and the scorers' `Embedder` when present. */
|
|
@@ -39,15 +41,27 @@ export interface ComposeOptions {
|
|
|
39
41
|
meta: Record<string, unknown>;
|
|
40
42
|
}): Splitter | null;
|
|
41
43
|
};
|
|
42
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Extra scorers of the `inhalt` signal, added to the built-in signals of `material` (`tags`, `element`, `fall`, `person`,
|
|
46
|
+
* length); `compose_examples` ranks with `[tagFitScorer(hierarchy), ...scorers, lengthScorer]`. Default `[embeddingScorer]`,
|
|
47
|
+
* which scores 0 without `ports.embeddings`.
|
|
48
|
+
*/
|
|
43
49
|
scorers?: Scorer[];
|
|
50
|
+
/** Block kind of the snippets in `snippetIds`; default `'snippet'`. */
|
|
51
|
+
snippetKind?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Whether a document belongs to the same person as the case (the `person` signal of `material`, asked for each origin
|
|
54
|
+
* document of the library entries); absent = the signal is 0.
|
|
55
|
+
*/
|
|
56
|
+
sameOwner?: (documentId: string, user: object) => Promise<boolean> | boolean;
|
|
44
57
|
/** System message of the `facts` operation; default `FACTS_SYSTEM` (domain-neutral). A host with a domain passes its own. */
|
|
45
58
|
factsSystem?: string;
|
|
46
59
|
}
|
|
47
60
|
export interface ComposeOperations {
|
|
48
61
|
/**
|
|
49
|
-
* Facts and ranked examples for the `slot` block `block` of `document
|
|
50
|
-
*
|
|
62
|
+
* Facts, ranked snippets and ranked examples for the `slot` block `block` of `document` (`materialFor`): the block's
|
|
63
|
+
* identifier picks facts and library entries, its `meta.element` ranks by the `element` signal, `document` by `fall`, the
|
|
64
|
+
* template variant it was made from (`templateSlotOf`) is the slot the injected scorers see.
|
|
51
65
|
*/
|
|
52
66
|
material(input: ComposeInput<'material'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'material'>>;
|
|
53
67
|
split(input: ComposeInput<'split'>, user: object, signal?: AbortSignal): Promise<ComposeOutput<'split'>>;
|
|
@@ -78,11 +78,8 @@ const modelOf = (origin, embedder) => origin === "embedding" && embedder ? embed
|
|
|
78
78
|
function composeOperations(o) {
|
|
79
79
|
const { documents } = o.ports;
|
|
80
80
|
const h = o.hierarchy;
|
|
81
|
-
const scorers = o.scorers ?? [
|
|
82
|
-
|
|
83
|
-
embeddingScorer,
|
|
84
|
-
lengthScorer
|
|
85
|
-
];
|
|
81
|
+
const scorers = o.scorers ?? [embeddingScorer];
|
|
82
|
+
const snippetKind = o.snippetKind ?? "snippet";
|
|
86
83
|
const embedderFor = (user) => {
|
|
87
84
|
const embeddings = o.ports.embeddings;
|
|
88
85
|
return embeddings && {
|
|
@@ -108,6 +105,18 @@ function composeOperations(o) {
|
|
|
108
105
|
if (context.exampleIds === "all") return (await documents.listContents()).flatMap(({ id, content }) => examplesOf(content, id));
|
|
109
106
|
return (await Promise.all(context.exampleIds.map(async (id) => examplesOf(await documents.loadContent(id), id)))).flat();
|
|
110
107
|
}
|
|
108
|
+
async function snippetsOfCase(context) {
|
|
109
|
+
return (await Promise.all((context.snippetIds ?? []).map(async (id) => examplesOf(await documents.loadContent(id), id, snippetKind)))).flat();
|
|
110
|
+
}
|
|
111
|
+
/** `o.sameOwner` asked once per origin document of `entries`, as the synchronous predicate `materialFor` takes. */
|
|
112
|
+
async function ownerCheckOf(entries, user) {
|
|
113
|
+
const sameOwner = o.sameOwner;
|
|
114
|
+
if (!sameOwner) return void 0;
|
|
115
|
+
const ids = [...new Set(entries.flatMap((entry) => entry.origin ? [entry.origin.document] : []))];
|
|
116
|
+
const answers = await Promise.all(ids.map((id) => sameOwner(id, user)));
|
|
117
|
+
const owned = new Set(ids.filter((_, index) => answers[index]));
|
|
118
|
+
return (documentId) => owned.has(documentId);
|
|
119
|
+
}
|
|
111
120
|
/** The block `blockId` of `documentId` as a typed block of `kind`; `ComposeError(404)` otherwise. */
|
|
112
121
|
async function blockOf(documentId, blockId, kind) {
|
|
113
122
|
const node = findBlockNode(await documentOf(documentId), blockId);
|
|
@@ -145,23 +154,33 @@ function composeOperations(o) {
|
|
|
145
154
|
async material({ document, block }, user, signal) {
|
|
146
155
|
const context = await contextFor(document, user);
|
|
147
156
|
const node = await blockOf(document, block, KIND.slot);
|
|
148
|
-
const [facts, examples, template] = await Promise.all([
|
|
157
|
+
const [facts, examples, snippets, template] = await Promise.all([
|
|
149
158
|
factsOfCase(context),
|
|
150
159
|
examplesOfCase(context),
|
|
160
|
+
snippetsOfCase(context),
|
|
151
161
|
documents.loadContent(context.templateId)
|
|
152
162
|
]);
|
|
153
|
-
const
|
|
163
|
+
const meta = compositeMeta(node);
|
|
164
|
+
const identifier = meta[META.identifier];
|
|
165
|
+
const rawElement = meta[META.element];
|
|
166
|
+
const element = typeof rawElement === "string" ? rawElement.trim() : "";
|
|
154
167
|
const slotBlock = template ? templateSlotOf(node, template) : void 0;
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
...
|
|
158
|
-
tags:
|
|
159
|
-
|
|
168
|
+
const target = {
|
|
169
|
+
identifier: typeof identifier === "string" ? identifier : "",
|
|
170
|
+
...element ? { element } : {},
|
|
171
|
+
tags: compositeTags(node),
|
|
172
|
+
document,
|
|
173
|
+
...slotBlock && { slot: slotBlock }
|
|
174
|
+
};
|
|
160
175
|
const embedder = embedderFor(user);
|
|
161
|
-
|
|
176
|
+
const sameOwner = await ownerCheckOf([...examples, ...snippets], user);
|
|
177
|
+
return materialFor(target, context.case, facts, {
|
|
178
|
+
examples,
|
|
179
|
+
snippets
|
|
180
|
+
}, {
|
|
162
181
|
scorers,
|
|
163
182
|
...h && { h },
|
|
164
|
-
...
|
|
183
|
+
...sameOwner && { sameOwner },
|
|
165
184
|
ctx: {
|
|
166
185
|
...embedder && { embedder },
|
|
167
186
|
...signal && { signal }
|
|
@@ -348,7 +367,11 @@ function composeOperations(o) {
|
|
|
348
367
|
case: profile,
|
|
349
368
|
slot: slotBlock,
|
|
350
369
|
facts
|
|
351
|
-
},
|
|
370
|
+
}, [
|
|
371
|
+
tagFitScorer(h),
|
|
372
|
+
...scorers,
|
|
373
|
+
lengthScorer
|
|
374
|
+
], {
|
|
352
375
|
...embedder && { embedder },
|
|
353
376
|
...signal && { signal }
|
|
354
377
|
});
|
|
@@ -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, 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. */\n exampleIds: string[] | 'all'\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 /** Default `[tagFitScorer(hierarchy), embeddingScorer, lengthScorer]`; `embeddingScorer` scores 0 without `ports.embeddings`. */\n scorers?: Scorer[]\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 and ranked examples for the `slot` block `block` of `document`: its identifier picks facts and examples, the template\n * variant it was made from (`templateSlotOf`) is the slot the scorers see, its tags join the case tags for ranking.\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 the configured scorers, 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 ?? [tagFitScorer(h), embeddingScorer, lengthScorer]\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 /** 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, template] = await Promise.all([\n factsOfCase(context),\n examplesOfCase(context),\n documents.loadContent(context.templateId),\n ])\n const identifier = compositeMeta(node)[META.identifier]\n const slotBlock = template ? templateSlotOf(node, template) : undefined\n const blockTags = compositeTags(node)\n const profile: CaseProfile = blockTags.length > 0\n ? { ...context.case, tags: [...new Set([...context.case.tags, ...blockTags])] }\n : context.case\n const embedder = embedderFor(user)\n return materialFor(typeof identifier === 'string' ? identifier : '', profile, facts, examples, {\n scorers,\n ...(h && { h }),\n ...(slotBlock && { slot: slotBlock }),\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 }, scorers, {\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, 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\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 a document belongs to the same person as the case (the `person` signal of `material`, asked for each origin\n * document of the library entries); absent = the signal is 0.\n */\n sameOwner?: (documentId: 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, ranked snippets and ranked examples for the `slot` block `block` of `document` (`materialFor`): the block's\n * identifier picks facts and library entries, its `meta.element` ranks by the `element` signal, `document` by `fall`, the\n * template variant it was made from (`templateSlotOf`) is the slot the injected scorers see.\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`, as the synchronous predicate `materialFor` takes. */\n async function ownerCheckOf(entries: readonly ExampleBlock[], user: object): 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, user)))\n const owned = new Set(ids.filter((_, index) => answers[index]))\n return documentId => owned.has(documentId)\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, template] = await Promise.all([\n factsOfCase(context),\n examplesOfCase(context),\n snippetsOfCase(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], user)\n return materialFor(target, context.case, facts, { examples, snippets }, {\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":";;;;;;;AA+FA,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,MAAM,CAAC;AACtG;;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,aAAa,SAAkC,MAAsE;EAClI,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,IAAI,CAAC,CAAC;EACpE,MAAM,QAAQ,IAAI,IAAI,IAAI,QAAQ,GAAG,UAAU,QAAQ,MAAM,CAAC;EAC9D,QAAO,eAAc,MAAM,IAAI,UAAU;CAC3C;;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,YAAY,MAAM,QAAQ,IAAI;IAC9D,YAAY,OAAO;IACnB,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,IAAI;GACrE,OAAO,YAAY,QAAQ,QAAQ,MAAM,OAAO;IAAE;IAAU;GAAS,GAAG;IACtE;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/dist/server/tools.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ToolProvider } from "@skaile/forge-editor/server";
|
|
|
3
3
|
//#region src/server/tools.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* The read-only chat tools of the compose operations: `compose_slots` (the open document's slots with their block ids),
|
|
6
|
-
* `compose_material` (facts and
|
|
6
|
+
* `compose_material` (facts, ranked snippets and examples of a slot block), `compose_examples` (examples by slot, tags and a free query) and `compose_classify`
|
|
7
7
|
* (which slot and tags a text belongs to). `ops` must be what `composeOperations` returned. Pass the result as
|
|
8
8
|
* `tools` of `provideEditorServer`; the tools use `ToolContext.documentId` (forge-editor ≥ 6.2), `user` and `signal`.
|
|
9
9
|
*/
|
package/dist/server/tools.js
CHANGED
|
@@ -65,7 +65,7 @@ async function slotCatalog(support, ctx, documentId) {
|
|
|
65
65
|
}
|
|
66
66
|
/**
|
|
67
67
|
* The read-only chat tools of the compose operations: `compose_slots` (the open document's slots with their block ids),
|
|
68
|
-
* `compose_material` (facts and
|
|
68
|
+
* `compose_material` (facts, ranked snippets and examples of a slot block), `compose_examples` (examples by slot, tags and a free query) and `compose_classify`
|
|
69
69
|
* (which slot and tags a text belongs to). `ops` must be what `composeOperations` returned. Pass the result as
|
|
70
70
|
* `tools` of `provideEditorServer`; the tools use `ToolContext.documentId` (forge-editor ≥ 6.2), `user` and `signal`.
|
|
71
71
|
*/
|
|
@@ -81,7 +81,7 @@ function composeTools(ops) {
|
|
|
81
81
|
}),
|
|
82
82
|
provider({
|
|
83
83
|
name: "compose_material",
|
|
84
|
-
description: "Liefert das Material für einen Slot-Block des geöffneten Dokuments (Block-Id aus compose_slots): die Fakten des Falls und
|
|
84
|
+
description: "Liefert das Material für einen Slot-Block des geöffneten Dokuments (Block-Id aus compose_slots): die Fakten des Falls, die passenden Bausteine (Inhalt zum Übernehmen und Anpassen) und die passenden geprüften Beispiele (nur Stil, nie übernehmen), jeweils mit Bewertung und ihren Teilwerten. Das Material ist die Grundlage für den Text des Slots.",
|
|
85
85
|
schema: inputs.material,
|
|
86
86
|
expected: "{ \"block\": \"Block-Id des Slots\" }",
|
|
87
87
|
async run({ block }, ctx, documentId) {
|
|
@@ -95,15 +95,22 @@ function composeTools(ops) {
|
|
|
95
95
|
quelle: fact.source,
|
|
96
96
|
text: cut(fact.text)
|
|
97
97
|
})),
|
|
98
|
+
bausteine: material.snippets.map((snippet) => ({
|
|
99
|
+
titel: snippet.title,
|
|
100
|
+
tags: snippet.tags,
|
|
101
|
+
wert: round(snippet.score),
|
|
102
|
+
teile: snippet.parts,
|
|
103
|
+
text: cut(snippet.text)
|
|
104
|
+
})),
|
|
98
105
|
beispiele: material.examples.map((example) => ({
|
|
99
106
|
wert: round(example.score),
|
|
100
107
|
teile: example.parts,
|
|
101
108
|
text: cut(example.text)
|
|
102
109
|
}))
|
|
103
110
|
};
|
|
104
|
-
return JSON.stringify(answer.fakten.length === 0 && answer.beispiele.length === 0 ? {
|
|
111
|
+
return JSON.stringify(answer.fakten.length === 0 && answer.bausteine.length === 0 && answer.beispiele.length === 0 ? {
|
|
105
112
|
...answer,
|
|
106
|
-
hinweis: "Für diesen Slot gibt es weder Fakten noch passende Beispiele."
|
|
113
|
+
hinweis: "Für diesen Slot gibt es weder Fakten noch passende Bausteine oder Beispiele."
|
|
107
114
|
} : answer);
|
|
108
115
|
}
|
|
109
116
|
}),
|
package/dist/server/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","names":[],"sources":["../../src/server/tools.ts"],"sourcesContent":["// i18n-ignore-file: model-facing tool descriptions and results (German, like the chat's system prompt)\n// The compose operations as read-only tools of the document chat (`ToolProvider` of forge-editor): what slots the open\n// document has, which material and examples a slot gets, and where a text belongs. `edit_document` stays the chat's own\n// tool; nothing here writes. The tool definitions carry zod schemas and no `execute`, so this entry never imports the AI SDK.\nimport { z } from 'zod'\nimport type { ToolContext, ToolProvider } from '@skaile/forge-editor/server'\nimport { bodyText, slotsOf } from '@skaile/forge-compose/documents'\nimport { ComposeError } from './errors'\nimport { composeToolSupportOf } from './operations'\nimport type { ComposeOperations, ComposeToolSupport } from './operations'\n\n/** Characters of an example text a tool answer carries. */\nexport const TOOL_TEXT_LIMIT = 2000\n/** Examples `compose_examples` answers when `limit` is not given, and the most it answers. */\nconst DEFAULT_EXAMPLES = 5\nconst MAX_EXAMPLES = 20\n\nconst text = z.string().trim().min(1)\n\nconst inputs = {\n material: z.object({ block: text }),\n examples: z.object({\n slot: text.optional(),\n tags: z.array(text).optional(),\n query: z.string().optional(),\n limit: z.number().int().min(1).max(MAX_EXAMPLES).optional(),\n }),\n slots: z.object({}),\n classify: z.object({ text }),\n}\n\nconst cut = (value: string): string => (value.length > TOOL_TEXT_LIMIT ? `${value.slice(0, TOOL_TEXT_LIMIT)}…` : value)\nconst round = (value: number): number => Math.round(value * 1000) / 1000\n\n/** A refusal the model can act on: a German sentence naming the tool, the problem and the expected shape. */\nfunction invalid(name: string, expected: string, error: z.ZodError): string {\n const problems = error.issues.map(issue => `${issue.path.join('.') || 'Eingabe'}: ${issue.message}`).join('; ')\n return `Ungültige Eingabe für ${name} (${problems}). Erwartet: ${expected}.`\n}\n\ninterface ToolSpec<S extends z.ZodType> {\n name: string\n description: string\n schema: S\n /** The expected input as the model reads it in a refusal. */\n expected: string\n run(input: z.infer<S>, ctx: ToolContext, documentId: string): Promise<string>\n}\n\n/**\n * One provider: the open document comes from the chat (`ctx.documentId`), the input is validated here and a refusal of the\n * operations (`ComposeError`: no case, nothing found) is answered as text so the model can react; other failures throw.\n */\nfunction provider<S extends z.ZodType>(spec: ToolSpec<S>): ToolProvider {\n return {\n name: spec.name,\n tool: { description: spec.description, inputSchema: spec.schema },\n async handle(ctx, input) {\n if (!ctx.documentId) return 'Kein Dokument geöffnet'\n const parsed = spec.schema.safeParse(input ?? {})\n if (!parsed.success) return invalid(spec.name, spec.expected, parsed.error)\n try {\n return await spec.run(parsed.data, ctx, ctx.documentId)\n }\n catch (error) {\n if (error instanceof ComposeError) return error.message\n throw error\n }\n },\n }\n}\n\n/** The slots of the open document (live content first), with whether each holds text. */\nasync function slotCatalog(support: ComposeToolSupport, ctx: ToolContext, documentId: string): Promise<string> {\n const doc = ctx.liveContent(documentId) ?? (await support.load(documentId))\n if (!doc) return `Das Dokument „${documentId}“ existiert nicht.`\n const slots = slotsOf(doc)\n if (slots.length === 0) return 'Das Dokument enthält keine Slots.'\n return JSON.stringify({\n slots: slots.map(slot => ({\n block: slot.id,\n bezeichner: slot.identifier,\n titel: slot.title,\n gefuellt: bodyText(slot.node).trim().length > 0,\n })),\n })\n}\n\n/**\n * The read-only chat tools of the compose operations: `compose_slots` (the open document's slots with their block ids),\n * `compose_material` (facts and ranked examples of a slot block), `compose_examples` (examples by slot, tags and a free query) and `compose_classify`\n * (which slot and tags a text belongs to). `ops` must be what `composeOperations` returned. Pass the result as\n * `tools` of `provideEditorServer`; the tools use `ToolContext.documentId` (forge-editor ≥ 6.2), `user` and `signal`.\n */\nexport function composeTools(ops: ComposeOperations): ToolProvider[] {\n const support = composeToolSupportOf(ops)\n return [\n provider({\n name: 'compose_slots',\n description:\n 'Listet die Slots (Textabschnitte) des geöffneten Dokuments mit Block-Id, Bezeichner, Titel und ob schon Text darin steht. ' +\n 'Nutze die Block-Id für compose_material und den Bezeichner für compose_examples.',\n schema: inputs.slots,\n expected: '{}',\n run: (_input, ctx, documentId) => slotCatalog(support, ctx, documentId),\n }),\n provider({\n name: 'compose_material',\n description:\n 'Liefert das Material für einen Slot-Block des geöffneten Dokuments (Block-Id aus compose_slots): die Fakten des Falls ' +\n 'und die am besten passenden geprüften Beispiele (mit Bewertung). Das Material ist die Grundlage für den Text des Slots.',\n schema: inputs.material,\n expected: '{ \"block\": \"Block-Id des Slots\" }',\n async run({ block }, ctx, documentId) {\n const material = await ops.material({ document: documentId, block }, ctx.user, ctx.signal)\n const answer = {\n block,\n fakten: material.facts.map(fact => ({ quelle: fact.source, text: cut(fact.text) })),\n beispiele: material.examples.map(example => ({ wert: round(example.score), teile: example.parts, text: cut(example.text) })),\n }\n return JSON.stringify(\n answer.fakten.length === 0 && answer.beispiele.length === 0\n ? { ...answer, hinweis: 'Für diesen Slot gibt es weder Fakten noch passende Beispiele.' }\n : answer,\n )\n },\n }),\n provider({\n name: 'compose_examples',\n description:\n 'Sucht geprüfte Beispieltexte aus der Beispielbibliothek des Falls. Optional eingeschränkt auf einen Slot (slot), ' +\n 'auf Tags (tags, alle müssen zutreffen) und nach Nähe zu einer Freitextfrage (query) sortiert. Beste zuerst.',\n schema: inputs.examples,\n expected: '{ \"slot\"?: \"Bezeichner\", \"tags\"?: [\"facette/wert\"], \"query\"?: \"Text\", \"limit\"?: 1-20 }',\n async run({ slot, tags, query, limit }, ctx, documentId) {\n const found = await support.examples(\n {\n document: documentId,\n ...(slot && { slot }),\n ...(tags && { tags }),\n ...(query && { query }),\n limit: Math.min(MAX_EXAMPLES, limit ?? DEFAULT_EXAMPLES),\n },\n ctx.user,\n ctx.signal,\n )\n if (found.length === 0) return 'Keine passenden Beispiele gefunden.'\n return JSON.stringify({\n beispiele: found.map(example => ({\n slot: example.slot,\n tags: example.tags,\n wert: round(example.score),\n teile: example.parts,\n text: cut(example.text),\n })),\n })\n },\n }),\n provider({\n name: 'compose_classify',\n description:\n 'Ordnet einen Text dem passenden Slot der Vorlage zu und schlägt Tags vor (Regeln, Beispiele, Modell). ' +\n 'Nutze es für „Wohin gehört dieser Absatz?“. slot ist null, wenn kein Slot sicher passt.',\n schema: inputs.classify,\n expected: '{ \"text\": \"der zuzuordnende Text\" }',\n async run({ text: value }, ctx, documentId) {\n const decision = await ops.classify({ document: documentId, text: value }, ctx.user, ctx.signal)\n return JSON.stringify({\n slot: decision.slot,\n tags: decision.tags,\n sicherheit: round(decision.confidence),\n herkunft: decision.origin,\n })\n },\n }),\n ]\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tools.js","names":[],"sources":["../../src/server/tools.ts"],"sourcesContent":["// i18n-ignore-file: model-facing tool descriptions and results (German, like the chat's system prompt)\n// The compose operations as read-only tools of the document chat (`ToolProvider` of forge-editor): what slots the open\n// document has, which material and examples a slot gets, and where a text belongs. `edit_document` stays the chat's own\n// tool; nothing here writes. The tool definitions carry zod schemas and no `execute`, so this entry never imports the AI SDK.\nimport { z } from 'zod'\nimport type { ToolContext, ToolProvider } from '@skaile/forge-editor/server'\nimport { bodyText, slotsOf } from '@skaile/forge-compose/documents'\nimport { ComposeError } from './errors'\nimport { composeToolSupportOf } from './operations'\nimport type { ComposeOperations, ComposeToolSupport } from './operations'\n\n/** Characters of an example text a tool answer carries. */\nexport const TOOL_TEXT_LIMIT = 2000\n/** Examples `compose_examples` answers when `limit` is not given, and the most it answers. */\nconst DEFAULT_EXAMPLES = 5\nconst MAX_EXAMPLES = 20\n\nconst text = z.string().trim().min(1)\n\nconst inputs = {\n material: z.object({ block: text }),\n examples: z.object({\n slot: text.optional(),\n tags: z.array(text).optional(),\n query: z.string().optional(),\n limit: z.number().int().min(1).max(MAX_EXAMPLES).optional(),\n }),\n slots: z.object({}),\n classify: z.object({ text }),\n}\n\nconst cut = (value: string): string => (value.length > TOOL_TEXT_LIMIT ? `${value.slice(0, TOOL_TEXT_LIMIT)}…` : value)\nconst round = (value: number): number => Math.round(value * 1000) / 1000\n\n/** A refusal the model can act on: a German sentence naming the tool, the problem and the expected shape. */\nfunction invalid(name: string, expected: string, error: z.ZodError): string {\n const problems = error.issues.map(issue => `${issue.path.join('.') || 'Eingabe'}: ${issue.message}`).join('; ')\n return `Ungültige Eingabe für ${name} (${problems}). Erwartet: ${expected}.`\n}\n\ninterface ToolSpec<S extends z.ZodType> {\n name: string\n description: string\n schema: S\n /** The expected input as the model reads it in a refusal. */\n expected: string\n run(input: z.infer<S>, ctx: ToolContext, documentId: string): Promise<string>\n}\n\n/**\n * One provider: the open document comes from the chat (`ctx.documentId`), the input is validated here and a refusal of the\n * operations (`ComposeError`: no case, nothing found) is answered as text so the model can react; other failures throw.\n */\nfunction provider<S extends z.ZodType>(spec: ToolSpec<S>): ToolProvider {\n return {\n name: spec.name,\n tool: { description: spec.description, inputSchema: spec.schema },\n async handle(ctx, input) {\n if (!ctx.documentId) return 'Kein Dokument geöffnet'\n const parsed = spec.schema.safeParse(input ?? {})\n if (!parsed.success) return invalid(spec.name, spec.expected, parsed.error)\n try {\n return await spec.run(parsed.data, ctx, ctx.documentId)\n }\n catch (error) {\n if (error instanceof ComposeError) return error.message\n throw error\n }\n },\n }\n}\n\n/** The slots of the open document (live content first), with whether each holds text. */\nasync function slotCatalog(support: ComposeToolSupport, ctx: ToolContext, documentId: string): Promise<string> {\n const doc = ctx.liveContent(documentId) ?? (await support.load(documentId))\n if (!doc) return `Das Dokument „${documentId}“ existiert nicht.`\n const slots = slotsOf(doc)\n if (slots.length === 0) return 'Das Dokument enthält keine Slots.'\n return JSON.stringify({\n slots: slots.map(slot => ({\n block: slot.id,\n bezeichner: slot.identifier,\n titel: slot.title,\n gefuellt: bodyText(slot.node).trim().length > 0,\n })),\n })\n}\n\n/**\n * The read-only chat tools of the compose operations: `compose_slots` (the open document's slots with their block ids),\n * `compose_material` (facts, ranked snippets and examples of a slot block), `compose_examples` (examples by slot, tags and a free query) and `compose_classify`\n * (which slot and tags a text belongs to). `ops` must be what `composeOperations` returned. Pass the result as\n * `tools` of `provideEditorServer`; the tools use `ToolContext.documentId` (forge-editor ≥ 6.2), `user` and `signal`.\n */\nexport function composeTools(ops: ComposeOperations): ToolProvider[] {\n const support = composeToolSupportOf(ops)\n return [\n provider({\n name: 'compose_slots',\n description:\n 'Listet die Slots (Textabschnitte) des geöffneten Dokuments mit Block-Id, Bezeichner, Titel und ob schon Text darin steht. ' +\n 'Nutze die Block-Id für compose_material und den Bezeichner für compose_examples.',\n schema: inputs.slots,\n expected: '{}',\n run: (_input, ctx, documentId) => slotCatalog(support, ctx, documentId),\n }),\n provider({\n name: 'compose_material',\n description:\n 'Liefert das Material für einen Slot-Block des geöffneten Dokuments (Block-Id aus compose_slots): die Fakten des Falls, ' +\n 'die passenden Bausteine (Inhalt zum Übernehmen und Anpassen) und die passenden geprüften Beispiele (nur Stil, nie ' +\n 'übernehmen), jeweils mit Bewertung und ihren Teilwerten. Das Material ist die Grundlage für den Text des Slots.',\n schema: inputs.material,\n expected: '{ \"block\": \"Block-Id des Slots\" }',\n async run({ block }, ctx, documentId) {\n const material = await ops.material({ document: documentId, block }, ctx.user, ctx.signal)\n const answer = {\n block,\n fakten: material.facts.map(fact => ({ quelle: fact.source, text: cut(fact.text) })),\n bausteine: material.snippets.map(snippet => ({\n titel: snippet.title,\n tags: snippet.tags,\n wert: round(snippet.score),\n teile: snippet.parts,\n text: cut(snippet.text),\n })),\n beispiele: material.examples.map(example => ({ wert: round(example.score), teile: example.parts, text: cut(example.text) })),\n }\n return JSON.stringify(\n answer.fakten.length === 0 && answer.bausteine.length === 0 && answer.beispiele.length === 0\n ? { ...answer, hinweis: 'Für diesen Slot gibt es weder Fakten noch passende Bausteine oder Beispiele.' }\n : answer,\n )\n },\n }),\n provider({\n name: 'compose_examples',\n description:\n 'Sucht geprüfte Beispieltexte aus der Beispielbibliothek des Falls. Optional eingeschränkt auf einen Slot (slot), ' +\n 'auf Tags (tags, alle müssen zutreffen) und nach Nähe zu einer Freitextfrage (query) sortiert. Beste zuerst.',\n schema: inputs.examples,\n expected: '{ \"slot\"?: \"Bezeichner\", \"tags\"?: [\"facette/wert\"], \"query\"?: \"Text\", \"limit\"?: 1-20 }',\n async run({ slot, tags, query, limit }, ctx, documentId) {\n const found = await support.examples(\n {\n document: documentId,\n ...(slot && { slot }),\n ...(tags && { tags }),\n ...(query && { query }),\n limit: Math.min(MAX_EXAMPLES, limit ?? DEFAULT_EXAMPLES),\n },\n ctx.user,\n ctx.signal,\n )\n if (found.length === 0) return 'Keine passenden Beispiele gefunden.'\n return JSON.stringify({\n beispiele: found.map(example => ({\n slot: example.slot,\n tags: example.tags,\n wert: round(example.score),\n teile: example.parts,\n text: cut(example.text),\n })),\n })\n },\n }),\n provider({\n name: 'compose_classify',\n description:\n 'Ordnet einen Text dem passenden Slot der Vorlage zu und schlägt Tags vor (Regeln, Beispiele, Modell). ' +\n 'Nutze es für „Wohin gehört dieser Absatz?“. slot ist null, wenn kein Slot sicher passt.',\n schema: inputs.classify,\n expected: '{ \"text\": \"der zuzuordnende Text\" }',\n async run({ text: value }, ctx, documentId) {\n const decision = await ops.classify({ document: documentId, text: value }, ctx.user, ctx.signal)\n return JSON.stringify({\n slot: decision.slot,\n tags: decision.tags,\n sicherheit: round(decision.confidence),\n herkunft: decision.origin,\n })\n },\n }),\n ]\n}\n"],"mappings":";;;;;;AAYA,MAAa,kBAAkB;;AAE/B,MAAM,mBAAmB;AACzB,MAAM,eAAe;AAErB,MAAM,OAAO,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;AAEpC,MAAM,SAAS;CACb,UAAU,EAAE,OAAO,EAAE,OAAO,KAAK,CAAC;CAClC,UAAU,EAAE,OAAO;EACjB,MAAM,KAAK,SAAS;EACpB,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC,SAAS;EAC7B,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;EAC3B,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,SAAS;CAC5D,CAAC;CACD,OAAO,EAAE,OAAO,CAAC,CAAC;CAClB,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC;AAC7B;AAEA,MAAM,OAAO,UAA2B,MAAM,SAAA,MAA2B,GAAG,MAAM,MAAM,GAAG,eAAe,EAAE,KAAK;AACjH,MAAM,SAAS,UAA0B,KAAK,MAAM,QAAQ,GAAI,IAAI;;AAGpE,SAAS,QAAQ,MAAc,UAAkB,OAA2B;CAE1E,OAAO,yBAAyB,KAAK,IADpB,MAAM,OAAO,KAAI,UAAS,GAAG,MAAM,KAAK,KAAK,GAAG,KAAK,UAAU,IAAI,MAAM,SAAS,CAAC,CAAC,KAAK,IAC1D,EAAE,eAAe,SAAS;AAC5E;;;;;AAeA,SAAS,SAA8B,MAAiC;CACtE,OAAO;EACL,MAAM,KAAK;EACX,MAAM;GAAE,aAAa,KAAK;GAAa,aAAa,KAAK;EAAO;EAChE,MAAM,OAAO,KAAK,OAAO;GACvB,IAAI,CAAC,IAAI,YAAY,OAAO;GAC5B,MAAM,SAAS,KAAK,OAAO,UAAU,SAAS,CAAC,CAAC;GAChD,IAAI,CAAC,OAAO,SAAS,OAAO,QAAQ,KAAK,MAAM,KAAK,UAAU,OAAO,KAAK;GAC1E,IAAI;IACF,OAAO,MAAM,KAAK,IAAI,OAAO,MAAM,KAAK,IAAI,UAAU;GACxD,SACO,OAAO;IACZ,IAAI,iBAAiB,cAAc,OAAO,MAAM;IAChD,MAAM;GACR;EACF;CACF;AACF;;AAGA,eAAe,YAAY,SAA6B,KAAkB,YAAqC;CAC7G,MAAM,MAAM,IAAI,YAAY,UAAU,KAAM,MAAM,QAAQ,KAAK,UAAU;CACzE,IAAI,CAAC,KAAK,OAAO,iBAAiB,WAAW;CAC7C,MAAM,QAAQ,QAAQ,GAAG;CACzB,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,OAAO,KAAK,UAAU,EACpB,OAAO,MAAM,KAAI,UAAS;EACxB,OAAO,KAAK;EACZ,YAAY,KAAK;EACjB,OAAO,KAAK;EACZ,UAAU,SAAS,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS;CAChD,EAAE,EACJ,CAAC;AACH;;;;;;;AAQA,SAAgB,aAAa,KAAwC;CACnE,MAAM,UAAU,qBAAqB,GAAG;CACxC,OAAO;EACL,SAAS;GACP,MAAM;GACN,aACE;GAEF,QAAQ,OAAO;GACf,UAAU;GACV,MAAM,QAAQ,KAAK,eAAe,YAAY,SAAS,KAAK,UAAU;EACxE,CAAC;EACD,SAAS;GACP,MAAM;GACN,aACE;GAGF,QAAQ,OAAO;GACf,UAAU;GACV,MAAM,IAAI,EAAE,SAAS,KAAK,YAAY;IACpC,MAAM,WAAW,MAAM,IAAI,SAAS;KAAE,UAAU;KAAY;IAAM,GAAG,IAAI,MAAM,IAAI,MAAM;IACzF,MAAM,SAAS;KACb;KACA,QAAQ,SAAS,MAAM,KAAI,UAAS;MAAE,QAAQ,KAAK;MAAQ,MAAM,IAAI,KAAK,IAAI;KAAE,EAAE;KAClF,WAAW,SAAS,SAAS,KAAI,aAAY;MAC3C,OAAO,QAAQ;MACf,MAAM,QAAQ;MACd,MAAM,MAAM,QAAQ,KAAK;MACzB,OAAO,QAAQ;MACf,MAAM,IAAI,QAAQ,IAAI;KACxB,EAAE;KACF,WAAW,SAAS,SAAS,KAAI,aAAY;MAAE,MAAM,MAAM,QAAQ,KAAK;MAAG,OAAO,QAAQ;MAAO,MAAM,IAAI,QAAQ,IAAI;KAAE,EAAE;IAC7H;IACA,OAAO,KAAK,UACV,OAAO,OAAO,WAAW,KAAK,OAAO,UAAU,WAAW,KAAK,OAAO,UAAU,WAAW,IACvF;KAAE,GAAG;KAAQ,SAAS;IAA+E,IACrG,MACN;GACF;EACF,CAAC;EACD,SAAS;GACP,MAAM;GACN,aACE;GAEF,QAAQ,OAAO;GACf,UAAU;GACV,MAAM,IAAI,EAAE,MAAM,MAAM,OAAO,SAAS,KAAK,YAAY;IACvD,MAAM,QAAQ,MAAM,QAAQ,SAC1B;KACE,UAAU;KACV,GAAI,QAAQ,EAAE,KAAK;KACnB,GAAI,QAAQ,EAAE,KAAK;KACnB,GAAI,SAAS,EAAE,MAAM;KACrB,OAAO,KAAK,IAAI,cAAc,SAAS,gBAAgB;IACzD,GACA,IAAI,MACJ,IAAI,MACN;IACA,IAAI,MAAM,WAAW,GAAG,OAAO;IAC/B,OAAO,KAAK,UAAU,EACpB,WAAW,MAAM,KAAI,aAAY;KAC/B,MAAM,QAAQ;KACd,MAAM,QAAQ;KACd,MAAM,MAAM,QAAQ,KAAK;KACzB,OAAO,QAAQ;KACf,MAAM,IAAI,QAAQ,IAAI;IACxB,EAAE,EACJ,CAAC;GACH;EACF,CAAC;EACD,SAAS;GACP,MAAM;GACN,aACE;GAEF,QAAQ,OAAO;GACf,UAAU;GACV,MAAM,IAAI,EAAE,MAAM,SAAS,KAAK,YAAY;IAC1C,MAAM,WAAW,MAAM,IAAI,SAAS;KAAE,UAAU;KAAY,MAAM;IAAM,GAAG,IAAI,MAAM,IAAI,MAAM;IAC/F,OAAO,KAAK,UAAU;KACpB,MAAM,SAAS;KACf,MAAM,SAAS;KACf,YAAY,MAAM,SAAS,UAAU;KACrC,UAAU,SAAS;IACrB,CAAC;GACH;EACF,CAAC;CACH;AACF"}
|
package/dist/vue/features.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features.js","names":["TYPED_BLOCK","blockIdOf"],"sources":["../../src/vue/features.ts"],"sourcesContent":["// Editor side of the compose operations. The features call the host through one injected function (`ComposeInvoke`), so a host\n// picks its transport (own routes, the forge-app-actions HTTP projection, anything else); the package mounts no route.\n//\n// Where the data comes from: the operations read the stored document (`documentId` plus a block id), so an edit the debounced\n// save has not reached yet is not seen by them. Where the result goes: the editor, in one transaction per result (one undo step);\n// the server writes nothing for `split` and `facts`.\nimport type { VueFeature } from '@skaile/forge-editor/vue'\nimport { bodyMarkdown } from '@skaile/forge-editor/composites'\nimport { BLOCK_TYPE_ATTR, derivationOf, fetchErrorMessage, metaOf, TYPED_BLOCK } from '@skaile/forge-editor/core'\nimport { blockIdOf, findBlockById } from '@skaile/forge-editor/core/schema'\nimport { KIND, MAX_DATA_ENTRIES, MAX_DATA_TEXT, META, materialToData, METHOD, STATUS } from '@skaile/forge-compose/documents'\nimport type { DocNode, InstanceReport } from '@skaile/forge-compose/documents'\nimport type { Editor, JSONContent } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport type { ComposeInput, ComposeOperationId, ComposeOutput } from '../server'\n\n/** Calls one compose operation on the host. Rejects with the transport's error; the features show its message in a notice. */\nexport type ComposeInvoke = <K extends ComposeOperationId>(id: K, input: ComposeInput<K>) => Promise<ComposeOutput<K>>\n\nexport interface ComposeFeaturesOptions {\n /** The document the editor shows; read at call time, so a host can change it without rebuilding the features. */\n documentId: () => string\n invoke: ComposeInvoke\n}\n\nexport interface HttpInvokeOptions {\n /** Route prefix; default `/api/compose`. */\n base?: string\n /** `$fetch` (Nuxt) or a thin `fetch` wrapper that parses the JSON answer; a non-2xx status must reject (as `$fetch` does). */\n fetch: (url: string, init: { method: 'POST'; body: unknown }) => Promise<unknown>\n}\n\n/** `POST {base}/{id}` with the input as JSON body. */\nexport function httpInvoke({ base = '/api/compose', fetch }: HttpInvokeOptions): ComposeInvoke {\n const prefix = base.replace(/\\/+$/, '')\n return ((id: ComposeOperationId, input: unknown) => fetch(`${prefix}/${id}`, { method: 'POST', body: input })) as ComposeInvoke\n}\n\n/** Creates the instance document `input.instanceId` from the template `input.templateId` for its case (not a feature: a page calls it). */\nexport function createInstance(invoke: ComposeInvoke, input: ComposeInput<'instance'>): Promise<InstanceReport> {\n return invoke('instance', input)\n}\n\nconst isKind = (node: PMNode | null | undefined, kind: string): node is PMNode =>\n node?.type.name === TYPED_BLOCK && node.attrs[BLOCK_TYPE_ATTR] === kind\n\n/** Label of the slot's skeleton among the `aiBlockData` entries (at most 100 characters). */\nconst SKELETON_LABEL = 'Skelett (Zieltext: Struktur und Reihenfolge beibehalten, {{…}} = freie Stellen füllen)'\n\nconst stringMeta = (node: PMNode, key: string): string => {\n const value = metaOf(node)[key]\n return typeof value === 'string' ? value.trim() : ''\n}\n\n/** Message of a failed call: the server's own (`data.message`, `data.statusMessage` of an ofetch error), else the error's. */\nfunction messageOf(error: unknown): string {\n const data = (error as { data?: { message?: unknown; statusMessage?: unknown } } | null)?.data\n for (const candidate of [data?.message, data?.statusMessage]) if (typeof candidate === 'string' && candidate) return candidate\n return fetchErrorMessage(error)\n}\n\n/** The siblings after the Typed block `node` at `pos`, in order, each with its position. */\nfunction siblingsAfter(editor: Editor, pos: number, node: PMNode): { node: PMNode; pos: number }[] {\n const after = pos + node.nodeSize\n const $after = editor.state.doc.resolve(after)\n const out: { node: PMNode; pos: number }[] = []\n let at = after\n for (let i = $after.index(); i < $after.parent.childCount; i++) {\n const child = $after.parent.child(i)\n out.push({ node: child, pos: at })\n at += child.nodeSize\n }\n return out\n}\n\n/** Example proposals of `sourceId` among the siblings that directly follow the source (a run of `example` and `fact` blocks). */\nfunction proposalsAfter(editor: Editor, pos: number, node: PMNode, sourceId: string): { node: PMNode; pos: number }[] {\n const out: { node: PMNode; pos: number }[] = []\n for (const sibling of siblingsAfter(editor, pos, node)) {\n if (!isKind(sibling.node, KIND.example) && !isKind(sibling.node, KIND.fact)) break\n if (!isKind(sibling.node, KIND.example) || stringMeta(sibling.node, META.status) !== STATUS.proposal) continue\n const from = derivationOf(sibling.node)?.from\n if (from && 'block' in from && from.block === sourceId) out.push(sibling)\n }\n return out\n}\n\n/**\n * Inserts `nodes` after the source block `sourceId` and after the run of `kind` blocks that already follows it, so a second\n * run appends. Returns how many were inserted; null when the source or the position is gone, or the parent refuses the nodes.\n */\nfunction insertAfterSource(editor: Editor, sourceId: string, nodes: readonly DocNode[], kind: string): number | null {\n const found = findBlockById(editor.state.doc, sourceId)\n if (!found) return null\n try {\n const built = nodes.map(node => editor.schema.nodeFromJSON(node as JSONContent))\n let at = found.pos + found.node.nodeSize\n for (const sibling of siblingsAfter(editor, found.pos, found.node)) {\n if (!isKind(sibling.node, kind)) break\n at = sibling.pos + sibling.node.nodeSize\n }\n editor.view.dispatch(editor.state.tr.insert(at, built))\n return built.length\n }\n catch {\n return null\n }\n}\n\n/** The proposal chip text: `0.82 · embed`; the origin `embedding` is shown short. */\nfunction proposalLabel(node: PMNode): string {\n const confidence = Number(metaOf(node)[META.confidence])\n const origin = stringMeta(node, META.origin)\n const parts = [Number.isFinite(confidence) ? confidence.toFixed(2) : '', origin === 'embedding' ? 'embed' : origin].filter(Boolean)\n return parts.length > 0 ? `Vorschlag ${parts.join(' · ')}` : 'Vorschlag'\n}\n\nexport function composeFeatures({ documentId, invoke }: ComposeFeaturesOptions): VueFeature[] {\n /** `operation:block` pairs with a request in flight: no second start while the model works. */\n const busy = new Set<string>()\n\n return [\n {\n id: 'compose-material',\n dimension: 'ai',\n // „Block füllen (KI)“ of a slot: the skeleton when the slot has one, then the facts of the case for this slot block and the\n // best matching examples (together at most the 10 entries the editor sends). The server resolves identifier, template\n // variant and tags from the block id.\n aiBlockData: async ({ node }) => {\n if (!isKind(node, KIND.slot)) return []\n const skeleton = stringMeta(node, META.skelett)\n const own = skeleton ? [{ label: SKELETON_LABEL, text: skeleton.slice(0, MAX_DATA_TEXT) }] : []\n const block = blockIdOf(node)\n if (!block || !stringMeta(node, META.identifier)) return own\n const material = materialToData(await invoke('material', { document: documentId(), block }))\n return [...own, ...material.slice(0, MAX_DATA_ENTRIES - own.length)]\n },\n },\n {\n id: 'compose-split',\n dimension: 'ai',\n // Proposals carry confidence and origin until a person accepts them; the chip shows both.\n rowBadges: () => [{\n id: 'compose-proposal',\n of: (editor, row) => {\n const node = editor.state.doc.nodeAt(row.pos)\n return isKind(node, KIND.example) && stringMeta(node, META.status) === STATUS.proposal\n ? { label: proposalLabel(node), tone: 'warning' }\n : null\n },\n }],\n blockActions: ctx => [\n {\n id: 'compose-split',\n label: 'In Beispiele zerlegen (KI)',\n icon: 'i-lucide-scissors',\n order: 24,\n blockTypes: [KIND.source],\n run: ({ editor, node }) => {\n const block = blockIdOf(node)\n if (!block) return\n if (bodyMarkdown(editor, node).trim().length === 0) {\n ctx.notify({ kind: 'warning', title: 'Die Quelle ist leer', description: 'Es gibt keinen Text, der sich zerlegen ließe.' })\n return\n }\n const key = `split:${block}`\n if (busy.has(key)) return\n busy.add(key)\n const noticeId = `compose-split-${block}`\n ctx.notify({ id: noticeId, kind: 'info', title: 'Quelle wird zerlegt …', icon: 'i-lucide-loader', duration: 0 })\n invoke('split', { document: documentId(), block })\n .then(({ nodes }) => {\n ctx.notify.dismiss?.(noticeId)\n if (nodes.length === 0) {\n ctx.notify({ kind: 'info', title: 'Keine Beispiele gefunden' })\n return\n }\n const inserted = insertAfterSource(editor, block, nodes, KIND.example)\n if (inserted === null) {\n ctx.notify({ kind: 'error', title: 'Beispiele nicht eingefügt', description: 'Der Quell-Block ist nicht mehr im Dokument oder nimmt keine Beispiele auf.' })\n return\n }\n const unassigned = nodes.filter(example => !(example.attrs?.meta as Record<string, unknown> | undefined)?.[META.slot]).length\n ctx.notify({\n kind: 'success',\n title: `${inserted} ${inserted === 1 ? 'Beispiel' : 'Beispiele'} als Vorschlag eingefügt`,\n ...(unassigned > 0 && { description: `${unassigned} ohne Slot.` }),\n })\n })\n .catch(error => {\n ctx.notify.dismiss?.(noticeId)\n ctx.notify({ kind: 'error', title: 'Quelle nicht zerlegt', description: messageOf(error) })\n })\n .finally(() => busy.delete(key))\n },\n },\n {\n id: 'compose-accept-proposals',\n label: 'Vorschläge annehmen',\n icon: 'i-lucide-check-check',\n order: 25,\n blockTypes: [KIND.source],\n isVisible: ({ editor, node, pos }) => {\n const block = blockIdOf(node)\n return block !== null && proposalsAfter(editor, pos, node, block).length > 0\n },\n run: ({ editor, node, pos }) => {\n const block = blockIdOf(node)\n if (!block) return\n const pending = proposalsAfter(editor, pos, node, block)\n if (pending.length === 0) return\n // Attribute updates keep every position valid; one transaction, one undo step.\n const tr = editor.state.tr\n for (const { node: proposal, pos: at } of pending) {\n const { [META.confidence]: _confidence, [META.origin]: _origin, ...meta } = metaOf(proposal)\n tr.setNodeMarkup(at, undefined, { ...proposal.attrs, meta: { ...meta, [META.status]: STATUS.accepted } })\n }\n editor.view.dispatch(tr)\n ctx.notify({ kind: 'success', title: `${pending.length} ${pending.length === 1 ? 'Vorschlag' : 'Vorschläge'} angenommen` })\n },\n },\n ],\n },\n {\n id: 'compose-facts',\n dimension: 'ai',\n blockActions: ctx => [\n {\n id: 'compose-facts',\n label: 'Stichpunkte erzeugen (KI)',\n icon: 'i-lucide-list-checks',\n order: 26,\n blockTypes: [KIND.source],\n run: ({ editor, node }) => {\n const block = blockIdOf(node)\n if (!block) return\n if (bodyMarkdown(editor, node).trim().length === 0) {\n ctx.notify({ kind: 'warning', title: 'Die Quelle ist leer', description: 'Es gibt keinen Text, aus dem Fakten entstehen könnten.' })\n return\n }\n const key = `facts:${block}`\n if (busy.has(key)) return\n busy.add(key)\n const noticeId = `compose-facts-${block}`\n ctx.notify({ id: noticeId, kind: 'info', title: 'Stichpunkte werden erzeugt …', description: 'Das Modell liest die Quelle.', icon: 'i-lucide-loader', duration: 0 })\n invoke('facts', { document: documentId(), block })\n .then(({ nodes }) => {\n ctx.notify.dismiss?.(noticeId)\n if (nodes.length === 0) {\n ctx.notify({ kind: 'info', title: 'Keine relevanten Fakten gefunden' })\n return\n }\n // The block may have moved while the model ran: the position is looked up again by block id.\n const inserted = insertAfterSource(editor, block, nodes, KIND.fact)\n if (inserted === null) {\n ctx.notify({ kind: 'error', title: 'Fakten nicht eingefügt', description: 'Der Quell-Block ist nicht mehr im Dokument oder nimmt keine Fakten auf.' })\n return\n }\n const unassigned = nodes.filter(fact => !(fact.attrs?.meta as Record<string, unknown> | undefined)?.[META.slot]).length\n ctx.notify({\n kind: 'success',\n title: `${inserted} ${inserted === 1 ? 'Fakt' : 'Fakten'} eingefügt`,\n ...(unassigned > 0 && { description: `${unassigned} ohne Slot.` }),\n })\n })\n .catch(error => {\n ctx.notify.dismiss?.(noticeId)\n ctx.notify({ kind: 'error', title: 'Fakten nicht erzeugt', description: messageOf(error) })\n })\n .finally(() => busy.delete(key))\n },\n },\n ],\n },\n {\n id: 'compose-example',\n dimension: 'content',\n blockActions: ctx => [\n {\n id: 'compose-example',\n label: 'Als Beispiel übernehmen',\n icon: 'i-lucide-bookmark-plus',\n order: 460,\n blockTypes: [KIND.slot],\n // Only slots of an instance (copied from a template) are adopted; a template slot is not text of a case.\n isVisible: ({ node }) => derivationOf(node)?.method === METHOD.copy,\n run: ({ node }) => {\n const block = blockIdOf(node)\n if (!block) return\n invoke('example', { document: documentId(), block }).then(\n ({ libraryId }) => ctx.notify({ kind: 'success', title: 'Als Beispiel übernommen', description: `Zu finden in „${libraryId}“.` }),\n error => ctx.notify({ kind: 'error', title: 'Beispiel nicht übernommen', description: messageOf(error) }),\n )\n },\n },\n ],\n },\n ]\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"features.js","names":["TYPED_BLOCK","blockIdOf"],"sources":["../../src/vue/features.ts"],"sourcesContent":["// Editor side of the compose operations. The features call the host through one injected function (`ComposeInvoke`), so a host\n// picks its transport (own routes, the forge-app-actions HTTP projection, anything else); the package mounts no route.\n//\n// Where the data comes from: the operations read the stored document (`documentId` plus a block id), so an edit the debounced\n// save has not reached yet is not seen by them. Where the result goes: the editor, in one transaction per result (one undo step);\n// the server writes nothing for `split` and `facts`.\nimport type { VueFeature } from '@skaile/forge-editor/vue'\nimport { bodyMarkdown } from '@skaile/forge-editor/composites'\nimport { BLOCK_TYPE_ATTR, derivationOf, fetchErrorMessage, metaOf, TYPED_BLOCK } from '@skaile/forge-editor/core'\nimport { blockIdOf, findBlockById } from '@skaile/forge-editor/core/schema'\nimport { KIND, MAX_DATA_ENTRIES, MAX_DATA_TEXT, META, materialToData, METHOD, STATUS } from '@skaile/forge-compose/documents'\nimport type { DocNode, InstanceReport } from '@skaile/forge-compose/documents'\nimport type { Editor, JSONContent } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport type { ComposeInput, ComposeOperationId, ComposeOutput } from '../server'\n\n/** Calls one compose operation on the host. Rejects with the transport's error; the features show its message in a notice. */\nexport type ComposeInvoke = <K extends ComposeOperationId>(id: K, input: ComposeInput<K>) => Promise<ComposeOutput<K>>\n\nexport interface ComposeFeaturesOptions {\n /** The document the editor shows; read at call time, so a host can change it without rebuilding the features. */\n documentId: () => string\n invoke: ComposeInvoke\n}\n\nexport interface HttpInvokeOptions {\n /** Route prefix; default `/api/compose`. */\n base?: string\n /** `$fetch` (Nuxt) or a thin `fetch` wrapper that parses the JSON answer; a non-2xx status must reject (as `$fetch` does). */\n fetch: (url: string, init: { method: 'POST'; body: unknown }) => Promise<unknown>\n}\n\n/** `POST {base}/{id}` with the input as JSON body. */\nexport function httpInvoke({ base = '/api/compose', fetch }: HttpInvokeOptions): ComposeInvoke {\n const prefix = base.replace(/\\/+$/, '')\n return ((id: ComposeOperationId, input: unknown) => fetch(`${prefix}/${id}`, { method: 'POST', body: input })) as ComposeInvoke\n}\n\n/** Creates the instance document `input.instanceId` from the template `input.templateId` for its case (not a feature: a page calls it). */\nexport function createInstance(invoke: ComposeInvoke, input: ComposeInput<'instance'>): Promise<InstanceReport> {\n return invoke('instance', input)\n}\n\nconst isKind = (node: PMNode | null | undefined, kind: string): node is PMNode =>\n node?.type.name === TYPED_BLOCK && node.attrs[BLOCK_TYPE_ATTR] === kind\n\n/** Label of the slot's skeleton among the `aiBlockData` entries (at most 100 characters). */\nconst SKELETON_LABEL = 'Skelett (Zieltext: Struktur und Reihenfolge beibehalten, {{…}} = freie Stellen füllen)'\n\nconst stringMeta = (node: PMNode, key: string): string => {\n const value = metaOf(node)[key]\n return typeof value === 'string' ? value.trim() : ''\n}\n\n/** Message of a failed call: the server's own (`data.message`, `data.statusMessage` of an ofetch error), else the error's. */\nfunction messageOf(error: unknown): string {\n const data = (error as { data?: { message?: unknown; statusMessage?: unknown } } | null)?.data\n for (const candidate of [data?.message, data?.statusMessage]) if (typeof candidate === 'string' && candidate) return candidate\n return fetchErrorMessage(error)\n}\n\n/** The siblings after the Typed block `node` at `pos`, in order, each with its position. */\nfunction siblingsAfter(editor: Editor, pos: number, node: PMNode): { node: PMNode; pos: number }[] {\n const after = pos + node.nodeSize\n const $after = editor.state.doc.resolve(after)\n const out: { node: PMNode; pos: number }[] = []\n let at = after\n for (let i = $after.index(); i < $after.parent.childCount; i++) {\n const child = $after.parent.child(i)\n out.push({ node: child, pos: at })\n at += child.nodeSize\n }\n return out\n}\n\n/** Example proposals of `sourceId` among the siblings that directly follow the source (a run of `example` and `fact` blocks). */\nfunction proposalsAfter(editor: Editor, pos: number, node: PMNode, sourceId: string): { node: PMNode; pos: number }[] {\n const out: { node: PMNode; pos: number }[] = []\n for (const sibling of siblingsAfter(editor, pos, node)) {\n if (!isKind(sibling.node, KIND.example) && !isKind(sibling.node, KIND.fact)) break\n if (!isKind(sibling.node, KIND.example) || stringMeta(sibling.node, META.status) !== STATUS.proposal) continue\n const from = derivationOf(sibling.node)?.from\n if (from && 'block' in from && from.block === sourceId) out.push(sibling)\n }\n return out\n}\n\n/**\n * Inserts `nodes` after the source block `sourceId` and after the run of `kind` blocks that already follows it, so a second\n * run appends. Returns how many were inserted; null when the source or the position is gone, or the parent refuses the nodes.\n */\nfunction insertAfterSource(editor: Editor, sourceId: string, nodes: readonly DocNode[], kind: string): number | null {\n const found = findBlockById(editor.state.doc, sourceId)\n if (!found) return null\n try {\n const built = nodes.map(node => editor.schema.nodeFromJSON(node as JSONContent))\n let at = found.pos + found.node.nodeSize\n for (const sibling of siblingsAfter(editor, found.pos, found.node)) {\n if (!isKind(sibling.node, kind)) break\n at = sibling.pos + sibling.node.nodeSize\n }\n editor.view.dispatch(editor.state.tr.insert(at, built))\n return built.length\n }\n catch {\n return null\n }\n}\n\n/** The proposal chip text: `0.82 · embed`; the origin `embedding` is shown short. */\nfunction proposalLabel(node: PMNode): string {\n const confidence = Number(metaOf(node)[META.confidence])\n const origin = stringMeta(node, META.origin)\n const parts = [Number.isFinite(confidence) ? confidence.toFixed(2) : '', origin === 'embedding' ? 'embed' : origin].filter(Boolean)\n return parts.length > 0 ? `Vorschlag ${parts.join(' · ')}` : 'Vorschlag'\n}\n\nexport function composeFeatures({ documentId, invoke }: ComposeFeaturesOptions): VueFeature[] {\n /** `operation:block` pairs with a request in flight: no second start while the model works. */\n const busy = new Set<string>()\n\n return [\n {\n id: 'compose-material',\n dimension: 'ai',\n // „Block füllen (KI)“ of a slot: the skeleton when the slot has one, then the facts of the case for this slot block, the\n // best matching snippets (to adopt) and examples (style only), together at most the 10 entries the editor sends. The\n // server resolves identifier, element, template variant and tags from the block id.\n aiBlockData: async ({ node }) => {\n if (!isKind(node, KIND.slot)) return []\n const skeleton = stringMeta(node, META.skelett)\n const own = skeleton ? [{ label: SKELETON_LABEL, text: skeleton.slice(0, MAX_DATA_TEXT) }] : []\n const block = blockIdOf(node)\n if (!block || !stringMeta(node, META.identifier)) return own\n const material = materialToData(await invoke('material', { document: documentId(), block }))\n return [...own, ...material.slice(0, MAX_DATA_ENTRIES - own.length)]\n },\n },\n {\n id: 'compose-split',\n dimension: 'ai',\n // Proposals carry confidence and origin until a person accepts them; the chip shows both.\n rowBadges: () => [{\n id: 'compose-proposal',\n of: (editor, row) => {\n const node = editor.state.doc.nodeAt(row.pos)\n return isKind(node, KIND.example) && stringMeta(node, META.status) === STATUS.proposal\n ? { label: proposalLabel(node), tone: 'warning' }\n : null\n },\n }],\n blockActions: ctx => [\n {\n id: 'compose-split',\n label: 'In Beispiele zerlegen (KI)',\n icon: 'i-lucide-scissors',\n order: 24,\n blockTypes: [KIND.source],\n run: ({ editor, node }) => {\n const block = blockIdOf(node)\n if (!block) return\n if (bodyMarkdown(editor, node).trim().length === 0) {\n ctx.notify({ kind: 'warning', title: 'Die Quelle ist leer', description: 'Es gibt keinen Text, der sich zerlegen ließe.' })\n return\n }\n const key = `split:${block}`\n if (busy.has(key)) return\n busy.add(key)\n const noticeId = `compose-split-${block}`\n ctx.notify({ id: noticeId, kind: 'info', title: 'Quelle wird zerlegt …', icon: 'i-lucide-loader', duration: 0 })\n invoke('split', { document: documentId(), block })\n .then(({ nodes }) => {\n ctx.notify.dismiss?.(noticeId)\n if (nodes.length === 0) {\n ctx.notify({ kind: 'info', title: 'Keine Beispiele gefunden' })\n return\n }\n const inserted = insertAfterSource(editor, block, nodes, KIND.example)\n if (inserted === null) {\n ctx.notify({ kind: 'error', title: 'Beispiele nicht eingefügt', description: 'Der Quell-Block ist nicht mehr im Dokument oder nimmt keine Beispiele auf.' })\n return\n }\n const unassigned = nodes.filter(example => !(example.attrs?.meta as Record<string, unknown> | undefined)?.[META.slot]).length\n ctx.notify({\n kind: 'success',\n title: `${inserted} ${inserted === 1 ? 'Beispiel' : 'Beispiele'} als Vorschlag eingefügt`,\n ...(unassigned > 0 && { description: `${unassigned} ohne Slot.` }),\n })\n })\n .catch(error => {\n ctx.notify.dismiss?.(noticeId)\n ctx.notify({ kind: 'error', title: 'Quelle nicht zerlegt', description: messageOf(error) })\n })\n .finally(() => busy.delete(key))\n },\n },\n {\n id: 'compose-accept-proposals',\n label: 'Vorschläge annehmen',\n icon: 'i-lucide-check-check',\n order: 25,\n blockTypes: [KIND.source],\n isVisible: ({ editor, node, pos }) => {\n const block = blockIdOf(node)\n return block !== null && proposalsAfter(editor, pos, node, block).length > 0\n },\n run: ({ editor, node, pos }) => {\n const block = blockIdOf(node)\n if (!block) return\n const pending = proposalsAfter(editor, pos, node, block)\n if (pending.length === 0) return\n // Attribute updates keep every position valid; one transaction, one undo step.\n const tr = editor.state.tr\n for (const { node: proposal, pos: at } of pending) {\n const { [META.confidence]: _confidence, [META.origin]: _origin, ...meta } = metaOf(proposal)\n tr.setNodeMarkup(at, undefined, { ...proposal.attrs, meta: { ...meta, [META.status]: STATUS.accepted } })\n }\n editor.view.dispatch(tr)\n ctx.notify({ kind: 'success', title: `${pending.length} ${pending.length === 1 ? 'Vorschlag' : 'Vorschläge'} angenommen` })\n },\n },\n ],\n },\n {\n id: 'compose-facts',\n dimension: 'ai',\n blockActions: ctx => [\n {\n id: 'compose-facts',\n label: 'Stichpunkte erzeugen (KI)',\n icon: 'i-lucide-list-checks',\n order: 26,\n blockTypes: [KIND.source],\n run: ({ editor, node }) => {\n const block = blockIdOf(node)\n if (!block) return\n if (bodyMarkdown(editor, node).trim().length === 0) {\n ctx.notify({ kind: 'warning', title: 'Die Quelle ist leer', description: 'Es gibt keinen Text, aus dem Fakten entstehen könnten.' })\n return\n }\n const key = `facts:${block}`\n if (busy.has(key)) return\n busy.add(key)\n const noticeId = `compose-facts-${block}`\n ctx.notify({ id: noticeId, kind: 'info', title: 'Stichpunkte werden erzeugt …', description: 'Das Modell liest die Quelle.', icon: 'i-lucide-loader', duration: 0 })\n invoke('facts', { document: documentId(), block })\n .then(({ nodes }) => {\n ctx.notify.dismiss?.(noticeId)\n if (nodes.length === 0) {\n ctx.notify({ kind: 'info', title: 'Keine relevanten Fakten gefunden' })\n return\n }\n // The block may have moved while the model ran: the position is looked up again by block id.\n const inserted = insertAfterSource(editor, block, nodes, KIND.fact)\n if (inserted === null) {\n ctx.notify({ kind: 'error', title: 'Fakten nicht eingefügt', description: 'Der Quell-Block ist nicht mehr im Dokument oder nimmt keine Fakten auf.' })\n return\n }\n const unassigned = nodes.filter(fact => !(fact.attrs?.meta as Record<string, unknown> | undefined)?.[META.slot]).length\n ctx.notify({\n kind: 'success',\n title: `${inserted} ${inserted === 1 ? 'Fakt' : 'Fakten'} eingefügt`,\n ...(unassigned > 0 && { description: `${unassigned} ohne Slot.` }),\n })\n })\n .catch(error => {\n ctx.notify.dismiss?.(noticeId)\n ctx.notify({ kind: 'error', title: 'Fakten nicht erzeugt', description: messageOf(error) })\n })\n .finally(() => busy.delete(key))\n },\n },\n ],\n },\n {\n id: 'compose-example',\n dimension: 'content',\n blockActions: ctx => [\n {\n id: 'compose-example',\n label: 'Als Beispiel übernehmen',\n icon: 'i-lucide-bookmark-plus',\n order: 460,\n blockTypes: [KIND.slot],\n // Only slots of an instance (copied from a template) are adopted; a template slot is not text of a case.\n isVisible: ({ node }) => derivationOf(node)?.method === METHOD.copy,\n run: ({ node }) => {\n const block = blockIdOf(node)\n if (!block) return\n invoke('example', { document: documentId(), block }).then(\n ({ libraryId }) => ctx.notify({ kind: 'success', title: 'Als Beispiel übernommen', description: `Zu finden in „${libraryId}“.` }),\n error => ctx.notify({ kind: 'error', title: 'Beispiel nicht übernommen', description: messageOf(error) }),\n )\n },\n },\n ],\n },\n ]\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skaile/forge-editor-compose",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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,7 +50,7 @@
|
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@skaile/forge-compose": "^0.
|
|
53
|
+
"@skaile/forge-compose": "^0.3.0",
|
|
54
54
|
"zod": "^4.5.4"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|