page.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. 'use client';
  2. import { useState, useCallback } from 'react';
  3. import {
  4. DataProvider,
  5. ActionProvider,
  6. VisibilityProvider,
  7. useUIStream,
  8. Renderer,
  9. } from '@json-render/react';
  10. import { componentRegistry } from '@/components/ui-components';
  11. // Sample data for the dashboard
  12. const INITIAL_DATA = {
  13. analytics: {
  14. revenue: 125000,
  15. growth: 0.15,
  16. customers: 1234,
  17. orders: 567,
  18. salesByRegion: [
  19. { label: 'US', value: 45000 },
  20. { label: 'EU', value: 35000 },
  21. { label: 'Asia', value: 28000 },
  22. { label: 'Other', value: 17000 },
  23. ],
  24. recentTransactions: [
  25. { id: 'TXN001', customer: 'Acme Corp', amount: 1500, status: 'completed', date: '2024-01-15' },
  26. { id: 'TXN002', customer: 'Globex Inc', amount: 2300, status: 'pending', date: '2024-01-14' },
  27. { id: 'TXN003', customer: 'Initech', amount: 890, status: 'completed', date: '2024-01-13' },
  28. { id: 'TXN004', customer: 'Umbrella Co', amount: 4200, status: 'completed', date: '2024-01-12' },
  29. ],
  30. },
  31. form: {
  32. dateRange: '',
  33. region: '',
  34. },
  35. };
  36. // Action handlers
  37. const ACTION_HANDLERS = {
  38. export_report: () => {
  39. alert('Exporting report... (demo)');
  40. },
  41. refresh_data: () => {
  42. alert('Refreshing data... (demo)');
  43. },
  44. view_details: (params: Record<string, unknown>) => {
  45. alert(`Viewing details for: ${JSON.stringify(params)}`);
  46. },
  47. apply_filter: () => {
  48. alert('Applying filters... (demo)');
  49. },
  50. };
  51. function DashboardContent() {
  52. const [prompt, setPrompt] = useState('');
  53. const {
  54. tree,
  55. isStreaming,
  56. error,
  57. send,
  58. clear,
  59. } = useUIStream({
  60. api: '/api/generate',
  61. onError: (err) => console.error('Generation error:', err),
  62. });
  63. const handleSubmit = useCallback(
  64. async (e: React.FormEvent) => {
  65. e.preventDefault();
  66. if (!prompt.trim()) return;
  67. await send(prompt, { data: INITIAL_DATA });
  68. },
  69. [prompt, send]
  70. );
  71. const examplePrompts = [
  72. 'Show me a revenue dashboard with key metrics and a chart of sales by region',
  73. 'Create a transactions table with recent orders',
  74. 'Build a widget showing customer count with a trend indicator',
  75. 'Make a simple card with total revenue and an export button',
  76. ];
  77. const hasElements = tree && Object.keys(tree.elements).length > 0;
  78. return (
  79. <div style={{ maxWidth: '1200px', margin: '0 auto', padding: '24px' }}>
  80. {/* Header */}
  81. <div style={{ marginBottom: '32px' }}>
  82. <h1
  83. style={{
  84. margin: '0 0 8px 0',
  85. fontSize: '28px',
  86. fontWeight: 700,
  87. color: '#111827',
  88. }}
  89. >
  90. json-render Dashboard Demo
  91. </h1>
  92. <p style={{ margin: 0, color: '#6b7280', fontSize: '16px' }}>
  93. AI-powered widget generation with guardrails. Only catalog components can be generated.
  94. </p>
  95. </div>
  96. {/* Input Form */}
  97. <div
  98. style={{
  99. backgroundColor: '#fff',
  100. borderRadius: '12px',
  101. border: '1px solid #e5e7eb',
  102. padding: '20px',
  103. marginBottom: '24px',
  104. }}
  105. >
  106. <form onSubmit={handleSubmit}>
  107. <div style={{ display: 'flex', gap: '12px', marginBottom: '16px' }}>
  108. <input
  109. type="text"
  110. value={prompt}
  111. onChange={(e) => setPrompt(e.target.value)}
  112. placeholder="Describe the widget you want to create..."
  113. style={{
  114. flex: 1,
  115. padding: '12px 16px',
  116. borderRadius: '8px',
  117. border: '1px solid #d1d5db',
  118. fontSize: '15px',
  119. outline: 'none',
  120. }}
  121. disabled={isStreaming}
  122. />
  123. <button
  124. type="submit"
  125. disabled={isStreaming || !prompt.trim()}
  126. style={{
  127. padding: '12px 24px',
  128. backgroundColor: isStreaming ? '#9ca3af' : '#6366f1',
  129. color: '#fff',
  130. border: 'none',
  131. borderRadius: '8px',
  132. fontSize: '15px',
  133. fontWeight: 500,
  134. cursor: isStreaming ? 'not-allowed' : 'pointer',
  135. }}
  136. >
  137. {isStreaming ? 'Generating...' : 'Generate'}
  138. </button>
  139. <button
  140. type="button"
  141. onClick={clear}
  142. style={{
  143. padding: '12px 20px',
  144. backgroundColor: '#fff',
  145. color: '#374151',
  146. border: '1px solid #d1d5db',
  147. borderRadius: '8px',
  148. fontSize: '15px',
  149. fontWeight: 500,
  150. cursor: 'pointer',
  151. }}
  152. >
  153. Clear
  154. </button>
  155. </div>
  156. {/* Example Prompts */}
  157. <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
  158. <span style={{ fontSize: '13px', color: '#6b7280', marginRight: '4px' }}>
  159. Try:
  160. </span>
  161. {examplePrompts.map((example, i) => (
  162. <button
  163. key={i}
  164. type="button"
  165. onClick={() => setPrompt(example)}
  166. style={{
  167. padding: '4px 10px',
  168. backgroundColor: '#f3f4f6',
  169. border: 'none',
  170. borderRadius: '4px',
  171. fontSize: '12px',
  172. color: '#4b5563',
  173. cursor: 'pointer',
  174. }}
  175. >
  176. {example.slice(0, 40)}...
  177. </button>
  178. ))}
  179. </div>
  180. </form>
  181. </div>
  182. {/* Error Display */}
  183. {error && (
  184. <div
  185. style={{
  186. padding: '16px',
  187. backgroundColor: '#fef2f2',
  188. border: '1px solid #fca5a5',
  189. borderRadius: '8px',
  190. marginBottom: '24px',
  191. color: '#991b1b',
  192. }}
  193. >
  194. <strong>Error:</strong> {error.message}
  195. </div>
  196. )}
  197. {/* Generated UI */}
  198. <div
  199. style={{
  200. backgroundColor: '#fff',
  201. borderRadius: '12px',
  202. border: '1px solid #e5e7eb',
  203. padding: '24px',
  204. minHeight: '300px',
  205. }}
  206. >
  207. {!hasElements && !isStreaming ? (
  208. <div style={{ textAlign: 'center', padding: '60px 20px', color: '#9ca3af' }}>
  209. <div style={{ fontSize: '48px', marginBottom: '16px' }}>🎨</div>
  210. <p style={{ fontSize: '16px', margin: 0 }}>
  211. Enter a prompt above to generate a dashboard widget
  212. </p>
  213. </div>
  214. ) : tree ? (
  215. <Renderer
  216. tree={tree}
  217. registry={componentRegistry}
  218. loading={isStreaming}
  219. />
  220. ) : null}
  221. </div>
  222. {/* Debug: Show generated JSON */}
  223. {hasElements && (
  224. <details style={{ marginTop: '24px' }}>
  225. <summary
  226. style={{
  227. cursor: 'pointer',
  228. fontSize: '14px',
  229. color: '#6b7280',
  230. marginBottom: '8px',
  231. }}
  232. >
  233. View Generated JSON
  234. </summary>
  235. <pre
  236. style={{
  237. backgroundColor: '#1f2937',
  238. color: '#e5e7eb',
  239. padding: '16px',
  240. borderRadius: '8px',
  241. overflow: 'auto',
  242. fontSize: '12px',
  243. }}
  244. >
  245. {JSON.stringify(tree, null, 2)}
  246. </pre>
  247. </details>
  248. )}
  249. {/* Info Box */}
  250. <div
  251. style={{
  252. marginTop: '32px',
  253. padding: '20px',
  254. backgroundColor: '#eff6ff',
  255. borderRadius: '8px',
  256. border: '1px solid #bfdbfe',
  257. }}
  258. >
  259. <h3 style={{ margin: '0 0 12px 0', fontSize: '16px', color: '#1e40af' }}>
  260. How json-render Works
  261. </h3>
  262. <ul
  263. style={{
  264. margin: 0,
  265. paddingLeft: '20px',
  266. color: '#1e40af',
  267. fontSize: '14px',
  268. lineHeight: 1.6,
  269. }}
  270. >
  271. <li>
  272. <strong>Guardrails:</strong> AI can only generate components from the catalog (Card,
  273. Metric, Chart, etc.)
  274. </li>
  275. <li>
  276. <strong>Data Binding:</strong> Components reference data paths like{' '}
  277. <code>/analytics/revenue</code>
  278. </li>
  279. <li>
  280. <strong>Named Actions:</strong> Buttons declare actions like{' '}
  281. <code>export_report</code> - you control what they do
  282. </li>
  283. <li>
  284. <strong>Your Design System:</strong> The catalog maps to your actual React components
  285. </li>
  286. </ul>
  287. </div>
  288. </div>
  289. );
  290. }
  291. export default function DashboardPage() {
  292. return (
  293. <DataProvider initialData={INITIAL_DATA}>
  294. <VisibilityProvider>
  295. <ActionProvider handlers={ACTION_HANDLERS}>
  296. <DashboardContent />
  297. </ActionProvider>
  298. </VisibilityProvider>
  299. </DataProvider>
  300. );
  301. }