catalog.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import { createCatalog } from '@json-render/core';
  2. import { z } from 'zod';
  3. /**
  4. * Dashboard component catalog
  5. *
  6. * This defines the ONLY components that the AI can generate.
  7. * It acts as a guardrail - the AI cannot create arbitrary HTML/CSS.
  8. *
  9. * Note: OpenAI structured output requires all fields to be required.
  10. * Use .nullable() instead of .optional() for optional fields.
  11. */
  12. export const dashboardCatalog = createCatalog({
  13. name: 'dashboard',
  14. components: {
  15. // Layout Components
  16. Card: {
  17. props: z.object({
  18. title: z.string().nullable(),
  19. description: z.string().nullable(),
  20. padding: z.enum(['sm', 'md', 'lg']).nullable(),
  21. }),
  22. hasChildren: true,
  23. description: 'A card container with optional title',
  24. },
  25. Grid: {
  26. props: z.object({
  27. columns: z.number().min(1).max(4).nullable(),
  28. gap: z.enum(['sm', 'md', 'lg']).nullable(),
  29. }),
  30. hasChildren: true,
  31. description: 'Grid layout with configurable columns',
  32. },
  33. Stack: {
  34. props: z.object({
  35. direction: z.enum(['horizontal', 'vertical']).nullable(),
  36. gap: z.enum(['sm', 'md', 'lg']).nullable(),
  37. align: z.enum(['start', 'center', 'end', 'stretch']).nullable(),
  38. }),
  39. hasChildren: true,
  40. description: 'Flex stack for horizontal or vertical layouts',
  41. },
  42. // Data Display Components
  43. Metric: {
  44. props: z.object({
  45. label: z.string(),
  46. valuePath: z.string(),
  47. format: z.enum(['number', 'currency', 'percent']).nullable(),
  48. trend: z.enum(['up', 'down', 'neutral']).nullable(),
  49. trendValue: z.string().nullable(),
  50. }),
  51. description: 'Display a single metric with optional trend indicator',
  52. },
  53. Chart: {
  54. props: z.object({
  55. type: z.enum(['bar', 'line', 'pie', 'area']),
  56. dataPath: z.string(),
  57. title: z.string().nullable(),
  58. height: z.number().nullable(),
  59. }),
  60. description: 'Display a chart from array data',
  61. },
  62. Table: {
  63. props: z.object({
  64. dataPath: z.string(),
  65. columns: z.array(
  66. z.object({
  67. key: z.string(),
  68. label: z.string(),
  69. format: z.enum(['text', 'currency', 'date', 'badge']).nullable(),
  70. })
  71. ),
  72. }),
  73. description: 'Display tabular data',
  74. },
  75. List: {
  76. props: z.object({
  77. dataPath: z.string(),
  78. emptyMessage: z.string().nullable(),
  79. }),
  80. hasChildren: true,
  81. description: 'Render a list from array data',
  82. },
  83. // Interactive Components
  84. Button: {
  85. props: z.object({
  86. label: z.string(),
  87. variant: z.enum(['primary', 'secondary', 'danger', 'ghost']).nullable(),
  88. size: z.enum(['sm', 'md', 'lg']).nullable(),
  89. action: z.string(),
  90. disabled: z.boolean().nullable(),
  91. }),
  92. description: 'Clickable button with action',
  93. },
  94. Select: {
  95. props: z.object({
  96. label: z.string().nullable(),
  97. bindPath: z.string(),
  98. options: z.array(
  99. z.object({
  100. value: z.string(),
  101. label: z.string(),
  102. })
  103. ),
  104. placeholder: z.string().nullable(),
  105. }),
  106. description: 'Dropdown select input',
  107. },
  108. DatePicker: {
  109. props: z.object({
  110. label: z.string().nullable(),
  111. bindPath: z.string(),
  112. placeholder: z.string().nullable(),
  113. }),
  114. description: 'Date picker input',
  115. },
  116. // Typography
  117. Heading: {
  118. props: z.object({
  119. text: z.string(),
  120. level: z.enum(['h1', 'h2', 'h3', 'h4']).nullable(),
  121. }),
  122. description: 'Section heading',
  123. },
  124. Text: {
  125. props: z.object({
  126. content: z.string(),
  127. variant: z.enum(['body', 'caption', 'label']).nullable(),
  128. color: z.enum(['default', 'muted', 'success', 'warning', 'danger']).nullable(),
  129. }),
  130. description: 'Text paragraph',
  131. },
  132. // Status Components
  133. Badge: {
  134. props: z.object({
  135. text: z.string(),
  136. variant: z.enum(['default', 'success', 'warning', 'danger', 'info']).nullable(),
  137. }),
  138. description: 'Small status badge',
  139. },
  140. Alert: {
  141. props: z.object({
  142. type: z.enum(['info', 'success', 'warning', 'error']),
  143. title: z.string(),
  144. message: z.string().nullable(),
  145. dismissible: z.boolean().nullable(),
  146. }),
  147. description: 'Alert/notification banner',
  148. },
  149. // Special Components
  150. Divider: {
  151. props: z.object({
  152. label: z.string().nullable(),
  153. }),
  154. description: 'Visual divider',
  155. },
  156. Empty: {
  157. props: z.object({
  158. title: z.string(),
  159. description: z.string().nullable(),
  160. action: z.string().nullable(),
  161. actionLabel: z.string().nullable(),
  162. }),
  163. description: 'Empty state placeholder',
  164. },
  165. },
  166. actions: {
  167. export_report: { description: 'Export the current dashboard to PDF' },
  168. refresh_data: { description: 'Refresh all metrics and charts' },
  169. view_details: { description: 'View detailed information' },
  170. apply_filter: { description: 'Apply the current filter settings' },
  171. },
  172. validation: 'strict',
  173. });
  174. // Export the component list for the AI prompt
  175. export const componentList = dashboardCatalog.componentNames as string[];