SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
3.4 KB · 89 lines typescript
Raw Blame History
1/**2 * Prompt-library template variables — pure helpers shared by the server (render on `/use`) and the client3 * (live detection in the editor, fill-in sheet). No React, no DB, no `server-only`.4 *5 * Syntax: `{{name}}` (whitespace inside the braces is tolerated: `{{ name }}`). Names start with a letter or6 * underscore and may contain letters, digits, `_`, `-` and `.`. Anything else is left untouched, so JSON examples7 * and Jinja-like blocks (`{% … %}`) inside a prompt are safe.8 */910export const VARIABLE_RE = /\{\{\s*([A-Za-z_][\w.-]*)\s*\}\}/g;1112export interface PromptVariable {13  name: string;14  label?: string;15  default?: string;16  required?: boolean;17  /** When set, the fill-in UI shows a select instead of a free text input. */18  options?: string[];19}2021/** Unique variable names in order of first appearance. */22export function parseVariables(content: string): string[] {23  const out: string[] = [];24  const seen = new Set<string>();25  if (!content) return out;26  for (const m of content.matchAll(VARIABLE_RE)) {27    const name = m[1];28    if (!seen.has(name)) {29      seen.add(name);30      out.push(name);31    }32  }33  return out;34}3536/**37 * Reconciles the declared variable list with the names actually present in the content: keeps38 * label/default/options for names that still exist, appends new names, drops removed ones. Order = content order.39 */40export function mergeVariables(content: string, declared: PromptVariable[] | null | undefined): PromptVariable[] {41  const byName = new Map((declared ?? []).map((v) => [v.name, v]));42  return parseVariables(content).map((name) => {43    const prev = byName.get(name);44    return prev ? { ...prev, name } : { name };45  });46}4748/** Human label fallback: `customer_name` → `Customer name`, `lang.code` → `Lang code`. */49export function labelFor(v: PromptVariable): string {50  if (v.label?.trim()) return v.label.trim();51  const words = v.name.replace(/[_.-]+/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim().toLowerCase();52  return words.charAt(0).toUpperCase() + words.slice(1);53}5455export interface RenderResult {56  text: string;57  /** Variables that had neither a value nor a default (left as empty strings). */58  missing: string[];59  /** Variables that were filled with their declared default. */60  defaulted: string[];61}6263/**64 * Replaces every `{{name}}` with `values[name]`, falling back to the declared default, then to `""`.65 * Unknown names (not declared) still render from `values` when provided.66 */67export function renderTemplate(content: string, values: Record<string, string | undefined> = {}, declared: PromptVariable[] = []): RenderResult {68  const defaults = new Map(declared.map((v) => [v.name, v.default]));69  const missing = new Set<string>();70  const defaulted = new Set<string>();71  const text = content.replace(VARIABLE_RE, (_m, name: string) => {72    const v = values[name];73    if (v !== undefined && v !== null && v !== "") return String(v);74    const d = defaults.get(name);75    if (d !== undefined && d !== "") {76      defaulted.add(name);77      return d;78    }79    missing.add(name);80    return "";81  });82  return { text, missing: [...missing], defaulted: [...defaulted] };83}8485/** Variables that the fill-in sheet must ask for: everything without a default (or explicitly required). */86export function variablesToAsk(vars: PromptVariable[]): PromptVariable[] {87  return vars.filter((v) => v.required || v.default === undefined || v.default === "");88}89