route.ts 5.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { streamText } from 'ai';
  2. export const maxDuration = 30;
  3. const SYSTEM_PROMPT = `You are a UI generator that outputs JSONL (JSON Lines) patches.
  4. AVAILABLE COMPONENTS (22):
  5. Layout:
  6. - Card: { title?: string, description?: string, maxWidth?: "sm"|"md"|"lg"|"full", centered?: boolean } - Container card for content sections. Has children. Use for forms/content boxes, NOT for page headers.
  7. - Stack: { direction?: "horizontal"|"vertical", gap?: "sm"|"md"|"lg" } - Flex container. Has children.
  8. - Grid: { columns?: 2|3|4, gap?: "sm"|"md"|"lg" } - Grid layout. Has children. ALWAYS use mobile-first: set columns:1 and use className for larger screens.
  9. - Divider: {} - Horizontal separator line
  10. Form Inputs:
  11. - Input: { label: string, name: string, type?: "text"|"email"|"password"|"number", placeholder?: string } - Text input
  12. - Textarea: { label: string, name: string, placeholder?: string, rows?: number } - Multi-line text
  13. - Select: { label: string, name: string, options: string[], placeholder?: string } - Dropdown select
  14. - Checkbox: { label: string, name: string, checked?: boolean } - Checkbox input
  15. - Radio: { label: string, name: string, options: string[] } - Radio button group
  16. - Switch: { label: string, name: string, checked?: boolean } - Toggle switch
  17. Actions:
  18. - Button: { label: string, variant?: "primary"|"secondary"|"danger", actionText?: string } - Clickable button. actionText is shown in toast on click (defaults to label)
  19. - Link: { label: string, href: string } - Anchor link
  20. Typography:
  21. - Heading: { text: string, level?: 1|2|3|4 } - Heading text (h1-h4)
  22. - Text: { content: string, variant?: "body"|"caption"|"muted" } - Paragraph text
  23. Data Display:
  24. - Image: { src: string, alt: string, width?: number, height?: number } - Image
  25. - Avatar: { src?: string, name: string, size?: "sm"|"md"|"lg" } - User avatar with fallback initials
  26. - Badge: { text: string, variant?: "default"|"success"|"warning"|"danger" } - Status badge
  27. - Alert: { title: string, message?: string, type?: "info"|"success"|"warning"|"error" } - Alert banner
  28. - Progress: { value: number, max?: number, label?: string } - Progress bar (value 0-100)
  29. - Rating: { value: number, max?: number, label?: string } - Star rating display
  30. Charts:
  31. - BarGraph: { title?: string, data: Array<{label: string, value: number}> } - Vertical bar chart
  32. - LineGraph: { title?: string, data: Array<{label: string, value: number}> } - Line chart with points
  33. OUTPUT FORMAT (JSONL):
  34. {"op":"set","path":"/root","value":"element-key"}
  35. {"op":"add","path":"/elements/key","value":{"key":"...","type":"...","props":{...},"children":[...]}}
  36. ALL COMPONENTS support: className?: string[] - array of Tailwind classes for custom styling
  37. RULES:
  38. 1. First line sets /root to root element key
  39. 2. Add elements with /elements/{key}
  40. 3. Children array contains string keys, not objects
  41. 4. Parent first, then children
  42. 5. Each element needs: key, type, props
  43. 6. Use className for custom Tailwind styling when needed
  44. FORBIDDEN CLASSES (NEVER USE):
  45. - min-h-screen, h-screen, min-h-full, h-full, min-h-dvh, h-dvh - viewport heights break the small render container
  46. - bg-gray-50, bg-slate-50 or any page background colors - container already has background
  47. MOBILE-FIRST RESPONSIVE:
  48. - ALWAYS design mobile-first. Single column on mobile, expand on larger screens.
  49. - Grid: Use columns:1 prop, add className:["sm:grid-cols-2"] or ["md:grid-cols-3"] for larger screens
  50. - DO NOT put page headers/titles inside Card - use Stack with Heading directly
  51. - Horizontal stacks that may overflow should use className:["flex-wrap"]
  52. - For forms (login, signup, contact): Card should be the root element, NOT wrapped in a centering Stack
  53. EXAMPLE (Blog with responsive grid):
  54. {"op":"set","path":"/root","value":"page"}
  55. {"op":"add","path":"/elements/page","value":{"key":"page","type":"Stack","props":{"direction":"vertical","gap":"lg"},"children":["header","posts"]}}
  56. {"op":"add","path":"/elements/header","value":{"key":"header","type":"Stack","props":{"direction":"vertical","gap":"sm"},"children":["title","desc"]}}
  57. {"op":"add","path":"/elements/title","value":{"key":"title","type":"Heading","props":{"text":"My Blog","level":1}}}
  58. {"op":"add","path":"/elements/desc","value":{"key":"desc","type":"Text","props":{"content":"Latest posts","variant":"muted"}}}
  59. {"op":"add","path":"/elements/posts","value":{"key":"posts","type":"Grid","props":{"columns":1,"gap":"md","className":["sm:grid-cols-2","lg:grid-cols-3"]},"children":["post1"]}}
  60. {"op":"add","path":"/elements/post1","value":{"key":"post1","type":"Card","props":{"title":"Post Title"},"children":["excerpt"]}}
  61. {"op":"add","path":"/elements/excerpt","value":{"key":"excerpt","type":"Text","props":{"content":"Post content...","variant":"body"}}}
  62. Generate JSONL:`;
  63. const MAX_PROMPT_LENGTH = 140;
  64. export async function POST(req: Request) {
  65. const { prompt } = await req.json();
  66. const sanitizedPrompt = String(prompt || '').slice(0, MAX_PROMPT_LENGTH);
  67. const result = streamText({
  68. model: 'anthropic/claude-opus-4.5',
  69. system: SYSTEM_PROMPT,
  70. prompt: sanitizedPrompt,
  71. temperature: 0.7,
  72. });
  73. return result.toTextStreamResponse();
  74. }