diff --git a/examples/material-ui-chat/.env.example b/examples/material-ui-chat/.env.example new file mode 100644 index 000000000..f750f24de --- /dev/null +++ b/examples/material-ui-chat/.env.example @@ -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= diff --git a/examples/material-ui-chat/.gitignore b/examples/material-ui-chat/.gitignore new file mode 100644 index 000000000..09d505398 --- /dev/null +++ b/examples/material-ui-chat/.gitignore @@ -0,0 +1,5 @@ +.next +node_modules +.env +.env.local +*.tsbuildinfo diff --git a/examples/material-ui-chat/README.md b/examples/material-ui-chat/README.md new file mode 100644 index 000000000..3a24912db --- /dev/null +++ b/examples/material-ui-chat/README.md @@ -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, `` 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 │ +│ │ ──────►│ │ +│ • 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. `` 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 + 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/.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. diff --git a/examples/material-ui-chat/next-env.d.ts b/examples/material-ui-chat/next-env.d.ts new file mode 100644 index 000000000..c4b7818fb --- /dev/null +++ b/examples/material-ui-chat/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +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. diff --git a/examples/material-ui-chat/next.config.ts b/examples/material-ui-chat/next.config.ts new file mode 100644 index 000000000..b08275c80 --- /dev/null +++ b/examples/material-ui-chat/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + turbopack: {}, +}; + +export default nextConfig; diff --git a/examples/material-ui-chat/package.json b/examples/material-ui-chat/package.json new file mode 100644 index 000000000..6ce7ee5d5 --- /dev/null +++ b/examples/material-ui-chat/package.json @@ -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" + } +} diff --git a/examples/material-ui-chat/src/app/api/chat/route.ts b/examples/material-ui-chat/src/app/api/chat/route.ts new file mode 100644 index 000000000..4c0ffdb77 --- /dev/null +++ b/examples/material-ui-chat/src/app/api/chat/route.ts @@ -0,0 +1,411 @@ +import { readFileSync } from "fs"; +import { NextRequest } from "next/server"; +import OpenAI from "openai"; +import type { ChatCompletionMessageParam } from "openai/resources/chat/completions.mjs"; +import { join } from "path"; + +const systemPrompt = readFileSync(join(process.cwd(), "src/generated/system-prompt.txt"), "utf-8"); + +const conversationLog: Array<{ role: string; content: string }> = []; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +function extractText(msg: any): string { + const content = msg?.content; + if (typeof content === "string") { + try { + const parsed = JSON.parse(content); + if (parsed?.parts) + return parsed.parts + .filter((p: any) => p.type === "text") + .map((p: any) => p.text) + .join(""); + } catch { + /* plain string */ + } + return content; + } + if (Array.isArray(content)) + return content + .filter((p: any) => p.type === "text") + .map((p: any) => p.text) + .join(""); + if (Array.isArray(msg?.parts)) + return msg.parts + .filter((p: any) => p.type === "text") + .map((p: any) => p.text) + .join(""); + if (typeof msg?.text === "string") return msg.text; + return JSON.stringify(msg); +} +/* eslint-enable @typescript-eslint/no-explicit-any */ + +// ── Tool implementations ── + +function getWeather({ location }: { location: string }): Promise { + return new Promise((resolve) => { + setTimeout(() => { + const knownTemps: Record = { + tokyo: 22, + "san francisco": 18, + london: 14, + "new york": 25, + paris: 19, + sydney: 27, + mumbai: 33, + berlin: 16, + }; + const conditions = ["Sunny", "Partly Cloudy", "Cloudy", "Light Rain", "Clear Skies"]; + const temp = knownTemps[location.toLowerCase()] ?? Math.floor(Math.random() * 30 + 5); + const condition = conditions[Math.floor(Math.random() * conditions.length)]; + resolve( + JSON.stringify({ + location, + temperature_celsius: temp, + temperature_fahrenheit: Math.round(temp * 1.8 + 32), + condition, + humidity_percent: Math.floor(Math.random() * 40 + 40), + wind_speed_kmh: Math.floor(Math.random() * 25 + 5), + forecast: [ + { day: "Tomorrow", high: temp + 2, low: temp - 4, condition: "Partly Cloudy" }, + { day: "Day After", high: temp + 1, low: temp - 3, condition: "Sunny" }, + ], + }), + ); + }, 800); + }); +} + +function getStockPrice({ symbol }: { symbol: string }): Promise { + return new Promise((resolve) => { + setTimeout(() => { + const s = symbol.toUpperCase(); + const knownPrices: Record = { + AAPL: 189.84, + GOOGL: 141.8, + TSLA: 248.42, + MSFT: 378.91, + AMZN: 178.25, + NVDA: 875.28, + META: 485.58, + }; + const price = knownPrices[s] ?? Math.floor(Math.random() * 500 + 20); + const change = parseFloat((Math.random() * 8 - 4).toFixed(2)); + resolve( + JSON.stringify({ + symbol: s, + price: parseFloat((price + change).toFixed(2)), + change, + change_percent: parseFloat(((change / price) * 100).toFixed(2)), + volume: `${(Math.random() * 50 + 10).toFixed(1)}M`, + day_high: parseFloat((price + Math.abs(change) + 1.5).toFixed(2)), + day_low: parseFloat((price - Math.abs(change) - 1.2).toFixed(2)), + }), + ); + }, 600); + }); +} + +function calculate({ expression }: { expression: string }): Promise { + return new Promise((resolve) => { + setTimeout(() => { + try { + const sanitized = expression.replace( + /[^0-9+\-*/().%\s,Math.sqrtpowabsceilfloorround]/g, + "", + ); + + const result = new Function(`return (${sanitized})`)(); + resolve(JSON.stringify({ expression, result: Number(result) })); + } catch { + resolve(JSON.stringify({ expression, error: "Invalid expression" })); + } + }, 300); + }); +} + +function searchWeb({ query }: { query: string }): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve( + JSON.stringify({ + query, + results: [ + { + title: `Top result for "${query}"`, + snippet: `Comprehensive overview of ${query} with the latest information.`, + }, + { + title: `${query} - Latest News`, + snippet: `Recent developments and updates related to ${query}.`, + }, + { + title: `Understanding ${query}`, + snippet: `An in-depth guide explaining everything about ${query}.`, + }, + ], + }), + ); + }, 1000); + }); +} + +// ── Tool definitions ── + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const tools: any[] = [ + { + type: "function", + function: { + name: "get_weather", + description: "Get current weather for a location.", + parameters: { + type: "object", + properties: { location: { type: "string", description: "City name" } }, + required: ["location"], + }, + function: getWeather, + parse: JSON.parse, + }, + }, + { + type: "function", + function: { + name: "get_stock_price", + description: "Get stock price for a ticker symbol.", + parameters: { + type: "object", + properties: { symbol: { type: "string", description: "Ticker symbol, e.g. AAPL" } }, + required: ["symbol"], + }, + function: getStockPrice, + parse: JSON.parse, + }, + }, + { + type: "function", + function: { + name: "calculate", + description: "Evaluate a math expression.", + parameters: { + type: "object", + properties: { expression: { type: "string", description: "Math expression to evaluate" } }, + required: ["expression"], + }, + function: calculate, + parse: JSON.parse, + }, + }, + { + type: "function", + function: { + name: "search_web", + description: "Search the web for information.", + parameters: { + type: "object", + properties: { query: { type: "string", description: "Search query" } }, + required: ["query"], + }, + function: searchWeb, + parse: JSON.parse, + }, + }, +]; + +// ── SSE helpers ── + +function sseToolCallStart( + encoder: TextEncoder, + tc: { id: string; function: { name: string } }, + index: number, +) { + return encoder.encode( + `data: ${JSON.stringify({ + id: `chatcmpl-tc-${tc.id}`, + object: "chat.completion.chunk", + choices: [ + { + index: 0, + delta: { + tool_calls: [ + { + index, + id: tc.id, + type: "function", + function: { name: tc.function.name, arguments: "" }, + }, + ], + }, + finish_reason: null, + }, + ], + })}\n\n`, + ); +} + +function sseToolCallArgs( + encoder: TextEncoder, + tc: { id: string; function: { arguments: string } }, + result: string, + index: number, +) { + let enrichedArgs: string; + try { + enrichedArgs = JSON.stringify({ + _request: JSON.parse(tc.function.arguments), + _response: JSON.parse(result), + }); + } catch { + enrichedArgs = tc.function.arguments; + } + return encoder.encode( + `data: ${JSON.stringify({ + id: `chatcmpl-tc-${tc.id}-args`, + object: "chat.completion.chunk", + choices: [ + { + index: 0, + delta: { tool_calls: [{ index, function: { arguments: enrichedArgs } }] }, + finish_reason: null, + }, + ], + })}\n\n`, + ); +} + +// ── Route handler ── + +export async function POST(req: NextRequest) { + const { messages } = await req.json(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const lastUserMsg = (messages as any[]).filter((m: any) => m.role === "user").pop(); + if (lastUserMsg) conversationLog.push({ role: "user", content: extractText(lastUserMsg) }); + + const client = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, + }); + const MODEL = "gpt-5.5"; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const cleanMessages = (messages as any[]) + .filter((m) => m.role !== "tool") + .map((m) => { + if (m.role === "assistant" && m.tool_calls?.length) { + // Strip tool_calls (runTools re-runs the agentic loop server-side) + // but preserve content so prior replies remain in context. + const { tool_calls: _tc, ...rest } = m; // eslint-disable-line @typescript-eslint/no-unused-vars + return rest; + } + return m; + }); + + const chatMessages: ChatCompletionMessageParam[] = [ + { role: "system", content: systemPrompt }, + ...cleanMessages, + ]; + + const encoder = new TextEncoder(); + let controllerClosed = false; + + const readable = new ReadableStream({ + start(controller) { + const enqueue = (data: Uint8Array) => { + if (controllerClosed) return; + try { + controller.enqueue(data); + } catch { + /* already closed */ + } + }; + const close = () => { + if (controllerClosed) return; + controllerClosed = true; + try { + controller.close(); + } catch { + /* already closed */ + } + }; + + let fullResponse = ""; + const pendingCalls: Array<{ id: string; name: string; arguments: string }> = []; + let callIdx = 0; + let resultIdx = 0; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const runner = (client.chat.completions as any).runTools({ + model: MODEL, + messages: chatMessages, + tools, + stream: true, + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + runner.on("functionToolCall", (fc: any) => { + const id = `tc-${callIdx}`; + pendingCalls.push({ id, name: fc.name, arguments: fc.arguments }); + enqueue(sseToolCallStart(encoder, { id, function: { name: fc.name } }, callIdx)); + callIdx++; + }); + + runner.on("functionToolCallResult", (result: string) => { + const tc = pendingCalls[resultIdx]; + if (tc) { + enqueue( + sseToolCallArgs( + encoder, + { id: tc.id, function: { arguments: tc.arguments } }, + result, + resultIdx, + ), + ); + } + resultIdx++; + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + runner.on("chunk", (chunk: any) => { + const choice = chunk.choices?.[0]; + const delta = choice?.delta; + if (!delta) return; + if (delta.content) { + fullResponse += delta.content; + enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`)); + } + if (choice?.finish_reason === "stop") { + enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`)); + } + }); + + runner.on("end", () => { + conversationLog.push({ role: "assistant", content: fullResponse }); + console.info( + "[OpenUI Lang] Conversation:\n", + JSON.stringify( + conversationLog.map((m) => ({ ...m, content: m.content.replace(/\n/g, " ") })), + null, + 2, + ), + ); + enqueue(encoder.encode("data: [DONE]\n\n")); + close(); + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + runner.on("error", (err: any) => { + const msg = err instanceof Error ? err.message : "Stream error"; + console.error("Chat route error:", msg); + enqueue(encoder.encode(`data: ${JSON.stringify({ error: msg })}\n\n`)); + close(); + }); + }, + }); + + return new Response(readable, { + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache, no-transform", + Connection: "keep-alive", + }, + }); +} diff --git a/examples/material-ui-chat/src/app/globals.css b/examples/material-ui-chat/src/app/globals.css new file mode 100644 index 000000000..dfe06a90b --- /dev/null +++ b/examples/material-ui-chat/src/app/globals.css @@ -0,0 +1,11 @@ +html, +body { + height: 100%; + margin: 0; + padding: 0; +} + +#__next, +body > div { + height: 100%; +} diff --git a/examples/material-ui-chat/src/app/layout.tsx b/examples/material-ui-chat/src/app/layout.tsx new file mode 100644 index 000000000..ab4b43ba4 --- /dev/null +++ b/examples/material-ui-chat/src/app/layout.tsx @@ -0,0 +1,23 @@ +import type { Metadata } from "next"; + +import { ColorModeProvider } from "@/hooks/use-system-theme"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "Material UI Chat", + description: "Generative UI Chat with Material UI components", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/examples/material-ui-chat/src/app/page.tsx b/examples/material-ui-chat/src/app/page.tsx new file mode 100644 index 000000000..27a218a7d --- /dev/null +++ b/examples/material-ui-chat/src/app/page.tsx @@ -0,0 +1,76 @@ +"use client"; +import "@openuidev/react-ui/components.css"; + +import DarkModeIcon from "@mui/icons-material/DarkMode"; +import LightModeIcon from "@mui/icons-material/LightMode"; +import Box from "@mui/material/Box"; +import IconButton from "@mui/material/IconButton"; +import Tooltip from "@mui/material/Tooltip"; +import { openAIAdapter, openAIMessageFormat } from "@openuidev/react-headless"; +import { FullScreen } from "@openuidev/react-ui"; + +import { useColorMode } from "@/hooks/use-system-theme"; +import { muiChatLibrary } from "@/lib/mui-genui"; + +export default function Page() { + const { mode, toggle } = useColorMode(); + + return ( + + + + + {mode === "dark" ? : } + + + + + { + return fetch("/api/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + messages: openAIMessageFormat.toApi(messages), + }), + signal: abortController.signal, + }); + }} + streamProtocol={openAIAdapter()} + componentLibrary={muiChatLibrary} + agentName="Material UI Chat" + theme={{ mode }} + conversationStarters={{ + variant: "short", + options: [ + { + displayText: "Startup dashboard", + prompt: + "Build a startup analytics dashboard with a CardHeader, Tabs (Revenue BarChart, Growth LineChart, Breakdown PieChart), a key metrics Table, a progress bar toward the annual goal, and follow-ups.", + }, + { + displayText: "Onboarding form", + prompt: + "Create an account onboarding form with an info Alert, then a Form containing inputs for name and email, a Select for plan, and a SwitchGroup for notification preferences. Add follow-ups.", + }, + { + displayText: "Project status", + prompt: + "Generate a project status report with a CardHeader, a warning Alert for blockers, a List of this week's tasks, an Accordion (Done, In progress, Blocked), and a BarChart of story points per engineer. Add follow-ups.", + }, + { + displayText: "Pricing comparison", + prompt: + "Show a pricing comparison with a CardHeader, a Table comparing three plans, a List of included features, and buttons to choose a plan. Add follow-ups.", + }, + { + displayText: "Chart showcase", + prompt: + "Build a 'Quarterly Report' with Tabs containing a grouped BarChart (revenue by region, 2 series), a LineChart (monthly active users, 2 series), and a donut PieChart (traffic sources, 4 slices). Below, add a summary Table and follow-ups.", + }, + ], + }} + /> + + ); +} diff --git a/examples/material-ui-chat/src/generated/system-prompt.txt b/examples/material-ui-chat/src/generated/system-prompt.txt new file mode 100644 index 000000000..0fc1dffd3 --- /dev/null +++ b/examples/material-ui-chat/src/generated/system-prompt.txt @@ -0,0 +1,176 @@ +You are an AI assistant that responds using openui-lang, a declarative UI language. Your ENTIRE response must be valid openui-lang code — no markdown, no explanations, just openui-lang. + +## Syntax Rules + +1. Each statement is on its own line: `identifier = Expression` +2. `root` is the entry point — every program must define `root = Card(...)` +3. Expressions are: strings ("..."), numbers, booleans (true/false), null, arrays ([...]), objects ({...}), or component calls TypeName(arg1, arg2, ...) +4. Use references for readability: define `name = ...` on one line, then use `name` later +5. EVERY variable (except root) MUST be referenced by at least one other variable. Unreferenced variables are silently dropped and will NOT render. Always include defined variables in their parent's children/items array. +6. Arguments are POSITIONAL (order matters, not names). Write `Stack([children], "row", "l")` NOT `Stack([children], direction: "row", gap: "l")` — colon syntax is NOT supported and silently breaks +7. Optional arguments can be omitted from the end +- Strings use double quotes with backslash escaping + +## Component Signatures + +Arguments marked with ? are optional. Sub-components can be inline or referenced; prefer references for better streaming. + +### Content +CardHeader(title: string, description?: string) — Title/description header block for a Card. +TextContent(text: string, size?: "small" | "default" | "large" | "small-heavy" | "large-heavy") — Text block with optional size. size: "small" | "default" | "large" | "small-heavy" | "large-heavy". +Heading(text: string, level?: "h1" | "h2" | "h3" | "h4") — Section heading. level: "h1" | "h2" | "h3" | "h4". +Alert(title: string, description: string, variant?: "default" | "destructive" | "info" | "success" | "warning") — Alert banner with icon, title, and description. variant: "default" | "destructive" | "info" | "success" | "warning". +List(items: ListItem[], dense?: boolean) — Vertical list of items. items: ListItem[]. dense for tighter spacing. +ListItem(primary: string, secondary?: string) — Item in a List. primary text with optional secondary text. +Separator(orientation?: "horizontal" | "vertical") — Horizontal or vertical rule. orientation: "horizontal" | "vertical". +Progress(value: number, label?: string) — Progress bar showing completion percentage (0-100). Optional label. +- Use CardHeader for the main section title. Use Heading for sub-section titles. +- TextContent size: "small" | "default" | "large" | "small-heavy" | "large-heavy". +- List takes ListItem[] — each ListItem has primary text and optional secondary text. + +### Tables +Table(columns: Col[], rows: any[][]) — Data table. columns: Col[] with header/type, rows: 2D array of values. +Col(header: string, type?: "string" | "number" | "boolean") — Column definition for Table — header label and optional type. + +### Charts +BarChart(labels: string[], series: Series[], variant?: "grouped" | "stacked", xLabel?: string, yLabel?: string) — Vertical bar chart. Use for comparing values across categories. +LineChart(labels: string[], series: Series[], xLabel?: string, yLabel?: string) — Line chart for trends over categories. +PieChart(slices: Slice[], donut?: boolean) — Pie or donut chart. slices: Slice[], donut: boolean for a ring chart. +Series(category: string, values: number[]) — One named data series with values matching labels. +Slice(category: string, value: number) — A single slice in a PieChart. +- BarChart/LineChart take labels (string[]) and series (Series[]). Each Series has a category name and a values array aligned to labels. +- PieChart takes slices (Slice[]). Set donut: true for a ring chart. + +### Forms +Form(name: string, buttons: Buttons, fields?: FormControl[]) — Form container with fields and explicit action buttons. fields: FormControl[], buttons: Buttons. +FormControl(label: string, field: any) — Wraps a form field with a label and error display. +Input(name: string, placeholder?: string, type?: "text" | "email" | "password" | "number" | "url", rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}) — Text input field. type: "text" | "email" | "password" | "number" | "url". rules for validation. +Select(name: string, items: SelectItem[], placeholder?: string, rules?: {required?: boolean, email?: boolean, url?: boolean, numeric?: boolean, min?: number, max?: number, minLength?: number, maxLength?: number, pattern?: string}) — Dropdown select. items: SelectItem[], placeholder, rules for validation. +SelectItem(value: string, label: string) — Option for Select dropdown. +SwitchGroup(name: string, items: SwitchItem[]) — Group of toggle switches. items: SwitchItem[]. +SwitchItem(value: string, label: string) — Toggle option in a SwitchGroup. +- Define EACH FormControl as its own reference — do NOT inline all controls in one array. +- NEVER nest Form inside Form. +- Form requires explicit buttons. Always pass a Buttons(...) reference as the second Form argument. +- rules is an optional object: { required: true, email: true, minLength: 8, maxLength: 100 }. +- The renderer shows error messages automatically — do NOT generate error text in the UI. + +### Buttons +Button(label: string, action?: {type: "open_url", url: string} | {type: "continue_conversation", context?: string} | {type: string, params?: Record}, variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link", size?: "default" | "xs" | "sm" | "lg" | "icon") — Clickable button. variant: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link". size: "default" | "xs" | "sm" | "lg" | "icon". action: { type: "continue_conversation" | "open_url", url? }. +Buttons(buttons: Button[], direction?: "row" | "column") — Group of Button components. direction: "row" | "column". + +### Layout +Tabs(items: TabItem[], defaultValue?: string) — Tabbed content. items: TabItem[]. defaultValue: initially active tab. +TabItem(value: string, trigger: string, content: (CardHeader | TextContent | Heading | Alert | List | Separator | Progress | Table | BarChart | LineChart | PieChart | Form | Buttons)[]) — Tab panel. value: unique id, trigger: tab label, content: children. +Accordion(items: AccordionItem[], type?: "single" | "multiple") — Collapsible sections. type: "single" | "multiple". items: AccordionItem[]. +AccordionItem(value: string, trigger: string, content: (CardHeader | TextContent | Heading | Alert | List | Separator | Progress | Table | BarChart | LineChart | PieChart | Form | Buttons)[]) — Collapsible item inside Accordion. value: unique id, trigger: header text. +- Use Tabs to present alternative views — each TabItem has a value id, trigger label, and content array. +- Accordion type: "single" | "multiple". items: AccordionItem[]. + +### Follow-ups +FollowUpBlock(items: FollowUpItem[]) — List of follow-up suggestion chips at the end of a response. +FollowUpItem(text: string) — Clickable follow-up suggestion — sends text as a user message when clicked. +- Use FollowUpBlock with FollowUpItem references at the END of a response to suggest next actions. +- Clicking a FollowUpItem sends its text to the LLM as a user message. + +### Other +Card(children: (CardHeader | TextContent | Heading | Alert | List | Separator | Progress | Table | BarChart | LineChart | PieChart | Form | Buttons | FollowUpBlock | Tabs | Accordion)[]) — Vertical container for all content in a chat response. Children stack top to bottom automatically. + +## Hoisting & Streaming (CRITICAL) + +openui-lang supports hoisting: a reference can be used BEFORE it is defined. The parser resolves all references after the full input is parsed. + +During streaming, the output is re-parsed on every chunk. Undefined references are temporarily unresolved and appear once their definitions stream in. This creates a progressive top-down reveal — structure first, then data fills in. + +**Recommended statement order for optimal streaming:** +1. `root = Card(...)` — UI shell appears immediately +2. Component definitions — fill in as they stream +3. Data values — leaf content last + +Always write the root = Card(...) statement first so the UI shell appears immediately, even before child data has streamed in. + +## Examples + +Example 1 — Table with follow-ups: +root = Card([title, tbl, followUps]) +title = CardHeader("Top Languages", "By number of active developers") +tbl = Table(cols, rows) +cols = [Col("Language", "string"), Col("Users (M)", "number"), Col("Year", "number")] +rows = [["Python", 15.7, 1991], ["JavaScript", 14.2, 1995], ["Java", 12.1, 1995]] +followUps = FollowUpBlock([fu1, fu2]) +fu1 = FollowUpItem("Tell me more about Python") +fu2 = FollowUpItem("Show me a JavaScript comparison") + +Example 2 — Form with validation: +root = Card([title, form]) +title = CardHeader("Contact Us") +form = Form("contact", btns, [nameField, emailField, planField]) +nameField = FormControl("Name", Input("name", "Your name", "text", { required: true, minLength: 2 })) +emailField = FormControl("Email", Input("email", "you@example.com", "email", { required: true, email: true })) +planField = FormControl("Plan", Select("plan", [opt1, opt2], "Choose a plan", { required: true })) +opt1 = SelectItem("free", "Free") +opt2 = SelectItem("pro", "Pro") +btns = Buttons([Button("Submit", { type: "continue_conversation" }, "default")]) + +Example 3 — Alert variants and a list: +root = Card([info, warning, items]) +info = Alert("Update available", "A new version is ready to install.", "info") +warning = Alert("Disk almost full", "Less than 10% storage remaining.", "warning") +items = List([i1, i2, i3]) +i1 = ListItem("Back up your data", "Recommended before updating") +i2 = ListItem("Free up space") +i3 = ListItem("Install the update") + +Example 4 — Charts inside Tabs: +root = Card([header, tabs, followUps]) +header = CardHeader("Sales Dashboard", "Compare metrics across periods") +tabs = Tabs([tab1, tab2, tab3]) +tab1 = TabItem("revenue", "Revenue", [revChart]) +tab2 = TabItem("users", "Users", [usersChart]) +tab3 = TabItem("breakdown", "Breakdown", [pieChart]) +revChart = BarChart(["Jan", "Feb", "Mar", "Apr"], [Series("Revenue", [45, 52, 61, 58])], "grouped", "Month", "USD (K)") +usersChart = LineChart(["Jan", "Feb", "Mar", "Apr"], [Series("Active", [1200, 1350, 1500, 1420]), Series("New", [300, 420, 380, 450])], "Month", "Users") +pieChart = PieChart([Slice("Desktop", 62), Slice("Mobile", 31), Slice("Tablet", 7)], true) +followUps = FollowUpBlock([FollowUpItem("Add Q2 data"), FollowUpItem("Export as table")]) + +Example 5 — Accordion with progress: +root = Card([header, acc]) +header = CardHeader("Project Status", "Sprint 14") +acc = Accordion([a1, a2, a3], "single") +a1 = AccordionItem("done", "Done", [doneText, doneProgress]) +doneText = TextContent("All planned API work is complete.") +doneProgress = Progress(100, "API") +a2 = AccordionItem("progress", "In progress", [Progress(60, "Dashboard UI")]) +a3 = AccordionItem("blocked", "Blocked", [Alert("Waiting on design", "Final mockups pending review.", "warning")]) + +Example 6 — Buttons with variants: +root = Card([title, btns]) +title = CardHeader("Choose an action") +btns = Buttons([b1, b2, b3]) +b1 = Button("Confirm", { type: "continue_conversation" }, "default") +b2 = Button("Learn more", { type: "open_url", url: "https://mui.com" }, "outline") +b3 = Button("Delete", { type: "continue_conversation" }, "destructive") + +## Important Rules +- When asked about data, generate realistic/plausible data +- Choose components that best represent the content (tables for comparisons, charts for trends, forms for input, etc.) + +## Final Verification +Before finishing, walk your output and verify: +1. root = Card(...) is the FIRST line (for optimal streaming). +2. Every referenced name is defined. Every defined name (other than root) is reachable from root. + +- Every response is a single Card(children) — children stack vertically automatically. +- Card is the only top-level layout container. Use Tabs to switch between sections, Accordion for collapsible sections. +- Use FollowUpBlock at the END of a Card to suggest what the user can do or ask next. +- For forms, define one FormControl reference per field so controls can stream progressively. +- For forms, always provide the second Form argument with Buttons(...) actions. +- Never nest Form inside Form. +- Button variant mapping — "default" (filled primary), "secondary" (filled secondary), "outline" (bordered), "ghost" (transparent text), "link" (underlined text), "destructive" (red). Pick the variant that fits the action. +- Button size mapping — "default" (standard), "xs"/"sm" (small), "lg" (large), "icon" (square). +- Alert variants — "default"/"info" (blue), "success" (green), "warning" (amber), "destructive" (red error). Always pick the variant that matches the message tone. +- Use CardHeader for the main section title and Heading for sub-section titles. +- Use Table for tabular data and List for simple itemized content. +- Use Progress for completion indicators (value is a 0-100 percentage). +- Use BarChart/LineChart for trends and comparisons, PieChart (donut: true for a ring) for proportions. Series values must align with the labels array. +- When the user asks for a specific component, generate a realistic, fully-populated example of it with sample data. diff --git a/examples/material-ui-chat/src/hooks/use-system-theme.tsx b/examples/material-ui-chat/src/hooks/use-system-theme.tsx new file mode 100644 index 000000000..8a9ea58f9 --- /dev/null +++ b/examples/material-ui-chat/src/hooks/use-system-theme.tsx @@ -0,0 +1,79 @@ +"use client"; + +import CssBaseline from "@mui/material/CssBaseline"; +import { ThemeProvider } from "@mui/material/styles"; +import { createContext, useContext, useEffect, useMemo, useState } from "react"; + +import { createAppTheme } from "@/lib/mui-genui/theme"; + +type ThemeMode = "light" | "dark"; + +interface ColorModeContextType { + mode: ThemeMode; + toggle: () => void; +} + +const ColorModeContext = createContext(undefined); + +function getSystemMode(): ThemeMode { + if (typeof window === "undefined") return "light"; + return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; +} + +/** + * Provides the Material UI theme + a light/dark color mode. The mode follows the + * OS color scheme by default, and stays in sync with it until the user picks a + * mode manually via `toggle()`. + */ +export function ColorModeProvider({ children }: { children: React.ReactNode }) { + const [mode, setMode] = useState("light"); + const [userOverride, setUserOverride] = useState(false); + + // Pick up the system preference once mounted (avoids SSR hydration mismatch). + useEffect(() => { + if (!userOverride) setMode(getSystemMode()); + }, [userOverride]); + + // Keep following the system preference until the user overrides it. + useEffect(() => { + if (userOverride) return undefined; + const mq = window.matchMedia("(prefers-color-scheme: dark)"); + const handler = (e: MediaQueryListEvent) => setMode(e.matches ? "dark" : "light"); + mq.addEventListener("change", handler); + return () => mq.removeEventListener("change", handler); + }, [userOverride]); + + useEffect(() => { + document.body.setAttribute("data-theme", mode); + }, [mode]); + + const theme = useMemo(() => createAppTheme(mode), [mode]); + + const value = useMemo( + () => ({ + mode, + toggle: () => { + setUserOverride(true); + setMode((m) => (m === "light" ? "dark" : "light")); + }, + }), + [mode], + ); + + return ( + + + + {children} + + + ); +} + +export function useColorMode(): ColorModeContextType { + const ctx = useContext(ColorModeContext); + if (!ctx) { + throw new Error("useColorMode must be used within a ColorModeProvider"); + } + return ctx; +} diff --git a/examples/material-ui-chat/src/lib/mui-genui/action.ts b/examples/material-ui-chat/src/lib/mui-genui/action.ts new file mode 100644 index 000000000..edd4b54b5 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/action.ts @@ -0,0 +1,23 @@ +import { BuiltinActionType } from "@openuidev/react-lang"; +import { z } from "zod"; + +const continueConversationAction = z.object({ + type: z.literal(BuiltinActionType.ContinueConversation), + context: z.string().optional(), +}); + +const openUrlAction = z.object({ + type: z.literal(BuiltinActionType.OpenUrl), + url: z.string(), +}); + +const customAction = z.object({ + type: z.string(), + params: z.record(z.string(), z.any()).optional(), +}); + +export const actionSchema = z + .union([openUrlAction, continueConversationAction, customAction]) + .optional(); + +export type ActionSchema = z.infer; diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/accordion.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/accordion.tsx new file mode 100644 index 000000000..37f7b5949 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/accordion.tsx @@ -0,0 +1,72 @@ +"use client"; + +import MuiAccordion, { type AccordionProps } from "@mui/material/Accordion"; +import AccordionDetails from "@mui/material/AccordionDetails"; +import AccordionSummary from "@mui/material/AccordionSummary"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; +import { defineComponent } from "@openuidev/react-lang"; +import * as React from "react"; +import { z } from "zod"; +import { ContentChildUnion } from "../unions"; + +const AccordionItemSchema = z.object({ + value: z.string(), + trigger: z.string(), + content: z.array(ContentChildUnion), +}); + +export const AccordionItemDef = defineComponent({ + name: "AccordionItem", + props: AccordionItemSchema, + description: "Collapsible item inside Accordion. value: unique id, trigger: header text.", + component: () => null, +}); + +const AccordionSchema = z.object({ + items: z.array(AccordionItemDef.ref), + type: z.enum(["single", "multiple"]).optional(), +}); + +export const Accordion = defineComponent({ + name: "Accordion", + props: AccordionSchema, + description: 'Collapsible sections. type: "single" | "multiple". items: AccordionItem[].', + component: ({ props, renderNode }) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const items = (props.items ?? []) as any[]; + const single = (props.type ?? "multiple") === "single"; + const [expanded, setExpanded] = React.useState(false); + + if (!items.length) return null; + + return ( + + {items.map((item, i) => { + const val = String(item?.props?.value ?? i); + const ctrl: Partial = single + ? { + expanded: expanded === val, + onChange: (_, isExpanded) => setExpanded(isExpanded ? val : false), + } + : { defaultExpanded: i === 0 }; + return ( + + }> + + {String(item?.props?.trigger ?? "")} + + + + + {renderNode(item?.props?.content)} + + + + ); + })} + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/alert.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/alert.tsx new file mode 100644 index 000000000..91214f417 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/alert.tsx @@ -0,0 +1,36 @@ +"use client"; + +import MuiAlert, { type AlertColor } from "@mui/material/Alert"; +import AlertTitle from "@mui/material/AlertTitle"; +import { defineComponent } from "@openuidev/react-lang"; +import { z } from "zod"; + +const AlertSchema = z.object({ + title: z.string(), + description: z.string(), + variant: z.enum(["default", "destructive", "info", "success", "warning"]).optional(), +}); + +const severityMap: Record = { + default: "info", + destructive: "error", + info: "info", + success: "success", + warning: "warning", +}; + +export const Alert = defineComponent({ + name: "Alert", + props: AlertSchema, + description: + 'Alert banner with icon, title, and description. variant: "default" | "destructive" | "info" | "success" | "warning".', + component: ({ props }) => { + const severity = severityMap[props.variant ?? "default"] ?? "info"; + return ( + + {props.title} + {props.description} + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/button.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/button.tsx new file mode 100644 index 000000000..bedef50a7 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/button.tsx @@ -0,0 +1,77 @@ +"use client"; + +import MuiButton from "@mui/material/Button"; +import { + BuiltinActionType, + defineComponent, + useFormName, + useFormValidation, + useIsStreaming, + useTriggerAction, +} from "@openuidev/react-lang"; +import { z } from "zod"; +import { actionSchema, type ActionSchema } from "../action"; + +const ButtonSchema = z.object({ + label: z.string(), + action: actionSchema, + variant: z.enum(["default", "destructive", "outline", "secondary", "ghost", "link"]).optional(), + size: z.enum(["default", "xs", "sm", "lg", "icon"]).optional(), +}); + +const sizeMap: Record = { + default: "medium", + xs: "small", + sm: "small", + lg: "large", + icon: "small", +}; + +export const Button = defineComponent({ + name: "Button", + props: ButtonSchema, + description: + 'Clickable button. variant: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link". size: "default" | "xs" | "sm" | "lg" | "icon". action: { type: "continue_conversation" | "open_url", url? }.', + component: ({ props }) => { + const triggerAction = useTriggerAction(); + const formName = useFormName(); + const formValidation = useFormValidation(); + const isStreaming = useIsStreaming(); + + const label = String(props.label ?? ""); + const action = props.action as ActionSchema; + const v = props.variant ?? "default"; + + const muiVariant = v === "outline" ? "outlined" : v === "ghost" || v === "link" ? "text" : "contained"; + const color = v === "destructive" ? "error" : v === "secondary" ? "secondary" : "primary"; + const muiSize = sizeMap[props.size ?? "default"] ?? "medium"; + + return ( + { + const actionType = action?.type ?? BuiltinActionType.ContinueConversation; + if ( + formValidation && + v === "default" && + actionType === BuiltinActionType.ContinueConversation + ) { + const valid = formValidation.validateForm(); + if (!valid) return; + } + const actionParams = + action?.type === BuiltinActionType.OpenUrl + ? { url: (action as { url: string }).url } + : (action as { params?: Record })?.params; + triggerAction(label, formName, { type: actionType, params: actionParams }); + }} + > + {label} + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/buttons.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/buttons.tsx new file mode 100644 index 000000000..c906b7c74 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/buttons.tsx @@ -0,0 +1,22 @@ +"use client"; + +import Stack from "@mui/material/Stack"; +import { defineComponent } from "@openuidev/react-lang"; +import { z } from "zod"; +import { Button } from "./button"; + +const ButtonsSchema = z.object({ + buttons: z.array(Button.ref), + direction: z.enum(["row", "column"]).optional(), +}); + +export const Buttons = defineComponent({ + name: "Buttons", + props: ButtonsSchema, + description: 'Group of Button components. direction: "row" | "column".', + component: ({ props, renderNode }) => ( + + {renderNode(props.buttons)} + + ), +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/card-header.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/card-header.tsx new file mode 100644 index 000000000..5d5594c0d --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/card-header.tsx @@ -0,0 +1,29 @@ +"use client"; + +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { defineComponent } from "@openuidev/react-lang"; +import { z } from "zod"; + +const CardHeaderSchema = z.object({ + title: z.string(), + description: z.string().optional(), +}); + +export const CardHeader = defineComponent({ + name: "CardHeader", + props: CardHeaderSchema, + description: "Title/description header block for a Card.", + component: ({ props }) => ( + + + {props.title} + + {props.description && ( + + {props.description} + + )} + + ), +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/charts.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/charts.tsx new file mode 100644 index 000000000..145596d36 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/charts.tsx @@ -0,0 +1,147 @@ +"use client"; + +import Box from "@mui/material/Box"; +import { BarChart } from "@mui/x-charts/BarChart"; +import { LineChart } from "@mui/x-charts/LineChart"; +import { PieChart } from "@mui/x-charts/PieChart"; +import { defineComponent } from "@openuidev/react-lang"; +import { z } from "zod"; + +import { asArray, buildPie, buildSeries, hasAllProps } from "../helpers"; +import { CHART_PALETTE } from "../theme"; + +const CHART_HEIGHT = 260; +const CHART_MARGIN = { top: 16, right: 16, bottom: 30, left: 44 }; + +// ── Virtual sub-components (data-only) ── + +const SeriesSchema = z.object({ + category: z.string(), + values: z.array(z.number()), +}); + +export const Series = defineComponent({ + name: "Series", + props: SeriesSchema, + description: "One named data series with values matching labels.", + component: () => null, +}); + +const SliceSchema = z.object({ + category: z.string(), + value: z.number(), +}); + +export const Slice = defineComponent({ + name: "Slice", + props: SliceSchema, + description: "A single slice in a PieChart.", + component: () => null, +}); + +// ── BarChart ── + +export const BarChartComponent = defineComponent({ + name: "BarChart", + props: z.object({ + labels: z.array(z.string()), + series: z.array(SeriesSchema), + variant: z.enum(["grouped", "stacked"]).optional(), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + }), + description: "Vertical bar chart. Use for comparing values across categories.", + component: ({ props }) => { + if (!hasAllProps(props as Record, "labels", "series")) return null; + const labels = asArray(props.labels).map(String); + const series = buildSeries(props.series); + if (!series.length) return null; + const stacked = props.variant === "stacked"; + + return ( + + ({ + data: s.data, + label: s.label, + color: CHART_PALETTE[i % CHART_PALETTE.length], + ...(stacked ? { stack: "total" } : {}), + }))} + /> + + ); + }, +}); + +// ── LineChart ── + +export const LineChartComponent = defineComponent({ + name: "LineChart", + props: z.object({ + labels: z.array(z.string()), + series: z.array(SeriesSchema), + xLabel: z.string().optional(), + yLabel: z.string().optional(), + }), + description: "Line chart for trends over categories.", + component: ({ props }) => { + if (!hasAllProps(props as Record, "labels", "series")) return null; + const labels = asArray(props.labels).map(String); + const series = buildSeries(props.series); + if (!series.length) return null; + + return ( + + ({ + data: s.data, + label: s.label, + color: CHART_PALETTE[i % CHART_PALETTE.length], + curve: "monotoneX", + showMark: false, + }))} + /> + + ); + }, +}); + +// ── PieChart ── + +export const PieChartComponent = defineComponent({ + name: "PieChart", + props: z.object({ + slices: z.array(SliceSchema), + donut: z.boolean().optional(), + }), + description: "Pie or donut chart. slices: Slice[], donut: boolean for a ring chart.", + component: ({ props }) => { + const data = buildPie(props.slices); + if (!data.length) return null; + const colored = data.map((d, i) => ({ ...d, color: CHART_PALETTE[i % CHART_PALETTE.length] })); + + return ( + + + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/divider.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/divider.tsx new file mode 100644 index 000000000..c346978e8 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/divider.tsx @@ -0,0 +1,19 @@ +"use client"; + +import Divider from "@mui/material/Divider"; +import { defineComponent } from "@openuidev/react-lang"; +import { z } from "zod"; + +const SeparatorSchema = z.object({ + orientation: z.enum(["horizontal", "vertical"]).optional(), +}); + +export const Separator = defineComponent({ + name: "Separator", + props: SeparatorSchema, + description: 'Horizontal or vertical rule. orientation: "horizontal" | "vertical".', + component: ({ props }) => { + const orientation = props.orientation ?? "horizontal"; + return ; + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/follow-up-block.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/follow-up-block.tsx new file mode 100644 index 000000000..cbd0ff330 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/follow-up-block.tsx @@ -0,0 +1,50 @@ +"use client"; + +import Chip from "@mui/material/Chip"; +import Stack from "@mui/material/Stack"; +import { defineComponent, useTriggerAction } from "@openuidev/react-lang"; +import { z } from "zod"; + +const FollowUpItemSchema = z.object({ + text: z.string(), +}); + +export const FollowUpItem = defineComponent({ + name: "FollowUpItem", + props: FollowUpItemSchema, + description: "Clickable follow-up suggestion — sends text as a user message when clicked.", + component: () => null, +}); + +const FollowUpBlockSchema = z.object({ + items: z.array(FollowUpItem.ref), +}); + +export const FollowUpBlock = defineComponent({ + name: "FollowUpBlock", + props: FollowUpBlockSchema, + description: "List of follow-up suggestion chips at the end of a response.", + component: ({ props }) => { + const triggerAction = useTriggerAction(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const items = (props.items ?? []) as any[]; + + return ( + + {items.map((item, i) => { + const text = String(item?.props?.text ?? ""); + return ( + triggerAction(text)} + /> + ); + })} + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/form-control.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/form-control.tsx new file mode 100644 index 000000000..022a1b273 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/form-control.tsx @@ -0,0 +1,38 @@ +"use client"; + +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import { defineComponent, useFormValidation } from "@openuidev/react-lang"; +import { z } from "zod"; + +const FormControlSchema = z.object({ + label: z.string(), + field: z.any(), +}); + +export const FormControl = defineComponent({ + name: "FormControl", + props: FormControlSchema, + description: "Wraps a form field with a label and error display.", + component: ({ props, renderNode }) => { + const formValidation = useFormValidation(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const fieldName = (props.field as any)?.props?.name as string | undefined; + const errors = formValidation?.errors as Record | undefined; + const error = fieldName ? errors?.[fieldName] : undefined; + + return ( + + + {props.label} + + {renderNode(props.field)} + {error && ( + + {error} + + )} + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/form.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/form.tsx new file mode 100644 index 000000000..0cdfc63f2 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/form.tsx @@ -0,0 +1,40 @@ +"use client"; + +import Box from "@mui/material/Box"; +import { + FormNameContext, + FormValidationContext, + defineComponent, + useCreateFormValidation, +} from "@openuidev/react-lang"; +import { z } from "zod"; +import { Buttons } from "./buttons"; +import { FormControl } from "./form-control"; + +const FormSchema = z.object({ + name: z.string(), + buttons: Buttons.ref, + fields: z.array(FormControl.ref).default([]), +}); + +export const Form = defineComponent({ + name: "Form", + props: FormSchema, + description: + "Form container with fields and explicit action buttons. fields: FormControl[], buttons: Buttons.", + component: ({ props, renderNode }) => { + const formValidation = useCreateFormValidation(); + const formName = props.name as string; + + return ( + + + + {renderNode(props.fields)} + {renderNode(props.buttons)} + + + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/heading.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/heading.tsx new file mode 100644 index 000000000..30a64a165 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/heading.tsx @@ -0,0 +1,24 @@ +"use client"; + +import Typography from "@mui/material/Typography"; +import { defineComponent } from "@openuidev/react-lang"; +import { z } from "zod"; + +const HeadingSchema = z.object({ + text: z.string(), + level: z.enum(["h1", "h2", "h3", "h4"]).optional(), +}); + +export const Heading = defineComponent({ + name: "Heading", + props: HeadingSchema, + description: 'Section heading. level: "h1" | "h2" | "h3" | "h4".', + component: ({ props }) => { + const level = props.level ?? "h2"; + return ( + + {props.text} + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/input.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/input.tsx new file mode 100644 index 000000000..9107101a7 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/input.tsx @@ -0,0 +1,67 @@ +"use client"; + +import TextField from "@mui/material/TextField"; +import { + defineComponent, + parseStructuredRules, + useFormName, + useFormValidation, + useGetFieldValue, + useIsStreaming, + useSetFieldValue, +} from "@openuidev/react-lang"; +import React from "react"; +import { z } from "zod"; +import { rulesSchema } from "../rules"; + +const InputSchema = z.object({ + name: z.string(), + placeholder: z.string().optional(), + type: z.enum(["text", "email", "password", "number", "url"]).optional(), + rules: rulesSchema, +}); + +export const Input = defineComponent({ + name: "Input", + props: InputSchema, + description: + 'Text input field. type: "text" | "email" | "password" | "number" | "url". rules for validation.', + component: ({ props }) => { + const formName = useFormName(); + const getFieldValue = useGetFieldValue(); + const setFieldValue = useSetFieldValue(); + const isStreaming = useIsStreaming(); + const formValidation = useFormValidation(); + + const fieldName = props.name as string; + const rules = React.useMemo(() => parseStructuredRules(props.rules), [props.rules]); + const savedValue = getFieldValue(formName, fieldName) ?? ""; + + React.useEffect(() => { + if (!isStreaming && rules.length > 0 && formValidation) { + formValidation.registerField(fieldName, rules, () => getFieldValue(formName, fieldName)); + return () => formValidation.unregisterField(fieldName); + } + return undefined; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isStreaming, rules.length > 0]); + + return ( + { + const val = e.target.value; + if (val !== savedValue) setFieldValue(formName, "Input", fieldName, val, true); + if (rules.length > 0 && formValidation) + formValidation.validateField(fieldName, val, rules); + }} + /> + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/list.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/list.tsx new file mode 100644 index 000000000..8342a2a4c --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/list.tsx @@ -0,0 +1,47 @@ +"use client"; + +import MuiList from "@mui/material/List"; +import MuiListItem from "@mui/material/ListItem"; +import ListItemText from "@mui/material/ListItemText"; +import { defineComponent } from "@openuidev/react-lang"; +import { z } from "zod"; + +const ListItemSchema = z.object({ + primary: z.string(), + secondary: z.string().optional(), +}); + +export const ListItemDef = defineComponent({ + name: "ListItem", + props: ListItemSchema, + description: "Item in a List. primary text with optional secondary text.", + component: () => null, +}); + +const ListSchema = z.object({ + items: z.array(ListItemDef.ref), + dense: z.boolean().optional(), +}); + +export const List = defineComponent({ + name: "List", + props: ListSchema, + description: "Vertical list of items. items: ListItem[]. dense for tighter spacing.", + component: ({ props }) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const items = ((props.items ?? []) as any[]).filter((it) => it?.props?.primary != null); + if (!items.length) return null; + return ( + + {items.map((it, i) => ( + + + + ))} + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/progress.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/progress.tsx new file mode 100644 index 000000000..80783df63 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/progress.tsx @@ -0,0 +1,36 @@ +"use client"; + +import Box from "@mui/material/Box"; +import LinearProgress from "@mui/material/LinearProgress"; +import Typography from "@mui/material/Typography"; +import { defineComponent } from "@openuidev/react-lang"; +import { z } from "zod"; + +const ProgressSchema = z.object({ + value: z.number(), + label: z.string().optional(), +}); + +export const Progress = defineComponent({ + name: "Progress", + props: ProgressSchema, + description: "Progress bar showing completion percentage (0-100). Optional label.", + component: ({ props }) => { + const value = Math.max(0, Math.min(100, Number(props.value) || 0)); + return ( + + + {props.label && {props.label}} + + {value}% + + + + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/select.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/select.tsx new file mode 100644 index 000000000..be5a364d2 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/select.tsx @@ -0,0 +1,93 @@ +"use client"; + +import MuiFormControl from "@mui/material/FormControl"; +import MenuItem from "@mui/material/MenuItem"; +import MuiSelect, { type SelectChangeEvent } from "@mui/material/Select"; +import { + defineComponent, + parseStructuredRules, + useFormName, + useFormValidation, + useGetFieldValue, + useIsStreaming, + useSetFieldValue, +} from "@openuidev/react-lang"; +import React from "react"; +import { z } from "zod"; +import { rulesSchema } from "../rules"; + +const SelectItemSchema = z.object({ + value: z.string(), + label: z.string(), +}); + +export const SelectItem = defineComponent({ + name: "SelectItem", + props: SelectItemSchema, + description: "Option for Select dropdown.", + component: () => null, +}); + +const SelectSchema = z.object({ + name: z.string(), + items: z.array(SelectItem.ref), + placeholder: z.string().optional(), + rules: rulesSchema, +}); + +export const Select = defineComponent({ + name: "Select", + props: SelectSchema, + description: "Dropdown select. items: SelectItem[], placeholder, rules for validation.", + component: ({ props }) => { + const formName = useFormName(); + const getFieldValue = useGetFieldValue(); + const setFieldValue = useSetFieldValue(); + const isStreaming = useIsStreaming(); + const formValidation = useFormValidation(); + + const fieldName = props.name as string; + const rules = React.useMemo(() => parseStructuredRules(props.rules), [props.rules]); + const value = (getFieldValue(formName, fieldName) as string | undefined) ?? ""; + + React.useEffect(() => { + if (!isStreaming && rules.length > 0 && formValidation) { + formValidation.registerField(fieldName, rules, () => getFieldValue(formName, fieldName)); + return () => formValidation.unregisterField(fieldName); + } + return undefined; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isStreaming, rules.length > 0]); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const items = ((props.items ?? []) as any[]).filter((item) => item?.props?.value); + + return ( + + + selected ? ( + String(selected) + ) : ( + {props.placeholder ?? "Select..."} + ) + } + onChange={(e: SelectChangeEvent) => { + const val = e.target.value; + setFieldValue(formName, "Select", fieldName, val, true); + if (rules.length > 0 && formValidation) + formValidation.validateField(fieldName, val, rules); + }} + > + {items.map((item, i) => ( + + {item.props.label || item.props.value} + + ))} + + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/switch-group.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/switch-group.tsx new file mode 100644 index 000000000..40e3cb496 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/switch-group.tsx @@ -0,0 +1,76 @@ +"use client"; + +import FormControlLabel from "@mui/material/FormControlLabel"; +import FormGroup from "@mui/material/FormGroup"; +import MuiSwitch from "@mui/material/Switch"; +import { + defineComponent, + useFormName, + useGetFieldValue, + useIsStreaming, + useSetFieldValue, +} from "@openuidev/react-lang"; +import { z } from "zod"; + +const SwitchItemSchema = z.object({ + value: z.string(), + label: z.string(), +}); + +export const SwitchItem = defineComponent({ + name: "SwitchItem", + props: SwitchItemSchema, + description: "Toggle option in a SwitchGroup.", + component: () => null, +}); + +const SwitchGroupSchema = z.object({ + name: z.string(), + items: z.array(SwitchItem.ref), +}); + +export const SwitchGroup = defineComponent({ + name: "SwitchGroup", + props: SwitchGroupSchema, + description: "Group of toggle switches. items: SwitchItem[].", + component: ({ props }) => { + const formName = useFormName(); + const getFieldValue = useGetFieldValue(); + const setFieldValue = useSetFieldValue(); + const isStreaming = useIsStreaming(); + + const fieldName = props.name as string; + const current = (getFieldValue(formName, fieldName) as string[] | undefined) ?? []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const items = ((props.items ?? []) as any[]).filter((item) => item?.props?.value); + + return ( + + {items.map((item, i) => { + const val = item.props.value as string; + const checked = current.includes(val); + return ( + { + const next = e.target.checked + ? [...current, val] + : current.filter((v: string) => v !== val); + setFieldValue(formName, "SwitchGroup", fieldName, next, true); + }} + /> + } + label={item.props.label || val} + /> + ); + })} + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/table.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/table.tsx new file mode 100644 index 000000000..33ad04cda --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/table.tsx @@ -0,0 +1,72 @@ +"use client"; + +import Paper from "@mui/material/Paper"; +import MuiTable from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import { defineComponent } from "@openuidev/react-lang"; +import { z } from "zod"; + +const ColSchema = z.object({ + header: z.string(), + type: z.enum(["string", "number", "boolean"]).optional(), +}); + +export const Col = defineComponent({ + name: "Col", + props: ColSchema, + description: "Column definition for Table — header label and optional type.", + component: () => null, +}); + +const TableSchema = z.object({ + columns: z.array(Col.ref), + rows: z.array(z.array(z.any())), +}); + +export const Table = defineComponent({ + name: "Table", + props: TableSchema, + description: "Data table. columns: Col[] with header/type, rows: 2D array of values.", + component: ({ props }) => { + const columns = ((props.columns ?? []) as unknown[]).map((c) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const col = c as any; + return { + header: String(col?.props?.header ?? ""), + type: (col?.props?.type ?? "string") as string, + }; + }); + const rows = (props.rows ?? []) as unknown[][]; + + return ( + + + + + {columns.map((col, i) => ( + + {col.header} + + ))} + + + + {rows.map((row, ri) => ( + + {columns.map((col, ci) => ( + + {String(row[ci] ?? "")} + + ))} + + ))} + + + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/tabs.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/tabs.tsx new file mode 100644 index 000000000..a78b64a90 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/tabs.tsx @@ -0,0 +1,81 @@ +"use client"; + +import Box from "@mui/material/Box"; +import Tab from "@mui/material/Tab"; +import MuiTabs from "@mui/material/Tabs"; +import { defineComponent } from "@openuidev/react-lang"; +import * as React from "react"; +import { z } from "zod"; +import { ContentChildUnion } from "../unions"; + +const TabItemSchema = z.object({ + value: z.string(), + trigger: z.string(), + content: z.array(ContentChildUnion), +}); + +export const TabItem = defineComponent({ + name: "TabItem", + props: TabItemSchema, + description: "Tab panel. value: unique id, trigger: tab label, content: children.", + component: () => null, +}); + +const TabsSchema = z.object({ + items: z.array(TabItem.ref), + defaultValue: z.string().optional(), +}); + +export const Tabs = defineComponent({ + name: "Tabs", + props: TabsSchema, + description: "Tabbed content. items: TabItem[]. defaultValue: initially active tab.", + component: ({ props, renderNode }) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rawItems = (props.items ?? []) as any[]; + const items = rawItems.filter( + (item) => item?.props?.value != null && item?.props?.trigger != null, + ); + + const [userSelected, setUserSelected] = React.useState(null); + + const firstValue = items[0]?.props?.value as string | undefined; + const preferredDefault = props.defaultValue ?? firstValue; + const userSelectionValid = + userSelected != null && items.some((item) => String(item?.props?.value) === userSelected); + const activeTab = userSelectionValid ? userSelected : (preferredDefault ?? ""); + + if (items.length === 0) return null; + + return ( + + setUserSelected(String(val))} + variant="scrollable" + scrollButtons="auto" + sx={{ borderBottom: 1, borderColor: "divider" }} + > + {items.map((item) => { + const val = String(item.props.value); + return ; + })} + + {items.map((item) => { + const val = String(item.props.value); + const selected = val === activeTab; + return ( + + ); + })} + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/components/text-content.tsx b/examples/material-ui-chat/src/lib/mui-genui/components/text-content.tsx new file mode 100644 index 000000000..45dd8cfc9 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/components/text-content.tsx @@ -0,0 +1,36 @@ +"use client"; + +import Typography from "@mui/material/Typography"; +import { defineComponent } from "@openuidev/react-lang"; +import { z } from "zod"; + +const TextContentSchema = z.object({ + text: z.string(), + size: z.enum(["small", "default", "large", "small-heavy", "large-heavy"]).optional(), +}); + +type Cfg = { variant: "body1" | "body2" | "h6"; fontWeight: number; color?: string }; + +const sizeMap: Record = { + small: { variant: "body2", fontWeight: 400, color: "text.secondary" }, + default: { variant: "body1", fontWeight: 400 }, + large: { variant: "h6", fontWeight: 400 }, + "small-heavy": { variant: "body2", fontWeight: 600 }, + "large-heavy": { variant: "h6", fontWeight: 600 }, +}; + +export const TextContent = defineComponent({ + name: "TextContent", + props: TextContentSchema, + description: + 'Text block with optional size. size: "small" | "default" | "large" | "small-heavy" | "large-heavy".', + component: ({ props }) => { + const text = props.text == null ? "" : String(props.text); + const cfg = sizeMap[props.size ?? "default"] ?? sizeMap.default; + return ( + + {text} + + ); + }, +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/helpers.ts b/examples/material-ui-chat/src/lib/mui-genui/helpers.ts new file mode 100644 index 000000000..5dec6013e --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/helpers.ts @@ -0,0 +1,58 @@ +/** + * Shared helpers for turning OpenUI Lang element nodes (Series, Slice, …) into + * the plain data shapes that `@mui/x-charts` expects. + */ + +type ElementLike = { + type: "element"; + props: Record; +}; + +export function hasAllProps(obj: Record, ...keys: string[]): boolean { + return keys.every((k) => obj[k] != null); +} + +export function asArray(v: unknown): unknown[] { + if (Array.isArray(v)) return v; + if (v == null) return []; + return [v]; +} + +function asElementNodes(v: unknown): ElementLike[] { + return asArray(v).filter( + (x): x is ElementLike => + typeof x === "object" && x !== null && (x as Record)["type"] === "element", + ); +} + +function toNumber(v: unknown): number { + return typeof v === "number" ? v : Number(v) || 0; +} + +export interface CartesianSeries { + label: string; + data: number[]; +} + +/** Convert an array of `Series(category, values)` nodes into x-charts series. */ +export function buildSeries(series: unknown): CartesianSeries[] { + return asElementNodes(series).map((s) => ({ + label: String(s.props["category"] ?? ""), + data: asArray(s.props["values"]).map(toNumber), + })); +} + +export interface PieDatum { + id: number; + value: number; + label: string; +} + +/** Convert an array of `Slice(category, value)` nodes into x-charts pie data. */ +export function buildPie(slices: unknown): PieDatum[] { + return asElementNodes(slices).map((s, i) => ({ + id: i, + value: toNumber(s.props["value"]), + label: String(s.props["category"] ?? ""), + })); +} diff --git a/examples/material-ui-chat/src/lib/mui-genui/index.tsx b/examples/material-ui-chat/src/lib/mui-genui/index.tsx new file mode 100644 index 000000000..4cf1aafb3 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/index.tsx @@ -0,0 +1,257 @@ +"use client"; + +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import type { ComponentGroup, PromptOptions } from "@openuidev/react-lang"; +import { createLibrary, defineComponent } from "@openuidev/react-lang"; +import { z } from "zod"; + +// Content +import { Alert } from "./components/alert"; +import { CardHeader } from "./components/card-header"; +import { Heading } from "./components/heading"; +import { List, ListItemDef } from "./components/list"; +import { Progress } from "./components/progress"; +import { Separator } from "./components/divider"; +import { TextContent } from "./components/text-content"; + +// Tables +import { Col, Table } from "./components/table"; + +// Charts +import { + BarChartComponent, + LineChartComponent, + PieChartComponent, + Series, + Slice, +} from "./components/charts"; + +// Forms +import { Form } from "./components/form"; +import { FormControl } from "./components/form-control"; +import { Input } from "./components/input"; +import { Select, SelectItem } from "./components/select"; +import { SwitchGroup, SwitchItem } from "./components/switch-group"; + +// Buttons +import { Button } from "./components/button"; +import { Buttons } from "./components/buttons"; + +// Layout +import { Accordion, AccordionItemDef } from "./components/accordion"; +import { TabItem, Tabs } from "./components/tabs"; + +// Chat-specific +import { FollowUpBlock, FollowUpItem } from "./components/follow-up-block"; + +import { ChatContentChildUnion } from "./unions"; + +const ChatCardChildUnion = z.union([...ChatContentChildUnion.options, Tabs.ref, Accordion.ref]); + +const ChatCard = defineComponent({ + name: "Card", + props: z.object({ + children: z.array(ChatCardChildUnion), + }), + description: + "Vertical container for all content in a chat response. Children stack top to bottom automatically.", + component: ({ props, renderNode }) => ( + + + {renderNode(props.children)} + + + ), +}); + +// ── Component Groups ── + +export const muiComponentGroups: ComponentGroup[] = [ + { + name: "Content", + components: ["CardHeader", "TextContent", "Heading", "Alert", "List", "ListItem", "Separator", "Progress"], + notes: [ + "- Use CardHeader for the main section title. Use Heading for sub-section titles.", + '- TextContent size: "small" | "default" | "large" | "small-heavy" | "large-heavy".', + "- List takes ListItem[] — each ListItem has primary text and optional secondary text.", + ], + }, + { + name: "Tables", + components: ["Table", "Col"], + }, + { + name: "Charts", + components: ["BarChart", "LineChart", "PieChart", "Series", "Slice"], + notes: [ + "- BarChart/LineChart take labels (string[]) and series (Series[]). Each Series has a category name and a values array aligned to labels.", + "- PieChart takes slices (Slice[]). Set donut: true for a ring chart.", + ], + }, + { + name: "Forms", + components: ["Form", "FormControl", "Input", "Select", "SelectItem", "SwitchGroup", "SwitchItem"], + notes: [ + "- Define EACH FormControl as its own reference — do NOT inline all controls in one array.", + "- NEVER nest Form inside Form.", + "- Form requires explicit buttons. Always pass a Buttons(...) reference as the second Form argument.", + "- rules is an optional object: { required: true, email: true, minLength: 8, maxLength: 100 }.", + "- The renderer shows error messages automatically — do NOT generate error text in the UI.", + ], + }, + { + name: "Buttons", + components: ["Button", "Buttons"], + }, + { + name: "Layout", + components: ["Tabs", "TabItem", "Accordion", "AccordionItem"], + notes: [ + "- Use Tabs to present alternative views — each TabItem has a value id, trigger label, and content array.", + '- Accordion type: "single" | "multiple". items: AccordionItem[].', + ], + }, + { + name: "Follow-ups", + components: ["FollowUpBlock", "FollowUpItem"], + notes: [ + "- Use FollowUpBlock with FollowUpItem references at the END of a response to suggest next actions.", + "- Clicking a FollowUpItem sends its text to the LLM as a user message.", + ], + }, +]; + +// ── Examples ── + +export const muiExamples: string[] = [ + `Example 1 — Table with follow-ups: +root = Card([title, tbl, followUps]) +title = CardHeader("Top Languages", "By number of active developers") +tbl = Table(cols, rows) +cols = [Col("Language", "string"), Col("Users (M)", "number"), Col("Year", "number")] +rows = [["Python", 15.7, 1991], ["JavaScript", 14.2, 1995], ["Java", 12.1, 1995]] +followUps = FollowUpBlock([fu1, fu2]) +fu1 = FollowUpItem("Tell me more about Python") +fu2 = FollowUpItem("Show me a JavaScript comparison")`, + + `Example 2 — Form with validation: +root = Card([title, form]) +title = CardHeader("Contact Us") +form = Form("contact", btns, [nameField, emailField, planField]) +nameField = FormControl("Name", Input("name", "Your name", "text", { required: true, minLength: 2 })) +emailField = FormControl("Email", Input("email", "you@example.com", "email", { required: true, email: true })) +planField = FormControl("Plan", Select("plan", [opt1, opt2], "Choose a plan", { required: true })) +opt1 = SelectItem("free", "Free") +opt2 = SelectItem("pro", "Pro") +btns = Buttons([Button("Submit", { type: "continue_conversation" }, "default")])`, + + `Example 3 — Alert variants and a list: +root = Card([info, warning, items]) +info = Alert("Update available", "A new version is ready to install.", "info") +warning = Alert("Disk almost full", "Less than 10% storage remaining.", "warning") +items = List([i1, i2, i3]) +i1 = ListItem("Back up your data", "Recommended before updating") +i2 = ListItem("Free up space") +i3 = ListItem("Install the update")`, + + `Example 4 — Charts inside Tabs: +root = Card([header, tabs, followUps]) +header = CardHeader("Sales Dashboard", "Compare metrics across periods") +tabs = Tabs([tab1, tab2, tab3]) +tab1 = TabItem("revenue", "Revenue", [revChart]) +tab2 = TabItem("users", "Users", [usersChart]) +tab3 = TabItem("breakdown", "Breakdown", [pieChart]) +revChart = BarChart(["Jan", "Feb", "Mar", "Apr"], [Series("Revenue", [45, 52, 61, 58])], "grouped", "Month", "USD (K)") +usersChart = LineChart(["Jan", "Feb", "Mar", "Apr"], [Series("Active", [1200, 1350, 1500, 1420]), Series("New", [300, 420, 380, 450])], "Month", "Users") +pieChart = PieChart([Slice("Desktop", 62), Slice("Mobile", 31), Slice("Tablet", 7)], true) +followUps = FollowUpBlock([FollowUpItem("Add Q2 data"), FollowUpItem("Export as table")])`, + + `Example 5 — Accordion with progress: +root = Card([header, acc]) +header = CardHeader("Project Status", "Sprint 14") +acc = Accordion([a1, a2, a3], "single") +a1 = AccordionItem("done", "Done", [doneText, doneProgress]) +doneText = TextContent("All planned API work is complete.") +doneProgress = Progress(100, "API") +a2 = AccordionItem("progress", "In progress", [Progress(60, "Dashboard UI")]) +a3 = AccordionItem("blocked", "Blocked", [Alert("Waiting on design", "Final mockups pending review.", "warning")])`, + + `Example 6 — Buttons with variants: +root = Card([title, btns]) +title = CardHeader("Choose an action") +btns = Buttons([b1, b2, b3]) +b1 = Button("Confirm", { type: "continue_conversation" }, "default") +b2 = Button("Learn more", { type: "open_url", url: "https://mui.com" }, "outline") +b3 = Button("Delete", { type: "continue_conversation" }, "destructive")`, +]; + +export const muiAdditionalRules: string[] = [ + "Every response is a single Card(children) — children stack vertically automatically.", + "Card is the only top-level layout container. Use Tabs to switch between sections, Accordion for collapsible sections.", + "Use FollowUpBlock at the END of a Card to suggest what the user can do or ask next.", + "For forms, define one FormControl reference per field so controls can stream progressively.", + "For forms, always provide the second Form argument with Buttons(...) actions.", + "Never nest Form inside Form.", + 'Button variant mapping — "default" (filled primary), "secondary" (filled secondary), "outline" (bordered), "ghost" (transparent text), "link" (underlined text), "destructive" (red). Pick the variant that fits the action.', + 'Button size mapping — "default" (standard), "xs"/"sm" (small), "lg" (large), "icon" (square).', + 'Alert variants — "default"/"info" (blue), "success" (green), "warning" (amber), "destructive" (red error). Always pick the variant that matches the message tone.', + "Use CardHeader for the main section title and Heading for sub-section titles.", + "Use Table for tabular data and List for simple itemized content.", + "Use Progress for completion indicators (value is a 0-100 percentage).", + "Use BarChart/LineChart for trends and comparisons, PieChart (donut: true for a ring) for proportions. Series values must align with the labels array.", + "When the user asks for a specific component, generate a realistic, fully-populated example of it with sample data.", +]; + +export const muiPromptOptions: PromptOptions = { + examples: muiExamples, + additionalRules: muiAdditionalRules, +}; + +// ── Library ── + +export const muiChatLibrary = createLibrary({ + root: "Card", + componentGroups: muiComponentGroups, + components: [ + // Root + ChatCard, + CardHeader, + // Content + TextContent, + Heading, + Alert, + List, + ListItemDef, + Separator, + Progress, + // Tables + Table, + Col, + // Charts + BarChartComponent, + LineChartComponent, + PieChartComponent, + Series, + Slice, + // Forms + Form, + FormControl, + Input, + Select, + SelectItem, + SwitchGroup, + SwitchItem, + // Buttons + Button, + Buttons, + // Layout + Tabs, + TabItem, + Accordion, + AccordionItemDef, + // Follow-ups + FollowUpBlock, + FollowUpItem, + ], +}); diff --git a/examples/material-ui-chat/src/lib/mui-genui/rules.ts b/examples/material-ui-chat/src/lib/mui-genui/rules.ts new file mode 100644 index 000000000..cd77461f9 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/rules.ts @@ -0,0 +1,17 @@ +import { z } from "zod"; + +export const rulesSchema = z + .object({ + required: z.boolean().optional(), + email: z.boolean().optional(), + url: z.boolean().optional(), + numeric: z.boolean().optional(), + min: z.number().optional(), + max: z.number().optional(), + minLength: z.number().optional(), + maxLength: z.number().optional(), + pattern: z.string().optional(), + }) + .optional(); + +export type RulesSchema = z.infer; diff --git a/examples/material-ui-chat/src/lib/mui-genui/theme.ts b/examples/material-ui-chat/src/lib/mui-genui/theme.ts new file mode 100644 index 000000000..eec63503f --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/theme.ts @@ -0,0 +1,42 @@ +import { createTheme, type Theme } from "@mui/material/styles"; + +/** + * Builds the Material UI theme for the chat surface. The same factory is used + * for both light and dark mode — pass the desired palette mode. + */ +export function createAppTheme(mode: "light" | "dark"): Theme { + return createTheme({ + palette: { + mode, + primary: { main: mode === "dark" ? "#90caf9" : "#1976d2" }, + secondary: { main: mode === "dark" ? "#ce93d8" : "#9c27b0" }, + ...(mode === "dark" + ? { background: { default: "#0b0f14", paper: "#121821" } } + : { background: { default: "#f7f8fa", paper: "#ffffff" } }), + }, + shape: { borderRadius: 10 }, + typography: { + fontFamily: + 'system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif', + h1: { fontSize: "1.8rem", fontWeight: 700 }, + h2: { fontSize: "1.5rem", fontWeight: 700 }, + h3: { fontSize: "1.25rem", fontWeight: 600 }, + h4: { fontSize: "1.1rem", fontWeight: 600 }, + }, + components: { + MuiCard: { + defaultProps: { variant: "outlined" }, + }, + }, + }); +} + +/** Categorical palette shared by all charts so series colors stay consistent. */ +export const CHART_PALETTE = [ + "#1976d2", + "#9c27b0", + "#2e7d32", + "#ed6c02", + "#0288d1", + "#d32f2f", +]; diff --git a/examples/material-ui-chat/src/lib/mui-genui/unions.ts b/examples/material-ui-chat/src/lib/mui-genui/unions.ts new file mode 100644 index 000000000..c2d3034c0 --- /dev/null +++ b/examples/material-ui-chat/src/lib/mui-genui/unions.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; + +import { Alert } from "./components/alert"; +import { Buttons } from "./components/buttons"; +import { CardHeader } from "./components/card-header"; +import { + BarChartComponent, + LineChartComponent, + PieChartComponent, +} from "./components/charts"; +import { Separator } from "./components/divider"; +import { FollowUpBlock } from "./components/follow-up-block"; +import { Form } from "./components/form"; +import { Heading } from "./components/heading"; +import { List } from "./components/list"; +import { Progress } from "./components/progress"; +import { Table } from "./components/table"; +import { TextContent } from "./components/text-content"; + +/** + * Components that may appear inside a container's `content` array (Card, Tabs, + * Accordion). Tabs/Accordion themselves are intentionally excluded here to keep + * nesting shallow — they are added to the Card child union in `index.tsx`. + */ +export const ContentChildUnion = z.union([ + CardHeader.ref, + TextContent.ref, + Heading.ref, + Alert.ref, + List.ref, + Separator.ref, + Progress.ref, + Table.ref, + BarChartComponent.ref, + LineChartComponent.ref, + PieChartComponent.ref, + Form.ref, + Buttons.ref, +]); + +export const ChatContentChildUnion = z.union([...ContentChildUnion.options, FollowUpBlock.ref]); diff --git a/examples/material-ui-chat/src/library.ts b/examples/material-ui-chat/src/library.ts new file mode 100644 index 000000000..063bcd642 --- /dev/null +++ b/examples/material-ui-chat/src/library.ts @@ -0,0 +1,8 @@ +/** + * Entry point for the OpenUI CLI prompt generator. + * + * `pnpm generate:prompt` runs `openui generate src/library.ts`, which bundles + * this file (stubbing asset imports) and reads the `library` + `promptOptions` + * exports to serialize the system prompt into `src/generated/system-prompt.txt`. + */ +export { muiChatLibrary as library, muiPromptOptions as promptOptions } from "./lib/mui-genui"; diff --git a/examples/material-ui-chat/tsconfig.json b/examples/material-ui-chat/tsconfig.json new file mode 100644 index 000000000..c43ccccc5 --- /dev/null +++ b/examples/material-ui-chat/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [{ "name": "next" }], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": ["node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 759229d5a..03368aa0a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,7 +85,7 @@ importers: version: 14.2.8(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react@19.2.4)(vite@7.3.1(@types/node@25.3.2)(jiti@2.6.1)(lightningcss@1.32.0)(sass@1.89.2)(terser@5.43.0)(tsx@4.20.3)(yaml@2.8.3)) fumadocs-ui: specifier: 16.6.5 - version: 16.6.5(@takumi-rs/image-response@0.68.17)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1) + version: 16.6.5(@emotion/is-prop-valid@1.4.0)(@takumi-rs/image-response@0.68.17)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1) gpt-tokenizer: specifier: ^3.4.0 version: 3.4.0 @@ -94,7 +94,7 @@ importers: version: 0.570.0(react@19.2.4) motion: specifier: ^12.34.3 - version: 12.34.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 12.34.3(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next: specifier: 16.1.6 version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2) @@ -377,6 +377,70 @@ importers: specifier: ^5 version: 5.9.3 + examples/material-ui-chat: + dependencies: + '@emotion/react': + specifier: ^11.13.5 + version: 11.14.0(@types/react@19.2.14)(react@19.2.3) + '@emotion/styled': + specifier: ^11.13.5 + version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3) + '@mui/icons-material': + specifier: ^6.4.12 + version: 6.5.0(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react@19.2.3) + '@mui/material': + specifier: ^6.4.12 + version: 6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mui/x-charts': + specifier: ^7.29.1 + version: 7.29.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mui/system@6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@openuidev/react-headless': + specifier: workspace:* + version: link:../../packages/react-headless + '@openuidev/react-lang': + specifier: workspace:* + version: link:../../packages/react-lang + '@openuidev/react-ui': + specifier: workspace:* + version: link:../../packages/react-ui + next: + specifier: 16.1.6 + version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.89.2) + openai: + specifier: ^6.22.0 + version: 6.34.0(ws@8.20.0)(zod@4.3.6) + react: + specifier: 19.2.3 + version: 19.2.3 + react-dom: + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) + zod: + specifier: ^4.0.0 + version: 4.3.6 + devDependencies: + '@openuidev/cli': + specifier: workspace:* + version: link:../../packages/openui-cli + '@types/node': + specifier: ^20 + version: 20.19.35 + '@types/react': + specifier: ^19 + version: 19.2.14 + '@types/react-dom': + specifier: ^19 + version: 19.2.3(@types/react@19.2.14) + eslint: + specifier: ^9 + version: 9.29.0(jiti@2.6.1) + eslint-config-next: + specifier: 16.1.6 + version: 16.1.6(@typescript-eslint/parser@8.56.1(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3) + typescript: + specifier: ^5 + version: 5.9.3 + examples/multi-agent-chat: dependencies: '@ai-sdk/openai': @@ -2293,6 +2357,10 @@ packages: resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -2485,6 +2553,60 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emotion/babel-plugin@11.13.5': + resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} + + '@emotion/cache@11.14.0': + resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/is-prop-valid@1.4.0': + resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/react@11.14.0': + resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.3.3': + resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} + + '@emotion/sheet@1.4.0': + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + + '@emotion/styled@11.14.1': + resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.10.0': + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': + resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.2': + resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} + + '@emotion/weak-memoize@0.4.0': + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + '@envelop/core@5.5.1': resolution: {integrity: sha512-3DQg8sFskDo386TkL5j12jyRAdip/8yzK3x7YGbZBgobZ4aKXrvDU0GppU0SnmrpQnNaiTUsxBs9LKkwQ/eyvw==} engines: {node: '>=18.0.0'} @@ -4013,6 +4135,140 @@ packages: '@cfworker/json-schema': optional: true + '@mui/core-downloads-tracker@6.5.0': + resolution: {integrity: sha512-LGb8t8i6M2ZtS3Drn3GbTI1DVhDY6FJ9crEey2lZ0aN2EMZo8IZBZj9wRf4vqbZHaWjsYgtbOnJw5V8UWbmK2Q==} + + '@mui/icons-material@6.5.0': + resolution: {integrity: sha512-VPuPqXqbBPlcVSA0BmnoE4knW4/xG6Thazo8vCLWkOKusko6DtwFV6B665MMWJ9j0KFohTIf3yx2zYtYacvG1g==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/material': ^6.5.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/material@6.5.0': + resolution: {integrity: sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material-pigment-css': ^6.5.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/material-pigment-css': + optional: true + '@types/react': + optional: true + + '@mui/private-theming@6.4.9': + resolution: {integrity: sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/styled-engine@6.5.0': + resolution: {integrity: sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/system@6.5.0': + resolution: {integrity: sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/types@7.2.24': + resolution: {integrity: sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/types@7.4.12': + resolution: {integrity: sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@6.4.9': + resolution: {integrity: sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@7.3.11': + resolution: {integrity: sha512-XTjGnifwteg71/ij+0e7Y7d+hwyntMYP5wPoA/g2drdGH+Flkvjwy0OfrVpKBbaOvofq4zU/LIyUZyKgmWu18g==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/x-charts-vendor@7.20.0': + resolution: {integrity: sha512-pzlh7z/7KKs5o0Kk0oPcB+sY0+Dg7Q7RzqQowDQjpy5Slz6qqGsgOB5YUzn0L+2yRmvASc4Pe0914Ao3tMBogg==} + + '@mui/x-charts@7.29.1': + resolution: {integrity: sha512-5s9PX51HWhpMa+DCDa4RgjtODSaMe+PlTZUqoGIil2vaW/+4ouDLREXvyuVvIF93KfZwrPKAL2SJKSQS4YYB2w==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.9.0 + '@emotion/styled': ^11.8.1 + '@mui/material': ^5.15.14 || ^6.0.0 || ^7.0.0 + '@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/x-internals@7.29.0': + resolution: {integrity: sha512-+Gk6VTZIFD70XreWvdXBwKd8GZ2FlSCuecQFzm6znwqXg1ZsndavrhG9tkxpxo2fM1Zf7Tk8+HcOO0hCbhTQFA==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -5005,6 +5261,9 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@poppinss/colors@4.1.6': resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} @@ -6570,6 +6829,33 @@ packages: '@types/react': optional: true + '@react-spring/animated@9.7.5': + resolution: {integrity: sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@react-spring/core@9.7.5': + resolution: {integrity: sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@react-spring/rafz@9.7.5': + resolution: {integrity: sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==} + + '@react-spring/shared@9.7.5': + resolution: {integrity: sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@react-spring/types@9.7.5': + resolution: {integrity: sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==} + + '@react-spring/web@9.7.5': + resolution: {integrity: sha512-lmvqGwpe+CSttsWNZVr+Dg62adtKhauGwLyGE/RRyZ8AAMLgb9x3NDMA5RMElXo+IMyTkPp7nxTB8ZQlmhb6JQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@react-stately/autocomplete@3.0.0-beta.4': resolution: {integrity: sha512-K2Uy7XEdseFvgwRQ8CyrYEHMupjVKEszddOapP8deNz4hntYvT1aRm0m+sKa5Kl/4kvg9c/3NZpQcrky/vRZIg==} peerDependencies: @@ -8192,9 +8478,15 @@ packages: '@types/node@25.3.2': resolution: {integrity: sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==} + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/prismjs@1.26.6': resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + '@types/qs@6.15.0': resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} @@ -8209,6 +8501,11 @@ packages: '@types/react-syntax-highlighter@15.5.13': resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} + '@types/react-transition-group@4.4.12': + resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} + peerDependencies: + '@types/react': '*' + '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} @@ -9030,6 +9327,10 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + babel-plugin-polyfill-corejs2@0.4.16: resolution: {integrity: sha512-xaVwwSfebXf0ooE11BJovZYKhFjIvQo7TsyVpETuIeH2JHv0k/T6Y5j22pPTvqYqmpkxdlPAJlyJ0tfOJAoMxw==} peerDependencies: @@ -9615,6 +9916,9 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -9661,6 +9965,10 @@ packages: cose-base@2.2.0: resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + crc-32@1.2.2: resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} engines: {node: '>=0.8'} @@ -10259,6 +10567,9 @@ packages: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + error-stack-parser-es@1.0.5: resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} @@ -10841,6 +11152,9 @@ packages: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -11332,6 +11646,9 @@ packages: highlightjs-vue@1.0.0: resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hono-openapi@1.3.0: resolution: {integrity: sha512-xDvCWpWEIv0weEmnl3EjRQzqbHIO8LnfzMuYOCmbuyE5aes6aXxLg4vM3ybnoZD5TiTUkA6PuRQPJs3R7WRBig==} peerDependencies: @@ -11557,6 +11874,9 @@ packages: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -13286,6 +13606,10 @@ packages: parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + parse-ms@4.0.0: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} @@ -13355,6 +13679,10 @@ packages: path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -13933,6 +14261,9 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-is@19.2.6: + resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} + react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: @@ -14816,6 +15147,9 @@ packages: styleq@0.1.3: resolution: {integrity: sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==} + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + stylis@4.3.6: resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} @@ -16113,6 +16447,10 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml@1.10.3: + resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} + engines: {node: '>= 6'} + yaml@2.8.0: resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} engines: {node: '>= 14.6'} @@ -17148,6 +17486,8 @@ snapshots: '@babel/runtime@7.27.6': {} + '@babel/runtime@7.29.7': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -17392,6 +17732,89 @@ snapshots: tslib: 2.8.1 optional: true + '@emotion/babel-plugin@11.13.5': + dependencies: + '@babel/helper-module-imports': 7.28.6 + '@babel/runtime': 7.27.6 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.3 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.14.0': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/hash@0.9.2': {} + + '@emotion/is-prop-valid@1.4.0': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.27.6 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.3) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.14 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.3': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.2.3 + + '@emotion/sheet@1.4.0': {} + + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.27.6 + '@emotion/babel-plugin': 11.13.5 + '@emotion/is-prop-valid': 1.4.0 + '@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.3) + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.3) + '@emotion/utils': 1.4.2 + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.14 + transitivePeerDependencies: + - supports-color + + '@emotion/unitless@0.10.0': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.3)': + dependencies: + react: 19.2.3 + + '@emotion/utils@1.4.2': {} + + '@emotion/weak-memoize@0.4.0': {} + '@envelop/core@5.5.1': dependencies: '@envelop/instrumentation': 1.0.0 @@ -19045,6 +19468,155 @@ snapshots: transitivePeerDependencies: - supports-color + '@mui/core-downloads-tracker@6.5.0': {} + + '@mui/icons-material@6.5.0(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.14)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/material': 6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.14 + + '@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/core-downloads-tracker': 6.5.0 + '@mui/system': 6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3) + '@mui/types': 7.2.24(@types/react@19.2.14) + '@mui/utils': 6.4.9(@types/react@19.2.14)(react@19.2.3) + '@popperjs/core': 2.11.8 + '@types/react-transition-group': 4.4.12(@types/react@19.2.14) + clsx: 2.1.1 + csstype: 3.2.3 + prop-types: 15.8.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-is: 19.2.6 + react-transition-group: 4.4.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.3) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3) + '@types/react': 19.2.14 + + '@mui/private-theming@6.4.9(@types/react@19.2.14)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/utils': 6.4.9(@types/react@19.2.14)(react@19.2.3) + prop-types: 15.8.1 + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.14 + + '@mui/styled-engine@6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.27.6 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/sheet': 1.4.0 + csstype: 3.2.3 + prop-types: 15.8.1 + react: 19.2.3 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.3) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3) + + '@mui/system@6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/private-theming': 6.4.9(@types/react@19.2.14)(react@19.2.3) + '@mui/styled-engine': 6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3) + '@mui/types': 7.2.24(@types/react@19.2.14) + '@mui/utils': 6.4.9(@types/react@19.2.14)(react@19.2.3) + clsx: 2.1.1 + csstype: 3.2.3 + prop-types: 15.8.1 + react: 19.2.3 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.3) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3) + '@types/react': 19.2.14 + + '@mui/types@7.2.24(@types/react@19.2.14)': + optionalDependencies: + '@types/react': 19.2.14 + + '@mui/types@7.4.12(@types/react@19.2.14)': + dependencies: + '@babel/runtime': 7.29.7 + optionalDependencies: + '@types/react': 19.2.14 + + '@mui/utils@6.4.9(@types/react@19.2.14)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/types': 7.2.24(@types/react@19.2.14) + '@types/prop-types': 15.7.15 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.2.3 + react-is: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@mui/utils@7.3.11(@types/react@19.2.14)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.29.7 + '@mui/types': 7.4.12(@types/react@19.2.14) + '@types/prop-types': 15.7.15 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.2.3 + react-is: 19.2.6 + optionalDependencies: + '@types/react': 19.2.14 + + '@mui/x-charts-vendor@7.20.0': + dependencies: + '@babel/runtime': 7.27.6 + '@types/d3-color': 3.1.3 + '@types/d3-delaunay': 6.0.4 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + d3-color: 3.1.0 + d3-delaunay: 6.0.4 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + delaunator: 5.1.0 + robust-predicates: 3.0.3 + + '@mui/x-charts@7.29.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mui/system@6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/material': 6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mui/system': 6.5.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3) + '@mui/utils': 7.3.11(@types/react@19.2.14)(react@19.2.3) + '@mui/x-charts-vendor': 7.20.0 + '@mui/x-internals': 7.29.0(@types/react@19.2.14)(react@19.2.3) + '@react-spring/rafz': 9.7.5 + '@react-spring/web': 9.7.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.2.14)(react@19.2.3) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@19.2.3))(@types/react@19.2.14)(react@19.2.3) + transitivePeerDependencies: + - '@types/react' + + '@mui/x-internals@7.29.0(@types/react@19.2.14)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/utils': 7.3.11(@types/react@19.2.14)(react@19.2.3) + react: 19.2.3 + transitivePeerDependencies: + - '@types/react' + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.8.1 @@ -20105,6 +20677,8 @@ snapshots: '@polka/url@1.0.0-next.29': {} + '@popperjs/core@2.11.8': {} + '@poppinss/colors@4.1.6': dependencies: kleur: 4.1.5 @@ -23019,6 +23593,38 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + '@react-spring/animated@9.7.5(react@19.2.3)': + dependencies: + '@react-spring/shared': 9.7.5(react@19.2.3) + '@react-spring/types': 9.7.5 + react: 19.2.3 + + '@react-spring/core@9.7.5(react@19.2.3)': + dependencies: + '@react-spring/animated': 9.7.5(react@19.2.3) + '@react-spring/shared': 9.7.5(react@19.2.3) + '@react-spring/types': 9.7.5 + react: 19.2.3 + + '@react-spring/rafz@9.7.5': {} + + '@react-spring/shared@9.7.5(react@19.2.3)': + dependencies: + '@react-spring/rafz': 9.7.5 + '@react-spring/types': 9.7.5 + react: 19.2.3 + + '@react-spring/types@9.7.5': {} + + '@react-spring/web@9.7.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@react-spring/animated': 9.7.5(react@19.2.3) + '@react-spring/core': 9.7.5(react@19.2.3) + '@react-spring/shared': 9.7.5(react@19.2.3) + '@react-spring/types': 9.7.5 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + '@react-stately/autocomplete@3.0.0-beta.4(react@19.2.3)': dependencies: '@react-stately/utils': 3.11.0(react@19.2.3) @@ -24675,8 +25281,12 @@ snapshots: dependencies: undici-types: 7.18.2 + '@types/parse-json@4.0.2': {} + '@types/prismjs@1.26.6': {} + '@types/prop-types@15.7.15': {} + '@types/qs@6.15.0': {} '@types/range-parser@1.2.7': {} @@ -24689,6 +25299,10 @@ snapshots: dependencies: '@types/react': 19.2.14 + '@types/react-transition-group@4.4.12(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + '@types/react@19.2.14': dependencies: csstype: 3.2.3 @@ -25704,6 +26318,12 @@ snapshots: '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.7 + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.27.6 + cosmiconfig: 7.1.0 + resolve: 1.22.11 + babel-plugin-polyfill-corejs2@0.4.16(@babel/core@7.29.0): dependencies: '@babel/compat-data': 7.29.0 @@ -26344,6 +26964,8 @@ snapshots: content-type@1.0.5: {} + convert-source-map@1.9.0: {} + convert-source-map@2.0.0: {} cookie-es@1.2.2: {} @@ -26381,6 +27003,14 @@ snapshots: dependencies: layout-base: 2.0.1 + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.3 + crc-32@1.2.2: {} crc32-stream@6.0.0: @@ -26943,6 +27573,10 @@ snapshots: environment@1.1.0: {} + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + error-stack-parser-es@1.0.5: {} error-stack-parser@2.1.4: @@ -27918,6 +28552,8 @@ snapshots: transitivePeerDependencies: - supports-color + find-root@1.1.0: {} + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -27969,12 +28605,13 @@ snapshots: fraction.js@5.3.4: {} - framer-motion@12.34.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + framer-motion@12.34.3(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: motion-dom: 12.34.3 motion-utils: 12.29.2 tslib: 2.8.1 optionalDependencies: + '@emotion/is-prop-valid': 1.4.0 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -28058,7 +28695,7 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-ui@16.6.5(@takumi-rs/image-response@0.68.17)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1): + fumadocs-ui@16.6.5(@emotion/is-prop-valid@1.4.0)(@takumi-rs/image-response@0.68.17)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1): dependencies: '@fumadocs/tailwind': 0.0.2(tailwindcss@4.2.1) '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -28074,7 +28711,7 @@ snapshots: class-variance-authority: 0.7.1 fumadocs-core: 16.6.5(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@0.570.0(react@19.2.4))(next@16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.89.2))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) lucide-react: 0.570.0(react@19.2.4) - motion: 12.34.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + motion: 12.34.3(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-themes: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -28521,6 +29158,10 @@ snapshots: highlightjs-vue@1.0.0: {} + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + hono-openapi@1.3.0(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(zod@4.3.6))(@types/json-schema@7.0.15)(hono@4.12.9)(openapi-types@12.1.3): dependencies: '@standard-community/standard-json': 0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(quansync@1.0.0)(zod-to-json-schema@3.25.2(zod@4.3.6))(zod@4.3.6) @@ -28743,6 +29384,8 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-arrayish@0.2.1: {} + is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -30441,11 +31084,12 @@ snapshots: motion-utils@12.29.2: {} - motion@12.34.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + motion@12.34.3(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: - framer-motion: 12.34.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + framer-motion: 12.34.3(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) tslib: 2.8.1 optionalDependencies: + '@emotion/is-prop-valid': 1.4.0 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -31253,6 +31897,13 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + parse-ms@4.0.0: {} parse-png@2.1.0: @@ -31308,6 +31959,8 @@ snapshots: path-to-regexp@8.3.0: {} + path-type@4.0.0: {} + pathe@1.1.2: {} pathe@2.0.3: {} @@ -32036,6 +32689,8 @@ snapshots: react-is@18.3.1: {} + react-is@19.2.6: {} + react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.3): dependencies: '@types/hast': 3.0.4 @@ -33371,6 +34026,8 @@ snapshots: styleq@0.1.3: {} + stylis@4.2.0: {} + stylis@4.3.6: {} sucrase@3.35.0: @@ -34768,6 +35425,8 @@ snapshots: yallist@5.0.0: {} + yaml@1.10.3: {} + yaml@2.8.0: {} yaml@2.8.3: {}