catalog.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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
  129. .enum(["default", "muted", "success", "warning", "danger"])
  130. .nullable(),
  131. }),
  132. description: "Text paragraph",
  133. },
  134. // Status Components
  135. Badge: {
  136. props: z.object({
  137. text: z.string(),
  138. variant: z
  139. .enum(["default", "success", "warning", "danger", "info"])
  140. .nullable(),
  141. }),
  142. description: "Small status badge",
  143. },
  144. Alert: {
  145. props: z.object({
  146. type: z.enum(["info", "success", "warning", "error"]),
  147. title: z.string(),
  148. message: z.string().nullable(),
  149. dismissible: z.boolean().nullable(),
  150. }),
  151. description: "Alert/notification banner",
  152. },
  153. // Special Components
  154. Divider: {
  155. props: z.object({
  156. label: z.string().nullable(),
  157. }),
  158. description: "Visual divider",
  159. },
  160. Empty: {
  161. props: z.object({
  162. title: z.string(),
  163. description: z.string().nullable(),
  164. action: z.string().nullable(),
  165. actionLabel: z.string().nullable(),
  166. }),
  167. description: "Empty state placeholder",
  168. },
  169. },
  170. actions: {
  171. export_report: { description: "Export the current dashboard to PDF" },
  172. refresh_data: { description: "Refresh all metrics and charts" },
  173. view_details: { description: "View detailed information" },
  174. apply_filter: { description: "Apply the current filter settings" },
  175. },
  176. validation: "strict",
  177. });
  178. // Export the component list for the AI prompt
  179. export const componentList = dashboardCatalog.componentNames as string[];