npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rameshnaik/prompt-ui

v0.1.5

Published

Framework-agnostic Web Component library for prompt-driven generative UI — charts, tables, list-detail views from natural language.

Downloads

61

Readme

@rameshnaik/prompt-ui

Framework-agnostic Web Component library for prompt-driven generative UI. Type a natural language prompt and get charts, tables, list-detail views, theme changes, and more — all generated on-the-fly from your data.

Works with React, Angular, Vue, Svelte, and vanilla HTML/JS — zero framework lock-in.

Preview

Installation

The library has three peer dependencies that power core features:

| Peer Dependency | Used For | |---|---| | chart.js | Rendering charts (pie, bar, line, doughnut, etc.) | | jspdf | PDF export of generated widgets | | html-to-image | Capturing widget DOM as image for PDF export |

npm install @rameshnaik/prompt-ui chart.js jspdf html-to-image

This single command works for all frameworks — React, Angular, Vue, Svelte, and vanilla JS.

React users: Your project already has react installed. The library lists it as an optional peer dependency for version compatibility — you don't need to install it again.

Quick Start by Framework

All examples below use the same sample data and fields:

const fields = [
  { key: "name",       label: "Name",       type: "string" },
  { key: "department", label: "Department",  type: "string" },
  { key: "hours",      label: "Hours",       type: "number" },
];

const data = [
  { name: "Alice", department: "Sales",       hours: 40 },
  { name: "Bob",   department: "Engineering", hours: 45 },
  { name: "Carol", department: "Sales",       hours: 38 },
];

React

The library ships a /react sub-entrypoint with a usePromptUI hook and built-in JSX type declarations — no boilerplate files needed.

import { usePromptUI } from "@rameshnaik/prompt-ui/react";
import type { FieldDescriptor } from "@rameshnaik/prompt-ui";

const FIELDS: FieldDescriptor[] = [
  { key: "name",       label: "Name",       type: "string" },
  { key: "department", label: "Department",  type: "string" },
  { key: "hours",      label: "Hours",       type: "number" },
];

const DATA = [
  { name: "Alice", department: "Sales",       hours: 40 },
  { name: "Bob",   department: "Engineering", hours: 45 },
];

const EXAMPLES = ["Pie chart by department", "Sort by hours descending"];

export default function TimecardPage() {
  const promptUI = usePromptUI({ fields: FIELDS });

  return (
    <>
      <prompt-ui-bar ref={promptUI.bar} examplePrompts={EXAMPLES} />
      <prompt-ui-canvas
        ref={promptUI.canvas}
        data={DATA as unknown as Record<string, unknown>[]}
        fields={FIELDS}
      />
      {/* ... rest of your page */}
    </>
  );
}

What usePromptUI does:

  • Creates a PromptUIController instance (once, persisted across re-renders)
  • Returns promptUI.bar and promptUI.canvas ref callbacks that wire the controller to each Web Component
  • Keeps fields in sync when they change
  • Registers the custom elements automatically via side-effect import

Why ref? React doesn't natively set JS object properties on custom elements. The ref callback bridges this gap. Other frameworks don't need it.


Angular

Angular binds properties to custom elements natively with [property] syntax. Just add CUSTOM_ELEMENTS_SCHEMA to your module or standalone component.

import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { PromptUIController } from "@rameshnaik/prompt-ui";
import "@rameshnaik/prompt-ui";

@Component({
  selector: "app-timecard",
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <prompt-ui-bar
      [controller]="controller"
      [examplePrompts]="examples"
    ></prompt-ui-bar>

    <prompt-ui-canvas
      [controller]="controller"
      [data]="data"
      [fields]="fields"
    ></prompt-ui-canvas>
  `,
})
export class TimecardComponent {
  fields = [
    { key: "name",       label: "Name",       type: "string" },
    { key: "department", label: "Department",  type: "string" },
    { key: "hours",      label: "Hours",       type: "number" },
  ];

  data = [
    { name: "Alice", department: "Sales",       hours: 40 },
    { name: "Bob",   department: "Engineering", hours: 45 },
  ];

  examples = ["Pie chart by department", "Sort by hours descending"];

  controller = new PromptUIController({ fields: this.fields });
}

No additional adapters or wrappers required.


Vue

Vue supports Web Components natively with :property binding. Add a custom element config to your vite.config.ts to suppress unknown-tag warnings.

vite.config.ts

import vue from "@vitejs/plugin-vue";

export default {
  plugins: [
    vue({
      template: {
        compilerOptions: {
          isCustomElement: (tag) => tag.startsWith("prompt-ui") || tag === "viz-widget",
        },
      },
    }),
  ],
};

TimecardPage.vue

<script setup lang="ts">
import { PromptUIController } from "@rameshnaik/prompt-ui";
import type { FieldDescriptor } from "@rameshnaik/prompt-ui";
import "@rameshnaik/prompt-ui";

const fields: FieldDescriptor[] = [
  { key: "name",       label: "Name",       type: "string" },
  { key: "department", label: "Department",  type: "string" },
  { key: "hours",      label: "Hours",       type: "number" },
];

const data = [
  { name: "Alice", department: "Sales",       hours: 40 },
  { name: "Bob",   department: "Engineering", hours: 45 },
];

const examples = ["Pie chart by department", "Sort by hours descending"];

const controller = new PromptUIController({ fields });
</script>

<template>
  <prompt-ui-bar
    :controller="controller"
    :examplePrompts="examples"
  />
  <prompt-ui-canvas
    :controller="controller"
    :data="data"
    :fields="fields"
  />
</template>

No additional adapters or wrappers required.


Svelte

Svelte treats custom elements as first-class citizens. Properties are passed directly.

<script lang="ts">
  import { PromptUIController } from "@rameshnaik/prompt-ui";
  import "@rameshnaik/prompt-ui";

  const fields = [
    { key: "name",       label: "Name",       type: "string" },
    { key: "department", label: "Department",  type: "string" },
    { key: "hours",      label: "Hours",       type: "number" },
  ];

  const data = [
    { name: "Alice", department: "Sales",       hours: 40 },
    { name: "Bob",   department: "Engineering", hours: 45 },
  ];

  const examples = ["Pie chart by department", "Sort by hours descending"];

  const controller = new PromptUIController({ fields });
</script>

<prompt-ui-bar
  controller={controller}
  examplePrompts={examples}
/>
<prompt-ui-canvas
  controller={controller}
  {data}
  {fields}
/>

No configuration or adapters required.


Vanilla HTML / No Framework

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Prompt UI Demo</title>
</head>
<body>
  <prompt-ui-bar id="bar"></prompt-ui-bar>
  <prompt-ui-canvas id="canvas"></prompt-ui-canvas>

  <script type="module">
    import { PromptUIController } from "@rameshnaik/prompt-ui";

    const fields = [
      { key: "name",       label: "Name",       type: "string" },
      { key: "department", label: "Department",  type: "string" },
      { key: "hours",      label: "Hours",       type: "number" },
    ];

    const data = [
      { name: "Alice", department: "Sales",       hours: 40 },
      { name: "Bob",   department: "Engineering", hours: 45 },
    ];

    const controller = new PromptUIController({
      fields,
      getData: () => data,
    });

    const bar = document.getElementById("bar");
    bar.controller = controller;
    bar.examplePrompts = ["Pie chart by department", "Sort by hours descending"];

    const canvas = document.getElementById("canvas");
    canvas.controller = controller;
    canvas.data = data;
    canvas.fields = fields;
  </script>
</body>
</html>

Framework Compatibility Summary

| Framework | Import | How properties are bound | Adapter needed? | |---|---|---|---| | React | @rameshnaik/prompt-ui/react | ref={promptUI.bar} | Built-in hook | | Angular | @rameshnaik/prompt-ui | [controller]="ctrl" | No | | Vue | @rameshnaik/prompt-ui | :controller="ctrl" | No | | Svelte | @rameshnaik/prompt-ui | controller={ctrl} | No | | Vanilla JS | @rameshnaik/prompt-ui | el.controller = ctrl | No |


Features

  • Charts: "pie chart by department", "bar chart of hours with count"
  • Tables: "show only Sales employees", "sort by hours descending"
  • List-Detail: "side panel view with title as Name"
  • Layout Transform: "convert to table view", "change this to side panel view" — replaces existing page content with the new layout
  • Theme: "apply dark theme", "revert dark theme"
  • Widget Management: "undo last widget", "clear all"
  • Voice Input: Built-in microphone button using Web Speech API
  • PDF Export: Download any generated widget as PDF
  • Prompt History: Arrow-up/down to navigate past prompts

Parser Modes

The library supports three parser modes for interpreting natural-language prompts. Choose the one that fits your needs:

| Mode | Strategy | Import | |---|---|---| | Mode 1 | Rule-based only — instant, free, offline | @rameshnaik/prompt-ui | | Mode 2 | LLM-first → rule-based fallback — max coverage | @rameshnaik/prompt-ui/llm | | Mode 3 | Rule-based first → LLM fallback — min cost, max speed | @rameshnaik/prompt-ui/llm |


Mode 1: Rule-Based Only (Default)

Instant, free, offline. No additional setup required.

The built-in parser uses pattern matching, stemming, fuzzy field resolution, and Levenshtein distance to interpret prompts. It handles common visualization patterns out of the box.

User prompt ──→ Rule-based parser ──→ VizSpec? ──→ Render
                                         │
                                     null (unrecognized)
                                         │
                                     Show error message

Usage:

import { PromptUIController } from "@rameshnaik/prompt-ui";

const controller = new PromptUIController({ fields });
// That's it — rule-based parser is used by default

Best for: Internal tools, offline apps, low-complexity datasets, or when you don't want any external API dependency.


Mode 2: LLM-First with Rule-Based Fallback

Maximum coverage. The LLM handles every prompt. If the LLM call fails (network error, timeout, unparseable response), the rule-based parser is used as a safety net.

User prompt ──→ LLM parser ──→ valid VizSpec? ──→ Render
                    │                 │
                 failed/timeout    invalid JSON
                    │                 │
                    └────────┬────────┘
                             ↓
                  Rule-based parser (fallback) ──→ Render or error

Usage:

import { PromptUIController } from "@rameshnaik/prompt-ui";
import { createLLMParser } from "@rameshnaik/prompt-ui/llm";

const parser = createLLMParser({
  call: async (systemPrompt, userPrompt) => {
    // You provide the HTTP call — use ANY LLM provider
    const res = await fetch("/api/llm", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
    });
    const data = await res.json();
    return data.text; // must return the raw LLM text response
  },
  fallback: "rule-based", // "rule-based" (default) | "none"
});

const controller = new PromptUIController({ fields, parser });

Options:

| Option | Type | Default | Description | |---|---|---|---| | call | LLMCallFn | required | Your LLM transport function | | fallback | "rule-based" \| "none" | "rule-based" | What to do when LLM fails |

Best for: Consumer-facing apps where you want the broadest possible prompt coverage and can afford the latency/cost of LLM calls.


Mode 3: Hybrid — Rule-Based First, LLM Fallback

Minimum cost, maximum speed. Tries the instant rule-based parser first. Only calls the LLM when the rule-based parser returns null (unrecognized prompt). This keeps ~80% of prompts instant and free, while the LLM covers the remaining complex/ambiguous ones.

User prompt ──→ Rule-based parser ──→ VizSpec found? ──→ Render (instant)
                                          │
                                      null (can't parse)
                                          │
                                    LLM parser (async)
                                          │
                                    valid VizSpec? ──→ Render
                                          │
                                       null ──→ Show error

Usage:

import { PromptUIController } from "@rameshnaik/prompt-ui";
import { createHybridParser } from "@rameshnaik/prompt-ui/llm";

const parser = createHybridParser({
  call: async (systemPrompt, userPrompt) => {
    const res = await fetch("https://api.openai.com/v1/chat/completions", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${API_KEY}`,
      },
      body: JSON.stringify({
        model: "gpt-4o-mini",
        messages: [
          { role: "system", content: systemPrompt },
          { role: "user", content: userPrompt },
        ],
        temperature: 0,
      }),
    });
    const data = await res.json();
    return data.choices[0].message.content;
  },
});

const controller = new PromptUIController({ fields, parser });

Best for: Production apps that want the best balance of speed, cost, and coverage. Most prompts resolve instantly via rules; complex ones gracefully upgrade to LLM.


Mode Comparison

| | Mode 1: Rule-Based | Mode 2: LLM-First | Mode 3: Hybrid | |---|---|---|---| | Latency | Instant (~0ms) | ~1-3s per prompt | Instant for known patterns, ~1-3s for complex | | Cost | Free | Every prompt costs tokens | Only unrecognized prompts cost tokens | | Offline | Yes | No (falls back to rules) | Degrades gracefully to rule-based | | Coverage | Common patterns | Handles anything | Best of both | | Setup | None | Provide call function | Provide call function | | Complex prompts | May fail | Works | Works |


The call Function

Both createLLMParser and createHybridParser accept a call function with this signature:

type LLMCallFn = (systemPrompt: string, userPrompt: string) => Promise<string>;
  • systemPrompt — The library generates this automatically from your fields. It describes the available data schema and the expected JSON output format. You never need to write this yourself.
  • userPrompt — The raw text the user typed (e.g. "pie chart by department").
  • Return value — The raw text response from the LLM. The library validates and parses it internally.

The library never touches API keys, never bundles provider SDKs, and works with any LLM backend. You own the transport.


Provider Examples

Below are call function implementations for popular LLM providers. Pick the one that matches your setup:

OpenAI (direct):

const call = async (systemPrompt: string, userPrompt: string) => {
  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${OPENAI_API_KEY}`,
    },
    body: JSON.stringify({
      model: "gpt-4o-mini",
      messages: [
        { role: "system", content: systemPrompt },
        { role: "user", content: userPrompt },
      ],
      temperature: 0,
    }),
  });
  const data = await res.json();
  return data.choices[0].message.content;
};

Anthropic (direct):

const call = async (systemPrompt: string, userPrompt: string) => {
  const res = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
    },
    body: JSON.stringify({
      model: "claude-sonnet-4-20250514",
      max_tokens: 1024,
      system: systemPrompt,
      messages: [{ role: "user", content: userPrompt }],
    }),
  });
  const data = await res.json();
  return data.content[0].text;
};

Your own backend proxy (recommended for production):

const call = async (systemPrompt: string, userPrompt: string) => {
  const res = await fetch("/api/prompt-ui/parse", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
  });
  const data = await res.json();
  return data.text;
};

Security tip: Never expose API keys in frontend code. Use a backend proxy endpoint that holds the key server-side.

Ollama (local):

const call = async (systemPrompt: string, userPrompt: string) => {
  const res = await fetch("http://localhost:11434/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "llama3",
      stream: false,
      messages: [
        { role: "system", content: systemPrompt },
        { role: "user", content: userPrompt },
      ],
    }),
  });
  const data = await res.json();
  return data.message.content;
};

Loading State

When using Mode 2 or Mode 3 with an async LLM call, the prompt bar automatically:

  • Shows a "Thinking..." spinner with a loading animation
  • Disables the input field and buttons to prevent duplicate submissions
  • Displays "Generating..." on the submit button

The controller exposes isLoading (boolean) and fires state-change events during loading transitions, so you can build custom loading UI if needed.


Complete LLM Integration Examples

Below are full working examples showing how to integrate LLM parsing in each supported framework. All examples use Mode 3 (hybrid) — swap createHybridParser for createLLMParser if you prefer Mode 2.

React + LLM

import { usePromptUI } from "@rameshnaik/prompt-ui/react";
import { createHybridParser } from "@rameshnaik/prompt-ui/llm";
import type { FieldDescriptor } from "@rameshnaik/prompt-ui";

const FIELDS: FieldDescriptor[] = [
  { key: "name",       label: "Name",       type: "string" },
  { key: "department", label: "Department",  type: "string" },
  { key: "hours",      label: "Hours",       type: "number" },
];

const DATA = [
  { name: "Alice", department: "Sales",       hours: 40 },
  { name: "Bob",   department: "Engineering", hours: 45 },
  { name: "Carol", department: "Sales",       hours: 38 },
];

const parser = createHybridParser({
  call: async (systemPrompt, userPrompt) => {
    const res = await fetch("/api/prompt-ui/parse", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
    });
    const data = await res.json();
    return data.text;
  },
  getData: () => DATA,
});

export default function TimecardPage() {
  const promptUI = usePromptUI({ fields: FIELDS, parser });

  return (
    <>
      <prompt-ui-bar
        ref={promptUI.bar}
        examplePrompts={["Pie chart by department", "Who works the most?"]}
      />
      <prompt-ui-canvas
        ref={promptUI.canvas}
        data={DATA as unknown as Record<string, unknown>[]}
        fields={FIELDS}
      />
    </>
  );
}

Angular + LLM

import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { PromptUIController } from "@rameshnaik/prompt-ui";
import { createHybridParser } from "@rameshnaik/prompt-ui/llm";
import "@rameshnaik/prompt-ui";

@Component({
  selector: "app-timecard",
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <prompt-ui-bar
      [controller]="controller"
      [examplePrompts]="examples"
    ></prompt-ui-bar>
    <prompt-ui-canvas
      [controller]="controller"
      [data]="data"
      [fields]="fields"
    ></prompt-ui-canvas>
  `,
})
export class TimecardComponent {
  fields = [
    { key: "name",       label: "Name",       type: "string" },
    { key: "department", label: "Department",  type: "string" },
    { key: "hours",      label: "Hours",       type: "number" },
  ];

  data = [
    { name: "Alice", department: "Sales",       hours: 40 },
    { name: "Bob",   department: "Engineering", hours: 45 },
  ];

  examples = ["Pie chart by department", "Compare hours across departments"];

  parser = createHybridParser({
    call: async (systemPrompt, userPrompt) => {
      const res = await fetch("/api/prompt-ui/parse", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
      });
      const data = await res.json();
      return data.text;
    },
    getData: () => this.data,
  });

  controller = new PromptUIController({
    fields: this.fields,
    parser: this.parser,
  });
}

Vue + LLM

<script setup lang="ts">
import { PromptUIController } from "@rameshnaik/prompt-ui";
import { createHybridParser } from "@rameshnaik/prompt-ui/llm";
import type { FieldDescriptor } from "@rameshnaik/prompt-ui";
import "@rameshnaik/prompt-ui";

const fields: FieldDescriptor[] = [
  { key: "name",       label: "Name",       type: "string" },
  { key: "department", label: "Department",  type: "string" },
  { key: "hours",      label: "Hours",       type: "number" },
];

const data = [
  { name: "Alice", department: "Sales",       hours: 40 },
  { name: "Bob",   department: "Engineering", hours: 45 },
];

const parser = createHybridParser({
  call: async (systemPrompt, userPrompt) => {
    const res = await fetch("/api/prompt-ui/parse", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
    });
    const json = await res.json();
    return json.text;
  },
  getData: () => data,
});

const controller = new PromptUIController({ fields, parser });
</script>

<template>
  <prompt-ui-bar
    :controller="controller"
    :examplePrompts="['Pie chart by department', 'Analyze hours trends']"
  />
  <prompt-ui-canvas
    :controller="controller"
    :data="data"
    :fields="fields"
  />
</template>

Vanilla JS + LLM

<script type="module">
  import { PromptUIController } from "@rameshnaik/prompt-ui";
  import { createHybridParser } from "@rameshnaik/prompt-ui/llm";

  const fields = [
    { key: "name",       label: "Name",       type: "string" },
    { key: "department", label: "Department",  type: "string" },
    { key: "hours",      label: "Hours",       type: "number" },
  ];

  const data = [
    { name: "Alice", department: "Sales",       hours: 40 },
    { name: "Bob",   department: "Engineering", hours: 45 },
  ];

  const parser = createHybridParser({
    call: async (systemPrompt, userPrompt) => {
      const res = await fetch("/api/prompt-ui/parse", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ system: systemPrompt, user: userPrompt }),
      });
      const json = await res.json();
      return json.text;
    },
    getData: () => data,
  });

  const controller = new PromptUIController({ fields, parser, getData: () => data });

  document.getElementById("bar").controller = controller;
  document.getElementById("canvas").controller = controller;
  document.getElementById("canvas").data = data;
  document.getElementById("canvas").fields = fields;
</script>

The getData Option (Data-Aware LLM)

The createHybridParser accepts an optional getData function that provides the current dataset to the system prompt. When supplied, the library includes a dataset summary (row count, unique values, min/max/avg for numeric fields) in the system prompt sent to the LLM. This helps the LLM make smarter decisions about which chart types, groupings, and filters best fit your actual data.

const parser = createHybridParser({
  call: myLLMCall,
  getData: () => fetchedData, // provide live data
});

Without getData, the LLM only sees the field schema. With it, the LLM also sees:

Dataset summary (150 rows):
  - Name: 45 unique values, top: "Alice"(12), "Bob"(8), "Carol"(7)
  - Department: 4 unique values, top: "Sales"(52), "Engineering"(41), "HR"(32)
  - Hours: min=20, max=60, avg=38.5

This enables prompts like "analyze this data" or "find insights" to produce meaningful results.


Backend Proxy Example (Node.js/Express)

In production, never expose API keys in frontend code. Here's a minimal backend proxy:

import express from "express";

const app = express();
app.use(express.json());

app.post("/api/prompt-ui/parse", async (req, res) => {
  const { system, user } = req.body;

  const response = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
    },
    body: JSON.stringify({
      model: "gpt-4o-mini",
      messages: [
        { role: "system", content: system },
        { role: "user", content: user },
      ],
      temperature: 0,
    }),
  });

  const data = await response.json();
  res.json({ text: data.choices[0].message.content });
});

app.listen(3001);

Advanced: Using Exported Utilities

The /llm sub-entrypoint exports two utility functions for building custom parser pipelines:

import { buildSystemPrompt, validateAndParse } from "@rameshnaik/prompt-ui/llm";

buildSystemPrompt(fields, data?) — Generates the system prompt string that describes your data schema and expected JSON output format. Use this to inspect what the library sends to the LLM, or to build a completely custom parser.

import { buildSystemPrompt } from "@rameshnaik/prompt-ui/llm";

const prompt = buildSystemPrompt(fields, data);
console.log(prompt); // see exactly what the LLM receives

validateAndParse(raw, fields) — Takes a raw LLM text response and validates/parses it into an ActionResult. Returns null if the response is malformed. Use this to integrate your own LLM call logic while reusing the library's validation.

import { validateAndParse } from "@rameshnaik/prompt-ui/llm";

const raw = await myCustomLLMCall(prompt);
const result = validateAndParse(raw, fields);
if (result) {
  // valid VizSpec, ThemeCommand, etc.
}

Recommended Models

The library works with any LLM that can return valid JSON. Recommended options:

| Model | Provider | Notes | |---|---|---| | gpt-4o-mini | OpenAI | Best cost/quality ratio for structured JSON | | gpt-4o | OpenAI | Higher accuracy for complex prompts | | claude-sonnet-4-20250514 | Anthropic | Excellent JSON adherence | | llama3 | Ollama (local) | Free, offline, good for development | | gemini-2.0-flash | Google | Fast and cost-effective |

Tip: Use temperature: 0 for deterministic, consistent results. The library's system prompt is optimized for JSON output — most models follow it reliably.


API Reference

PromptUIController

The central state manager. Create one instance per page and share it between the bar and canvas.

const controller = new PromptUIController(options);

| Option | Type | Default | Description | |---|---|---|---| | fields | FieldDescriptor[] | required | Describes available data columns | | getData | () => Record<string, unknown>[] | — | Returns current data at prompt time | | parser | ParserFn | built-in rule-based | Custom parser function (sync or async) | | maxWidgets | number | 6 | Max stacked widgets | | maxHistory | number | 50 | Max prompt history entries |

Methods:

| Method | Description | |---|---| | submitPrompt(text) | Parse and execute a prompt (returns Promise<void> — async for LLM parsers) | | confirmAction() | Confirm a pending theme change | | cancelAction() | Cancel a pending theme change | | dismissWidget(id) | Remove a specific widget | | undoLast() | Remove the most recent widget | | clearAll() | Remove all widgets | | revertTransform() | Revert the active layout transform and restore original content | | navigateHistory(dir) | Navigate prompt history ("up" or "down") |

Properties (read-only):

| Property | Type | Description | |---|---|---| | widgets | ActiveWidget[] | Currently active widgets | | history | string[] | Past prompts | | error | string \| null | Current error message | | toast | PromptToast \| null | Current toast notification | | pendingConfirmation | PendingConfirmation \| null | Pending theme confirmation | | activeTransform | ActiveTransform \| null | Currently active layout transform | | isActive | boolean | Whether any widgets are shown | | isLoading | boolean | Whether an async parser call is in progress |

Events:

| Event | Description | |---|---| | state-change | Fired whenever any state property changes (including isLoading transitions) |

<prompt-ui-bar>

The prompt input bar with voice input, undo, clear, and generate controls.

| Property | Type | Default | Description | |---|---|---|---| | controller | PromptUIController | required | The controller instance | | placeholder | string | "Describe what you want to see…" | Input placeholder text | | examplePrompts | string[] | [] | Example chips shown below the bar |

<prompt-ui-canvas>

Renders the generated widgets (charts, tables, list-detail views) and handles layout transforms.

| Property | Type | Default | Description | |---|---|---|---| | controller | PromptUIController | required | The controller instance | | data | Record<string, unknown>[] | [] | The dataset to visualize | | fields | FieldDescriptor[] | [] | Field descriptors for the data | | contentTarget | string | "" | CSS selector for existing page content to hide when a layout transform is active |

usePromptUI (React only)

import { usePromptUI } from "@rameshnaik/prompt-ui/react";

const promptUI = usePromptUI({ fields });
// promptUI.controller — the PromptUIController instance
// promptUI.bar        — ref callback for <prompt-ui-bar>
// promptUI.canvas     — ref callback for <prompt-ui-canvas>

Layout Transform

The layout transform feature lets users convert existing page content into a different layout using natural language. Unlike widgets (which stack on top of your page), a layout transform replaces the existing content and provides a "Revert to original" button.

The content-target Attribute

The content-target attribute (or contentTarget JS property) accepts a CSS selector that identifies which element(s) on your page should be hidden when a layout transform is active. This is the bridge between the library and your existing page content.

| Attribute (HTML) | Property (JS) | Type | Default | |---|---|---|---| | content-target | contentTarget | string (CSS selector) | "" (empty — no content is hidden) |

Key behaviors:

  • The selector is resolved via document.querySelectorAll(), so it can match one or multiple elements
  • Matched elements are hidden by setting display: none — their DOM position is preserved
  • When the transform is reverted, the original display value is restored exactly as it was
  • If content-target is empty or not set, layout transforms still render in the canvas but nothing else on the page is hidden
  • The canvas is cleaned up properly on disconnectedCallback — navigating away automatically restores hidden elements

How it works

  1. Set the content-target attribute on <prompt-ui-canvas> to a CSS selector identifying the existing content to replace:
<prompt-ui-canvas id="canvas" content-target="#my-list"></prompt-ui-canvas>
<ul id="my-list">
  <!-- your existing list — hidden during transforms -->
</ul>
  1. When the user types a transform prompt (e.g. "convert to table view"), the library:

    • Queries all elements matching the content-target selector
    • Saves their current display style and sets display: none
    • Renders the new layout (table, list-detail, or chart) inside the canvas
    • Shows a "Revert to original" button in the header
  2. Clicking "Revert to original" (or calling controller.revertTransform()) restores the original elements' display values.

Selector Patterns

<!-- Single element by ID -->
<prompt-ui-canvas content-target="#employee-list"></prompt-ui-canvas>

<!-- Multiple elements by class -->
<prompt-ui-canvas content-target=".data-section"></prompt-ui-canvas>

<!-- Complex selector — hides both the list and its header -->
<prompt-ui-canvas content-target="#list-header, #list-body"></prompt-ui-canvas>

<!-- Attribute selector -->
<prompt-ui-canvas content-target="[data-replaceable]"></prompt-ui-canvas>

Supported prompts

| Prompt example | Result | |---|---| | "convert to table view" | Replaces content with a data table | | "change this to side panel view" | Replaces content with a list-detail panel | | "switch to pie chart" | Replaces content with a pie chart | | "transform to bar chart view" | Replaces content with a bar chart | | "show data as grid" | Replaces content with a table grid |

Framework examples

React:

<prompt-ui-canvas
  ref={promptUI.canvas}
  data={data}
  fields={fields}
  contentTarget="#employee-list"
/>

Angular:

<prompt-ui-canvas
  [controller]="controller"
  [data]="data"
  [fields]="fields"
  contentTarget="#employee-list"
></prompt-ui-canvas>

Vue:

<prompt-ui-canvas
  :controller="controller"
  :data="data"
  :fields="fields"
  content-target="#employee-list"
/>

Svelte:

<prompt-ui-canvas
  controller={controller}
  {data}
  {fields}
  content-target="#employee-list"
/>

Vanilla JS:

const canvas = document.getElementById("canvas");
canvas.contentTarget = "#employee-list";
// or via HTML attribute:
// <prompt-ui-canvas content-target="#employee-list"></prompt-ui-canvas>

Full Page Example

Here's a complete example showing content-target in a real layout:

<prompt-ui-bar id="bar"></prompt-ui-bar>
<prompt-ui-canvas id="canvas" content-target="#original-content"></prompt-ui-canvas>

<div id="original-content">
  <h2>Employee Directory</h2>
  <ul>
    <li>Alice — Sales — 40h</li>
    <li>Bob — Engineering — 45h</li>
    <li>Carol — Sales — 38h</li>
  </ul>
</div>

<script type="module">
  import { PromptUIController } from "@rameshnaik/prompt-ui";

  const fields = [
    { key: "name",       label: "Name",       type: "string" },
    { key: "department", label: "Department",  type: "string" },
    { key: "hours",      label: "Hours",       type: "number" },
  ];

  const data = [
    { name: "Alice", department: "Sales",       hours: 40 },
    { name: "Bob",   department: "Engineering", hours: 45 },
    { name: "Carol", department: "Sales",       hours: 38 },
  ];

  const controller = new PromptUIController({ fields, getData: () => data });

  const bar = document.getElementById("bar");
  bar.controller = controller;
  bar.examplePrompts = ["Convert to table view", "Show as side panel"];

  const canvas = document.getElementById("canvas");
  canvas.controller = controller;
  canvas.data = data;
  canvas.fields = fields;
</script>

When the user types "convert to table view", the #original-content div is hidden and a generated data table appears in the canvas. Clicking "Revert to original" brings back the directory list.

Programmatic Revert

You can revert a layout transform from code at any time:

controller.revertTransform();

This restores all hidden elements and removes the generated transform from the canvas.

License

MIT