diff --git a/apps/web/app/docs/actions/page.tsx b/apps/web/app/(main)/docs/actions/page.tsx similarity index 100% rename from apps/web/app/docs/actions/page.tsx rename to apps/web/app/(main)/docs/actions/page.tsx diff --git a/apps/web/app/docs/ai-sdk/page.tsx b/apps/web/app/(main)/docs/ai-sdk/page.tsx similarity index 100% rename from apps/web/app/docs/ai-sdk/page.tsx rename to apps/web/app/(main)/docs/ai-sdk/page.tsx diff --git a/apps/web/app/docs/api/codegen/page.tsx b/apps/web/app/(main)/docs/api/codegen/page.tsx similarity index 100% rename from apps/web/app/docs/api/codegen/page.tsx rename to apps/web/app/(main)/docs/api/codegen/page.tsx diff --git a/apps/web/app/docs/api/core/page.tsx b/apps/web/app/(main)/docs/api/core/page.tsx similarity index 100% rename from apps/web/app/docs/api/core/page.tsx rename to apps/web/app/(main)/docs/api/core/page.tsx diff --git a/apps/web/app/docs/api/react/page.tsx b/apps/web/app/(main)/docs/api/react/page.tsx similarity index 100% rename from apps/web/app/docs/api/react/page.tsx rename to apps/web/app/(main)/docs/api/react/page.tsx diff --git a/apps/web/app/docs/catalog/page.tsx b/apps/web/app/(main)/docs/catalog/page.tsx similarity index 100% rename from apps/web/app/docs/catalog/page.tsx rename to apps/web/app/(main)/docs/catalog/page.tsx diff --git a/apps/web/app/docs/code-export/page.tsx b/apps/web/app/(main)/docs/code-export/page.tsx similarity index 100% rename from apps/web/app/docs/code-export/page.tsx rename to apps/web/app/(main)/docs/code-export/page.tsx diff --git a/apps/web/app/docs/components/page.tsx b/apps/web/app/(main)/docs/components/page.tsx similarity index 100% rename from apps/web/app/docs/components/page.tsx rename to apps/web/app/(main)/docs/components/page.tsx diff --git a/apps/web/app/docs/data-binding/page.tsx b/apps/web/app/(main)/docs/data-binding/page.tsx similarity index 100% rename from apps/web/app/docs/data-binding/page.tsx rename to apps/web/app/(main)/docs/data-binding/page.tsx diff --git a/apps/web/app/docs/installation/page.tsx b/apps/web/app/(main)/docs/installation/page.tsx similarity index 100% rename from apps/web/app/docs/installation/page.tsx rename to apps/web/app/(main)/docs/installation/page.tsx diff --git a/apps/web/app/docs/layout.tsx b/apps/web/app/(main)/docs/layout.tsx similarity index 100% rename from apps/web/app/docs/layout.tsx rename to apps/web/app/(main)/docs/layout.tsx diff --git a/apps/web/app/docs/page.tsx b/apps/web/app/(main)/docs/page.tsx similarity index 100% rename from apps/web/app/docs/page.tsx rename to apps/web/app/(main)/docs/page.tsx diff --git a/apps/web/app/docs/quick-start/page.tsx b/apps/web/app/(main)/docs/quick-start/page.tsx similarity index 100% rename from apps/web/app/docs/quick-start/page.tsx rename to apps/web/app/(main)/docs/quick-start/page.tsx diff --git a/apps/web/app/docs/streaming/page.tsx b/apps/web/app/(main)/docs/streaming/page.tsx similarity index 100% rename from apps/web/app/docs/streaming/page.tsx rename to apps/web/app/(main)/docs/streaming/page.tsx diff --git a/apps/web/app/docs/validation/page.tsx b/apps/web/app/(main)/docs/validation/page.tsx similarity index 100% rename from apps/web/app/docs/validation/page.tsx rename to apps/web/app/(main)/docs/validation/page.tsx diff --git a/apps/web/app/docs/visibility/page.tsx b/apps/web/app/(main)/docs/visibility/page.tsx similarity index 100% rename from apps/web/app/docs/visibility/page.tsx rename to apps/web/app/(main)/docs/visibility/page.tsx diff --git a/apps/web/app/(main)/layout.tsx b/apps/web/app/(main)/layout.tsx new file mode 100644 index 0000000..89ea65f --- /dev/null +++ b/apps/web/app/(main)/layout.tsx @@ -0,0 +1,16 @@ +import { Header } from "@/components/header"; +import { Footer } from "@/components/footer"; + +export default function MainLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+
{children}
+
+ ); +} diff --git a/apps/web/app/page.tsx b/apps/web/app/(main)/page.tsx similarity index 98% rename from apps/web/app/page.tsx rename to apps/web/app/(main)/page.tsx index 49237cd..36bf0d3 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/(main)/page.tsx @@ -10,11 +10,11 @@ export default function Home() { {/* Hero */}

- Predictable. Guardrailed. Fast. + AI → json-render → UI

- Let users generate dashboards, widgets, apps, and data visualizations - from prompts — safely constrained to components you define. + Define a component catalog. Users prompt. AI outputs JSON constrained + to your catalog. Your components render it.

diff --git a/apps/web/app/api/generate/route.ts b/apps/web/app/api/generate/route.ts index 4c97451..a127db0 100644 --- a/apps/web/app/api/generate/route.ts +++ b/apps/web/app/api/generate/route.ts @@ -77,18 +77,40 @@ EXAMPLE (Blog with responsive grid): Generate JSONL:`; -const MAX_PROMPT_LENGTH = 140; +const MAX_PROMPT_LENGTH = 500; const DEFAULT_MODEL = "anthropic/claude-haiku-4.5"; export async function POST(req: Request) { - const { prompt } = await req.json(); + const { prompt, context } = await req.json(); + const previousTree = context?.previousTree; const sanitizedPrompt = String(prompt || "").slice(0, MAX_PROMPT_LENGTH); + // Build the user prompt, including previous tree for iteration + let userPrompt = sanitizedPrompt; + if ( + previousTree && + previousTree.root && + Object.keys(previousTree.elements || {}).length > 0 + ) { + userPrompt = `CURRENT UI STATE (already loaded, DO NOT recreate existing elements): +${JSON.stringify(previousTree, null, 2)} + +USER REQUEST: ${sanitizedPrompt} + +IMPORTANT: The current UI is already loaded. Output ONLY the patches needed to make the requested change: +- To add a new element: {"op":"add","path":"/elements/new-key","value":{...}} +- To modify an existing element: {"op":"set","path":"/elements/existing-key","value":{...}} +- To update the root: {"op":"set","path":"/root","value":"new-root-key"} +- To add children: update the parent element with new children array + +DO NOT output patches for elements that don't need to change. Only output what's necessary for the requested modification.`; + } + const result = streamText({ model: process.env.AI_GATEWAY_MODEL || DEFAULT_MODEL, system: SYSTEM_PROMPT, - prompt: sanitizedPrompt, + prompt: userPrompt, temperature: 0.7, }); diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 6df043c..0d2a0c8 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,8 +1,6 @@ import type { Metadata } from "next"; import localFont from "next/font/local"; import "./globals.css"; -import { Header } from "@/components/header"; -import { Footer } from "@/components/footer"; import { ThemeProvider } from "@/components/theme-provider"; import { Analytics } from "@vercel/analytics/next"; import { SpeedInsights } from "@vercel/speed-insights/next"; @@ -76,13 +74,7 @@ export default function RootLayout({ return ( - -
-
-
{children}
-
-
-
+ {children} diff --git a/apps/web/app/playground/layout.tsx b/apps/web/app/playground/layout.tsx new file mode 100644 index 0000000..2bca258 --- /dev/null +++ b/apps/web/app/playground/layout.tsx @@ -0,0 +1,9 @@ +export default function PlaygroundLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
{children}
+ ); +} diff --git a/apps/web/app/playground/page.tsx b/apps/web/app/playground/page.tsx index 01930c3..53f0c05 100644 --- a/apps/web/app/playground/page.tsx +++ b/apps/web/app/playground/page.tsx @@ -1,72 +1,9 @@ -import { Button } from "@/components/ui/button"; +import { Playground } from "@/components/playground"; export const metadata = { title: "Playground | json-render", }; export default function PlaygroundPage() { - return ( -
-

Playground

-

- Try json-render with a live example. -

- -
-
-

Run locally

-

- Clone the repository and run the example dashboard. -

-
-            {`git clone https://github.com/vercel-labs/json-render
-cd json-render
-pnpm install
-pnpm dev`}
-          
-

- Open http://localhost:3001 for the example dashboard. -

-
- -
-

Example prompts

-

- Try these prompts in the example dashboard: -

-
- {[ - "Create a revenue dashboard with monthly metrics", - "Build a user management panel with a table", - "Design a settings form with text inputs", - "Make a notification center with alerts", - ].map((prompt) => ( -
- {prompt} -
- ))} -
-
- -
-

Interactive playground

-

- A browser-based playground is coming soon. -

- -
-
-
- ); + return ; } diff --git a/apps/web/components/demo.tsx b/apps/web/components/demo.tsx index 3ad7bf9..92ed992 100644 --- a/apps/web/components/demo.tsx +++ b/apps/web/components/demo.tsx @@ -159,9 +159,28 @@ type Phase = "typing" | "streaming" | "complete"; type Tab = "stream" | "json"; type RenderView = "dynamic" | "static"; -export function Demo() { - const [mode, setMode] = useState("simulation"); - const [phase, setPhase] = useState("typing"); +interface DemoProps { + fullscreen?: boolean; + skipSimulation?: boolean; +} + +const EXAMPLE_PROMPTS = [ + "Create a login form with email and password", + "Build a feedback form with rating stars", + "Design a contact card with avatar", + "Make a settings panel with toggles", +]; + +export function Demo({ + fullscreen = false, + skipSimulation = false, +}: DemoProps) { + const [mode, setMode] = useState( + skipSimulation ? "interactive" : "simulation", + ); + const [phase, setPhase] = useState( + skipSimulation ? "complete" : "typing", + ); const [typedPrompt, setTypedPrompt] = useState(""); const [userPrompt, setUserPrompt] = useState(""); const [stageIndex, setStageIndex] = useState(-1); @@ -909,10 +928,19 @@ Open [http://localhost:3000](http://localhost:3000) to view. const isStreamingSimulation = mode === "simulation" && phase === "streaming"; const showLoadingDots = isStreamingSimulation || isStreaming; + const handleExampleClick = useCallback((prompt: string) => { + setMode("interactive"); + setPhase("complete"); + setUserPrompt(prompt); + setTimeout(() => inputRef.current?.focus(), 0); + }, []); + return ( -
+
{/* Prompt input */} -
+
{ @@ -1000,16 +1028,32 @@ Open [http://localhost:3000](http://localhost:3000) to view. )}
-
- Try: "Create a login form" or "Build a feedback form - with rating" -
+ {fullscreen ? ( +
+ {EXAMPLE_PROMPTS.map((prompt) => ( + + ))} +
+ ) : ( +
+ Try: "Create a login form" or "Build a feedback form + with rating" +
+ )}
-
+
{/* Tabbed code/stream/json panel */} -
-
+
+
{(["json", "stream"] as const).map((tab) => (
-
+
{streamLines.length > 0 ? ( <> @@ -1059,7 +1105,7 @@ Open [http://localhost:3000](http://localhost:3000) to view. )}
{/* Rendered output using json-render */} -
-
+
+
{( [ @@ -1126,7 +1172,9 @@ Open [http://localhost:3000](http://localhost:3000) to view.
-
+
{renderView === "static" && (