Перейти к содержимому
Docxcelerate

Узлы

Paragraph

Проза, отрисованная из ваших данных или разрешённая из промптов во время запроса.

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

Хелперы
paragraph
Вид узла
paragraph
Категория
Текст
Разрешается
Both
Дочерние узлы
None. Paragraphs are leaves.
Опция Тип Что делает
id обязательно 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 обязательно (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 обязательно (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().

Как его написать

render получает ваши типизированные данные и бюджет токенов, а возвращает строку. Никакого языка шаблонов между ними нет, поэтому форматирование, ветвление и множественные формы — это обычный TypeScript:

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

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

Он может быть async — но узел, который ходит за данными, это узел, способный упасть посреди сборки, так что лучше сначала положить значение в свои данные.

Бюджет токенов

Второй аргумент render, placeholder и каждого промпта — availableTokens: бюджет, выделенный этому узлу, равный 2000, если вы не задали availableTokens в опциях сборки. Статические узлы могут его игнорировать. Динамическим стоит его расходовать:

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

Ветвление — дело функции render

Документ, который говорит одно из трёх, — это один узел с тремя исходами, а не три узла под условиями. Идентификатор остаётся на месте, дерево сохраняет форму, кем бы ни был получатель, а логика — это код, который можно протестировать.

Динамические абзацы

Динамический абзац меняет render на промпты и заполнитель. Сборки предпросмотра разрешают его в заполнитель и помечают; сборки во время запроса отправляют промпты. Обязателен только generalPrompt — четвёртый вариант ниже показывает, зачем нужны остальные три.

Варианты

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 ↗
Во что это разрешается

Узел в том виде, в каком он попадает в DocumentModel: JSON, который получает рендерер. Без стилей и без вёрстки.

{
  "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 ↗
Во что это разрешается

Узел в том виде, в каком он попадает в DocumentModel: JSON, который получает рендерер. Без стилей и без вёрстки.

{
  "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 ↗
Во что это разрешается

Узел в том виде, в каком он попадает в DocumentModel: JSON, который получает рендерер. Без стилей и без вёрстки.

{
  "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."
}
О чём просят эндпоинт

Разрешено на тех же тестовых данных. Сборка предпросмотра останавливается на заполнителе; сборка во время запроса отправляет их.

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 ↗
Во что это разрешается

Узел в том виде, в каком он попадает в DocumentModel: JSON, который получает рендерер. Без стилей и без вёрстки.

{
  "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."
}
О чём просят эндпоинт

Разрешено на тех же тестовых данных. Сборка предпросмотра останавливается на заполнителе; сборка во время запроса отправляет их.

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.

Примечания

  • Оба режима разрешаются в kind: "paragraph". Рендерер читает mode, если это вообще его волнует; он никогда не ветвится по тому, какой хелпер вы вызвали.
  • Пустая строка даёт пустой абзац, а не отсутствие узла. Чтобы узла не было, не включайте его в дерево.
  • Рендереры экранируют текст. Абзац не может протащить разметку на страницу и не является местом для оформления.

Изменить эту страницу на GitHub ↗