npm.io
0.0.4 • Published 3 months ago

jsx2ui

Licence
—
Version
0.0.4
Deps
3
Size
126 kB
Vulns
0
Weekly
0

jsx2ui

jsx2ui helps you integrate generative, model-authored UI into your app. It turns JSX returned by your model tool calls into serializable UI JSON and renders it with your own React component catalog.

Installation

npm install jsx2ui

Table of Contents

Package Entrypoints

Entrypoint Layer Use for
jsx2ui Core Framework-agnostic primitives like schema/prompt generation and JSX-to-JSON transformation
jsx2ui/ai-sdk Server AI SDK createUI tool and createGenUI helper
jsx2ui/react Frontend Shared React primitives (createCatalog, Renderer, result guards/types)
jsx2ui/react/ai-sdk Frontend AI SDK message-part rendering utilities

Quick Start (AI SDK + Cloudflare Workers)

1) Define the components in frontend
// src/catalog.tsx

import { createCatalog } from "jsx2ui/react";

function Title({ children, size }: { children: React.ReactNode; size: "sm" | "md" | "lg" }) {
	return <h1 className={`text-${size}`}>{children}</h1>;
}

function Card({ children }: { children: React.ReactNode }) {
	return <div className="card">{children}</div>;
}

const catalog = createCatalog({
	Title,
	Card,
});
2) Define the components schema for the LLM
import { defineComponents, z } from "jsx2ui";

export const components = defineComponents([
	{
		name: "Title",
		acceptsChildren: true,
		props: z.object({
			size: z.enum(["sm", "md", "lg"]).default("md"),
		}),
	},
	{
		name: "Card",
		acceptsChildren: true,
	},
]);
3) Add the UI tool to your AI chat configuration
import { createWorkersAI } from "workers-ai-provider";
import {
	createUIMessageStream,
	createUIMessageStreamResponse,
	convertToModelMessages,
	streamText,
} from "ai";
import { createGenUI } from "jsx2ui/ai-sdk";
import { components } from "./schema";

export default {
	async fetch(request, env) {
		const url = new URL(request.url);

		if (url.pathname.startsWith("/api/chat")) {
			const { messages } = await request.json();
			const workersAi = createWorkersAI({ binding: env.AI });

			const genUI = createGenUI({ loader: env.LOADER, components });
			const modelMessages = await convertToModelMessages(messages);

			const stream = createUIMessageStream({
				execute: ({ writer }) => {
					const textStream = streamText({
						model: workersAi("@cf/zai-org/glm-4.7-flash"),
						system: genUI.systemPrompt(),
						messages: modelMessages,
						tools: genUI.tools(writer),
						stopWhen: [genUI.stopCondition()],
					});
					writer.merge(textStream.toUIMessageStream());
				},
			});

			return createUIMessageStreamResponse({ stream });
		}

		return new Response(null, { status: 404 });
	},
} satisfies ExportedHandler<Env>;
4) Render the UI on the frontend
// src/chat.tsx
import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { useGenUI } from "jsx2ui/react/ai-sdk";
import { catalog } from "./catalog";

function Chat() {
	const { parsePart, onData } = useGenUI({ catalog });

	const { messages, sendMessage } = useChat({
		transport: new DefaultChatTransport({ api: "/api/chat" }),
		onData,
	});

	return (
		<>
			{messages.map((message) => (
				<div key={message.id}>
					{message.parts.map((part, partIdx) => {
						const result = parsePart(part);
						if (result.handled) {
							return <div key={partIdx}>{result.element}</div>;
						}
						if (part.type === "text") {
							return <span key={partIdx}>{part.text}</span>;
						}
						return null;
					})}
				</div>
			))}
		</>
	);
}
5) Styling for the default components (Optional - if using TailwindCSS)

For TailwindCSS v4:

/* src/index.css */
@source "../node_modules/jsx2ui/dist/**/*.*js";

For TailwindCSS v3:

// tailwind.config.js
module.exports = {
	content: [
		"./node_modules/jsx2ui/dist/**/*.*js", // add this to content
	],
};

Server-side APIs

Core framework-agnostic APIs (jsx2ui)
import { defineComponents, z } from "jsx2ui";

const genUI = defineComponents([
	{
		name: "Title",
		acceptsChildren: true,
		props: z.object({
			size: z.enum(["sm", "md", "lg"]).default("md"),
		}),
	},
	{ name: "Card", acceptsChildren: true },
]);

console.log(genUI.getSystemPrompt());
AI SDK server API (jsx2ui/ai-sdk)
import { streamText } from "ai";
import { createGenUI } from "jsx2ui/ai-sdk";

const genUI = createGenUI({ loader: env.LOADER, components });

const result = streamText({
	model,
	system: genUI.systemPrompt(),
	messages,
	tools: genUI.tools(),
	stopWhen: genUI.stopCondition(),
});

Frontend APIs (React)

Shared React APIs (jsx2ui/react)
import React from "react";
import { createCatalog, Renderer, type UINode } from "jsx2ui/react";

const catalog = createCatalog({ Title, Card });

export function GeneratedView({ ui }: { ui: UINode }) {
	return <Renderer node={ui} catalog={catalog} />;
}
AI SDK React APIs (jsx2ui/react/ai-sdk)
import React from "react";
import { isGenUiPart, useGenUI } from "jsx2ui/react/ai-sdk";

const { parsePart, reset } = useGenUI({ catalog });

// Optional guard when filtering AI SDK message parts yourself.
if (isGenUiPart(part)) {
	const result = parsePart(part);
	return <>{result.element}</>;
}

// Call reset() when removing chat messages to release cached generated UI state.

Keywords