| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- "use client";
- import { useState, useCallback } from "react";
- import {
- DataProvider,
- ActionProvider,
- VisibilityProvider,
- useUIStream,
- Renderer,
- } from "@json-render/react";
- import { componentRegistry } from "@/components/ui";
- const INITIAL_DATA = {
- analytics: {
- revenue: 125000,
- growth: 0.15,
- customers: 1234,
- orders: 567,
- salesByRegion: [
- { label: "US", value: 45000 },
- { label: "EU", value: 35000 },
- { label: "Asia", value: 28000 },
- { label: "Other", value: 17000 },
- ],
- recentTransactions: [
- {
- id: "TXN001",
- customer: "Acme Corp",
- amount: 1500,
- status: "completed",
- date: "2024-01-15",
- },
- {
- id: "TXN002",
- customer: "Globex Inc",
- amount: 2300,
- status: "pending",
- date: "2024-01-14",
- },
- {
- id: "TXN003",
- customer: "Initech",
- amount: 890,
- status: "completed",
- date: "2024-01-13",
- },
- {
- id: "TXN004",
- customer: "Umbrella Co",
- amount: 4200,
- status: "completed",
- date: "2024-01-12",
- },
- ],
- },
- form: {
- dateRange: "",
- region: "",
- },
- };
- const ACTION_HANDLERS = {
- export_report: () => alert("Exporting report..."),
- refresh_data: () => alert("Refreshing data..."),
- view_details: (params: Record<string, unknown>) =>
- alert(`Details: ${JSON.stringify(params)}`),
- apply_filter: () => alert("Applying filters..."),
- };
- function DashboardContent() {
- const [prompt, setPrompt] = useState("");
- const { tree, isStreaming, error, send, clear } = useUIStream({
- api: "/api/generate",
- onError: (err) => console.error("Generation error:", err),
- });
- const handleSubmit = useCallback(
- async (e: React.FormEvent) => {
- e.preventDefault();
- if (!prompt.trim()) return;
- await send(prompt, { data: INITIAL_DATA });
- },
- [prompt, send],
- );
- const examples = [
- "Revenue dashboard with metrics and chart",
- "Recent transactions table",
- "Customer count with trend",
- ];
- const hasElements = tree && Object.keys(tree.elements).length > 0;
- return (
- <div style={{ maxWidth: 960, margin: "0 auto", padding: "48px 24px" }}>
- <header style={{ marginBottom: 48 }}>
- <h1
- style={{
- margin: 0,
- fontSize: 32,
- fontWeight: 600,
- letterSpacing: "-0.02em",
- }}
- >
- Dashboard
- </h1>
- <p style={{ margin: "8px 0 0", color: "var(--muted)", fontSize: 16 }}>
- Generate widgets from prompts. Constrained to your catalog.
- </p>
- </header>
- <form onSubmit={handleSubmit} style={{ marginBottom: 32 }}>
- <div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
- <input
- type="text"
- value={prompt}
- onChange={(e) => setPrompt(e.target.value)}
- placeholder="Describe what you want..."
- disabled={isStreaming}
- style={{
- flex: 1,
- padding: "12px 16px",
- background: "var(--card)",
- border: "1px solid var(--border)",
- borderRadius: "var(--radius)",
- color: "var(--foreground)",
- fontSize: 16,
- outline: "none",
- }}
- />
- <button
- type="submit"
- disabled={isStreaming || !prompt.trim()}
- style={{
- padding: "12px 24px",
- background: isStreaming ? "var(--border)" : "var(--foreground)",
- color: "var(--background)",
- border: "none",
- borderRadius: "var(--radius)",
- fontSize: 16,
- fontWeight: 500,
- opacity: isStreaming || !prompt.trim() ? 0.5 : 1,
- }}
- >
- {isStreaming ? "Generating..." : "Generate"}
- </button>
- {hasElements && (
- <button
- type="button"
- onClick={clear}
- style={{
- padding: "12px 16px",
- background: "transparent",
- color: "var(--muted)",
- border: "1px solid var(--border)",
- borderRadius: "var(--radius)",
- fontSize: 16,
- }}
- >
- Clear
- </button>
- )}
- </div>
- <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
- {examples.map((ex) => (
- <button
- key={ex}
- type="button"
- onClick={() => setPrompt(ex)}
- style={{
- padding: "6px 12px",
- background: "var(--card)",
- color: "var(--muted)",
- border: "1px solid var(--border)",
- borderRadius: "var(--radius)",
- fontSize: 13,
- }}
- >
- {ex}
- </button>
- ))}
- </div>
- </form>
- {error && (
- <div
- style={{
- padding: 16,
- marginBottom: 24,
- background: "var(--card)",
- border: "1px solid var(--border)",
- borderRadius: "var(--radius)",
- color: "#ef4444",
- fontSize: 14,
- }}
- >
- {error.message}
- </div>
- )}
- <div
- style={{
- minHeight: 300,
- padding: 24,
- background: "var(--card)",
- border: "1px solid var(--border)",
- borderRadius: "var(--radius)",
- }}
- >
- {!hasElements && !isStreaming ? (
- <div
- style={{
- textAlign: "center",
- padding: "60px 20px",
- color: "var(--muted)",
- }}
- >
- <p style={{ margin: 0 }}>Enter a prompt to generate a widget</p>
- </div>
- ) : tree ? (
- <Renderer
- tree={tree}
- registry={componentRegistry}
- loading={isStreaming}
- />
- ) : null}
- </div>
- {hasElements && (
- <details style={{ marginTop: 24 }}>
- <summary
- style={{ cursor: "pointer", fontSize: 14, color: "var(--muted)" }}
- >
- View JSON
- </summary>
- <pre
- style={{
- marginTop: 8,
- padding: 16,
- background: "var(--card)",
- border: "1px solid var(--border)",
- borderRadius: "var(--radius)",
- overflow: "auto",
- fontSize: 12,
- color: "var(--muted)",
- }}
- >
- {JSON.stringify(tree, null, 2)}
- </pre>
- </details>
- )}
- </div>
- );
- }
- export default function DashboardPage() {
- return (
- <DataProvider initialData={INITIAL_DATA}>
- <VisibilityProvider>
- <ActionProvider handlers={ACTION_HANDLERS}>
- <DashboardContent />
- </ActionProvider>
- </VisibilityProvider>
- </DataProvider>
- );
- }
|