Zum Inhalt springen
Docxcelerate

Nodes

Paragraph

Prosa, aus Ihren Daten gerendert oder zur Anfragezeit aus Prompts aufgelöst.

The workhorse. A static paragraph returns a string from your typed data; a dynamic one carries prompts and a placeholder, and is filled at request time. Both land as the same node kind, differing only by `mode`.

Helper
paragraph
Node-Art
paragraph
Kategorie
Text
Wird aufgelöst
Both
Kinder
None. Paragraphs are leaves.
Option Typ Was sie bewirkt
id erforderlich string Stable address for the node. Generation endpoints target it and build artifacts diff on it, so treat a rename as a breaking change.
render erforderlich (data, availableTokens) => string Static only. Receives your typed data and the token budget, returns the text. May be async.
placeholder (data, availableTokens) => string Dynamic only. What previews show in place of generated content. Optional, but a letter that reads badly without one cannot be reviewed.
generalPrompt erforderlich (data, availableTokens) => string Dynamic only. What this node should say.
infoPrompt (data, availableTokens) => string Dynamic only. Context the model should have but should not restate.
negativePrompt (data, availableTokens) => string Dynamic only. What to avoid — claims, tones, or facts it must not invent.
systemPrompt (data, availableTokens) => string Dynamic only. Role and voice, applied ahead of the other prompts.
derivers DeriverInvocation[] Values computed before the node resolves, written to derived.* and readable from a template token. Built with derive().

Einen schreiben

render bekommt Ihre typisierten Daten und das Token-Budget und gibt einen String zurück. Dazwischen liegt keine Templatesprache — Formatierung, Verzweigung und Pluralbildung sind alle gewöhnliches TypeScript:

import { paragraph } from "docxcelerate";
import type { MemberData } from "../types.ts";

export const Greeting = paragraph<MemberData>({
  id: "greeting",
  render: (data) => `Dear ${data.memberName},`,
});

Er darf async sein — aber ein Node, der etwas holt, ist ein Node, der mitten im Build scheitern kann; legen Sie den Wert lieber vorher in Ihre Daten.

Das Token-Budget

Das zweite Argument von render, placeholder und jedem Prompt ist availableTokens: das Budget, das diesem Node zugeteilt wurde — 2000, sofern Sie availableTokens in den Build-Optionen nicht setzen. Statische Nodes dürfen es ignorieren. Dynamische sollten es ausgeben:

generalPrompt: (data, availableTokens) =>
  `Explain the change. At most ${Math.floor(availableTokens / 4)} words.`,

Verzweigen gehört in das Render

Ein Dokument, das eines von drei Dingen sagt, ist ein Node mit drei Ausgängen, nicht drei Nodes hinter Bedingungen. Die id bleibt, wo sie ist, der Baum behält seine Form, gleich wer der Empfänger ist, und die Logik ist Code, den Sie testen können.

Dynamische Absätze

Ein dynamischer Absatz tauscht render gegen Prompts und einen Platzhalter. Vorschau-Builds lösen ihn zum Platzhalter auf und kennzeichnen ihn; Builds zur Anfragezeit senden die Prompts. Nur generalPrompt ist erforderlich — die vierte Variante unten zeigt, wozu die anderen drei da sind.

Varianten

Static

src/nodes/paragraph/static.node.ts

Data in, a line of text out.

import { paragraph } from "docxcelerate";
import type { SampleData } from "../sample-data.ts";

/** The smallest useful node: an id, and a render that turns data into a line. */
export const Greeting = paragraph<SampleData>({
  id: "greeting",
  render: (data) => `Dear ${data.memberName},`,
});
paragraph · static Open ↗
Wozu es aufgelöst wird

Der Node, wie er im DocumentModel erscheint: das JSON, das ein Renderer bekommt. Kein Styling, kein Layout.

{
  "id": "greeting",
  "kind": "paragraph",
  "mode": "static",
  "text": "Dear Adaeze Nkemelu,"
}

Static, with branching

src/nodes/paragraph/conditional.node.ts

One node, several outcomes — and the id stays put.

import { paragraph } from "docxcelerate";
import { money, type SampleData } from "../sample-data.ts";

/**
 * Branching lives in the render, not the template: one node and one id,
 * whichever branch a member falls down.
 */
export const PriceChange = paragraph<SampleData>({
  id: "price-change",
  render: (data) => {
    const delta = data.newPrice - data.lastPrice;

    if (delta === 0) {
      return `Your ${data.plan} membership renews at ${money(data.newPrice)} ` +
        `a year — the same price you paid last year.`;
    }

    const direction = delta > 0 ? "rising" : "falling";
    const percent = Math.abs((delta / data.lastPrice) * 100).toFixed(1);

    return `Your ${data.plan} membership is ${direction} by ${percent}%, from ` +
      `${money(data.lastPrice)} to ${money(data.newPrice)} a year. That is ` +
      `${money(data.newPrice / 12)} a month from ${data.renewsOn}.`;
  },
});
paragraph · static, with branching Open ↗
Wozu es aufgelöst wird

Der Node, wie er im DocumentModel erscheint: das JSON, das ein Renderer bekommt. Kein Styling, kein Layout.

{
  "id": "price-change",
  "kind": "paragraph",
  "mode": "static",
  "text": "Your Peak Anytime membership is rising by 5.1%, from £468 to £492 a year. That is £41 a month from 1 October 2026."
}

Dynamic

src/nodes/paragraph/dynamic.node.ts

A prompt and a placeholder. Previews show the placeholder, labelled.

import { paragraph } from "docxcelerate";
import type { SampleData } from "../sample-data.ts";

/**
 * The minimum a dynamic paragraph needs: one prompt, and a placeholder so the
 * preview still reads as a letter.
 */
export const NextSteps = paragraph<SampleData>({
  id: "next-steps",
  placeholder: (data) =>
    `Your membership renews automatically on ${data.renewsOn}. ` +
    `Nothing is needed from you unless you want to change plan.`,
  generalPrompt: (data) =>
    `In two sentences, tell ${data.memberName} that their membership renews ` +
    `automatically on ${data.renewsOn} and how to change plan before then.`,
});
paragraph · dynamic Open ↗
Wozu es aufgelöst wird

Der Node, wie er im DocumentModel erscheint: das JSON, das ein Renderer bekommt. Kein Styling, kein Layout.

{
  "id": "next-steps",
  "kind": "paragraph",
  "mode": "dynamic",
  "text": "Your membership renews automatically on 1 October 2026. Nothing is needed from you unless you want to change plan."
}
Was der Endpoint gefragt wird

Gegen dieselben Beispieldaten aufgelöst. Ein Vorschau-Build hält beim Platzhalter an; ein Build zur Anfragezeit schickt diese mit.

general
In two sentences, tell Adaeze Nkemelu that their membership renews automatically on 1 October 2026 and how to change plan before then.

Dynamic, all four prompts

src/nodes/paragraph/prompted.node.ts

System, general, info and negative, each doing one job.

import { paragraph } from "docxcelerate";
import { money, type SampleData } from "../sample-data.ts";

/**
 * All four slots: general says what to write, info supplies facts without
 * asking for them back, negative fences off the failure modes, system fixes
 * the voice. `availableTokens` is the budget the build allotted this node.
 */
export const Apology = paragraph<SampleData>({
  id: "pool-closure",
  placeholder: () =>
    `The main pool is closed for resurfacing until 12 October. ` +
    `The teaching pool and all land-based classes are running as normal.`,
  systemPrompt: () =>
    `You write for a public leisure centre. Plain British English, second ` +
    `person, no marketing language.`,
  generalPrompt: (data, availableTokens) =>
    `Apologise to ${data.memberName} for the main pool closure and say what ` +
    `is still open. At most ${Math.floor(availableTokens / 4)} words.`,
  infoPrompt: (data) =>
    `The main pool at ${data.centreName} is resurfacing until 12 October. ` +
    `The teaching pool, gym and classes are unaffected. Members on ` +
    `${data.plan} paying ${money(data.newPrice)} a year get two guest passes ` +
    `as compensation.`,
  negativePrompt: () =>
    `Do not promise a refund, do not give a reopening date beyond 12 October, ` +
    `and do not restate the price.`,
});
paragraph · dynamic, all four prompts Open ↗
Wozu es aufgelöst wird

Der Node, wie er im DocumentModel erscheint: das JSON, das ein Renderer bekommt. Kein Styling, kein Layout.

{
  "id": "pool-closure",
  "kind": "paragraph",
  "mode": "dynamic",
  "text": "The main pool is closed for resurfacing until 12 October. The teaching pool and all land-based classes are running as normal."
}
Was der Endpoint gefragt wird

Gegen dieselben Beispieldaten aufgelöst. Ein Vorschau-Build hält beim Platzhalter an; ein Build zur Anfragezeit schickt diese mit.

system
You write for a public leisure centre. Plain British English, second person, no marketing language.
general
Apologise to Adaeze Nkemelu for the main pool closure and say what is still open. At most 500 words.
info
The main pool at Riverside Leisure Centre is resurfacing until 12 October. The teaching pool, gym and classes are unaffected. Members on Peak Anytime paying £492 a year get two guest passes as compensation.
negative
Do not promise a refund, do not give a reopening date beyond 12 October, and do not restate the price.

Anmerkungen

  • Beide Modi lösen zu kind: "paragraph" auf. Ein Renderer liest mode, falls es ihn überhaupt kümmert; er verzweigt nie danach, welchen Helper Sie aufgerufen haben.
  • Ein leerer String ergibt einen leeren Absatz, nicht keinen Node. Um den Node wegzulassen, lassen Sie ihn aus dem Baum.
  • Renderer escapen den Text. Ein Absatz kann kein Markup in die Seite schmuggeln und ist nicht der Ort für Formatierung.

Diese Seite auf GitHub bearbeiten ↗