Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/material-ui-chat/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# OpenAI API key used by the chat route (src/app/api/chat/route.ts).
# Copy this file to `.env.local` and fill in your key.
OPENAI_API_KEY=
5 changes: 5 additions & 0 deletions examples/material-ui-chat/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.next
node_modules
.env
.env.local
*.tsbuildinfo
136 changes: 136 additions & 0 deletions examples/material-ui-chat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Material UI Chat Example

A full-stack generative UI chatbot that wires [OpenUI Lang](https://www.openui.com/docs/openui-lang/overview) to a custom component library built on [Material UI](https://mui.com/material-ui/). Instead of replying with plain text, the LLM generates structured UI markup that the client renders as MUI components — cards, tables, charts, forms, tabs, accordions, alerts, lists, and more — in real time as tokens stream in.

This is the MUI counterpart to the [`shadcn-chat`](../shadcn-chat) example. It shows how to map OpenUI Lang nodes onto an existing design system (MUI components + theme), including light/dark mode.

## How It Works

The LLM is given a system prompt that describes every available Material UI component — its name, props, and when to use it. Instead of prose, the model responds in **OpenUI Lang**, a declarative markup that maps directly to React components:

```
root = Card([header, tbl])
header = CardHeader("Q1 Sales")
tbl = Table([Col("Product"), Col("Revenue", "number")], [["Widget", 1200]])
```

On the client, `<FullScreen />` from `@openuidev/react-ui` manages conversation state, streaming, input, and rendering. It parses the incoming SSE stream with `openAIAdapter()` and renders each OpenUI Lang node using `muiChatLibrary` — the custom component library defined in `src/lib/mui-genui/`.

## Architecture

```
┌────────────────────────────────────┐ ┌────────────────────────────────────┐
│ Browser │ HTTP │ Next.js API Route │
│ │ ──────►│ │
│ • <FullScreen /> manages UI │ │ • Loads system-prompt.txt │
│ • openAIAdapter() parses SSE │◄────── │ • Calls LLM with runTools │
│ • muiChatLibrary renders nodes │ SSE │ • Executes tools server-side │
│ • MUI ThemeProvider + CssBaseline │ │ • Streams response as SSE events │
└────────────────────────────────────┘ └────────────────────────────────────┘
```

1. The user types a message. `<FullScreen />` calls `processMessage`, which `POST`s to `/api/chat` with the conversation history formatted via `openAIMessageFormat.toApi()`.
2. The API route reads `src/generated/system-prompt.txt`, instantiates an OpenAI client, and calls `runTools` — the OpenAI SDK's multi-step tool-execution loop.
3. Tool calls run server-side; results are fed back to the model automatically and emitted as SSE events.
4. The LLM streams a final OpenUI Lang response. The stream ends with `data: [DONE]`.
5. The client parses the events with `openAIAdapter()` and renders each node as a Material UI component as it streams in.

## Project Structure

```
material-ui-chat/
├── src/
│ ├── library.ts # Entry the OpenUI CLI reads to generate the prompt
│ ├── app/
│ │ ├── api/chat/route.ts # Streaming chat endpoint (OpenAI SDK + SSE)
│ │ ├── page.tsx # Mounts <FullScreen /> + color-mode toggle
│ │ ├── layout.tsx # Root layout with ColorModeProvider
│ │ └── globals.css # Minimal full-height reset
│ ├── hooks/
│ │ └── use-system-theme.tsx # MUI ThemeProvider + light/dark color mode
│ ├── lib/
│ │ └── mui-genui/ # Custom OpenUI component library (Material UI)
│ │ ├── index.tsx # Library export — createLibrary() call
│ │ ├── theme.ts # MUI theme factory + chart palette
│ │ ├── action.ts # Button action Zod schemas
│ │ ├── helpers.ts # Chart data builders for @mui/x-charts
│ │ ├── rules.ts # Form validation rule schemas
│ │ ├── unions.ts # Zod union types for component children
│ │ └── components/ # One file per component (MUI wrappers)
│ └── generated/
│ └── system-prompt.txt # Auto-generated — do not edit manually
└── package.json
```

## Components

The library exposes a representative subset of Material UI components mapped to OpenUI Lang:

| Category | Components |
| ---------- | ---------- |
| Content | `CardHeader`, `TextContent`, `Heading`, `Alert`, `List` / `ListItem`, `Separator`, `Progress` |
| Tables | `Table` / `Col` |
| Charts | `BarChart`, `LineChart`, `PieChart` (via `@mui/x-charts`) with `Series` / `Slice` |
| Forms | `Form`, `FormControl`, `Input`, `Select` / `SelectItem`, `SwitchGroup` / `SwitchItem` |
| Buttons | `Button`, `Buttons` |
| Layout | `Tabs` / `TabItem`, `Accordion` / `AccordionItem` |
| Follow-ups | `FollowUpBlock` / `FollowUpItem` |

Each component is defined with `defineComponent({ name, props, description, component })` where `props` is a Zod schema. The schema and description are serialized into the system prompt by `pnpm generate:prompt` (the OpenUI CLI reads `src/library.ts`), and `component` renders the node with Material UI primitives.

## Theming

The app wraps everything in MUI's `ThemeProvider` + `CssBaseline` via `ColorModeProvider` (`src/hooks/use-system-theme.tsx`). It follows the OS color scheme by default and exposes a manual light/dark toggle (top-right of the screen). All generated components — and the chat surface — re-theme automatically. Customize `createAppTheme()` in `src/lib/mui-genui/theme.ts`.

## Getting Started

### Prerequisites

- Node.js 20.x
- pnpm 9+
- An OpenAI API key

### Setup

From the monorepo root, install dependencies (this example is part of the pnpm workspace):

```bash
pnpm install
```

Provide your API key:

```bash
cd examples/material-ui-chat
cp .env.example .env.local
# edit .env.local and set OPENAI_API_KEY
```

### Develop

```bash
pnpm dev
```

`pnpm dev` first runs `generate:prompt` to (re)generate `src/generated/system-prompt.txt` from the library, then starts Next.js on http://localhost:3000.

### Regenerate the system prompt

Whenever you add or change a component, regenerate the prompt:

```bash
pnpm generate:prompt
```

### Build

```bash
pnpm build
```

## Adding a Component

1. Create `src/lib/mui-genui/components/<name>.tsx` and export a `defineComponent({ ... })`.
2. If it can appear inside other containers, add its `.ref` to `ContentChildUnion` in `unions.ts`.
3. Register it in the `components` array (and a `componentGroups` entry) in `index.tsx`.
4. Run `pnpm generate:prompt` so the LLM learns about it.
6 changes: 6 additions & 0 deletions examples/material-ui-chat/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
7 changes: 7 additions & 0 deletions examples/material-ui-chat/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
turbopack: {},
};

export default nextConfig;
36 changes: 36 additions & 0 deletions examples/material-ui-chat/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "material-ui-chat",
"version": "0.1.0",
"private": true,
"scripts": {
"generate:prompt": "pnpm --filter @openuidev/cli build && pnpm exec openui generate src/library.ts --out src/generated/system-prompt.txt",
"dev": "pnpm generate:prompt && next dev",
"build": "pnpm generate:prompt && next build",
"start": "next start",
"lint": "eslint"
},
"dependencies": {
"@emotion/react": "^11.13.5",
"@emotion/styled": "^11.13.5",
"@mui/icons-material": "^6.4.12",
"@mui/material": "^6.4.12",
"@mui/x-charts": "^7.29.1",
"@openuidev/react-headless": "workspace:*",
"@openuidev/react-lang": "workspace:*",
"@openuidev/react-ui": "workspace:*",
"next": "16.1.6",
"openai": "^6.22.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"zod": "^4.0.0"
},
"devDependencies": {
"@openuidev/cli": "workspace:*",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.1.6",
"typescript": "^5"
}
}
Loading
Loading