Ir al contenido
Docxcelerate

Nodos

Paragraph

Prosa, renderizada a partir de tus datos o resuelta desde prompts en el momento de la petición.

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`.

Helpers
paragraph
Clase de nodo
paragraph
Categoría
Texto
Se resuelve
Both
Hijos
None. Paragraphs are leaves.
Opción Tipo Qué hace
id obligatorio 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 obligatorio (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 obligatorio (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().

Escribir uno

render recibe tus datos tipados y el presupuesto de tokens, y devuelve una cadena. No hay ningún lenguaje de plantillas de por medio, así que el formato, las bifurcaciones y los plurales son TypeScript corriente:

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

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

Puede ser async — pero un nodo que hace peticiones es un nodo que puede fallar a mitad de la compilación, así que es preferible poner antes el valor en tus datos.

El presupuesto de tokens

El segundo argumento de render, de placeholder y de cada prompt es availableTokens: el presupuesto asignado a este nodo, 2000 salvo que definas availableTokens en las opciones de compilación. Los nodos estáticos pueden ignorarlo. Los dinámicos deberían gastarlo:

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

Las bifurcaciones van dentro del render

Un documento que dice una de tres cosas es un nodo con tres desenlaces, no tres nodos detrás de condiciones. El id se queda donde está, el árbol conserva su forma sea quien sea el destinatario, y la lógica es código que puedes probar.

Párrafos dinámicos

Un párrafo dinámico cambia render por prompts y un marcador de posición. Las compilaciones de vista previa lo resuelven al marcador y lo etiquetan; las compilaciones en el momento de la petición envían los prompts. Solo generalPrompt es obligatorio — la cuarta variante de abajo muestra para qué sirven los otros tres.

Variantes

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 ↗
En qué se resuelve

El nodo tal como aparece en el DocumentModel: el JSON que recibe un renderizador. Sin estilos, sin maquetación.

{
  "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 ↗
En qué se resuelve

El nodo tal como aparece en el DocumentModel: el JSON que recibe un renderizador. Sin estilos, sin maquetación.

{
  "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 ↗
En qué se resuelve

El nodo tal como aparece en el DocumentModel: el JSON que recibe un renderizador. Sin estilos, sin maquetación.

{
  "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."
}
Qué se le pide al endpoint

Resuelto con los mismos datos de ejemplo. Una compilación de vista previa se detiene en el marcador de posición; una compilación en el momento de la petición envía estos.

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 ↗
En qué se resuelve

El nodo tal como aparece en el DocumentModel: el JSON que recibe un renderizador. Sin estilos, sin maquetación.

{
  "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."
}
Qué se le pide al endpoint

Resuelto con los mismos datos de ejemplo. Una compilación de vista previa se detiene en el marcador de posición; una compilación en el momento de la petición envía estos.

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.

Notas

  • Ambos modos se resuelven a kind: "paragraph". Un renderizador lee mode si es que le importa; nunca se bifurca según el helper que hayas llamado.
  • Una cadena vacía devuelve un párrafo vacío, no la ausencia de nodo. Para eliminar el nodo, déjalo fuera del árbol.
  • Los renderizadores escapan el texto. Un párrafo no puede colar marcado en la página, y no es el sitio para dar formato.

Editar esta página en GitHub ↗