route.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { streamText } from "ai";
  2. import { headers } from "next/headers";
  3. import { minuteRateLimit, dailyRateLimit } from "@/lib/rate-limit";
  4. import { playgroundCatalog } from "@/lib/catalog";
  5. export const maxDuration = 30;
  6. const SYSTEM_PROMPT = playgroundCatalog.prompt({
  7. customRules: [
  8. "For forms: Card should be the root element, not wrapped in a centering Stack",
  9. "NEVER use viewport height classes (min-h-screen, h-screen) - breaks the container",
  10. "NEVER use page background colors (bg-gray-50) - container has its own background",
  11. ],
  12. });
  13. const MAX_PROMPT_LENGTH = 500;
  14. const DEFAULT_MODEL = "anthropic/claude-haiku-4.5";
  15. export async function POST(req: Request) {
  16. // Get client IP for rate limiting
  17. const headersList = await headers();
  18. const ip = headersList.get("x-forwarded-for")?.split(",")[0] ?? "anonymous";
  19. // Check rate limits (minute and daily)
  20. const [minuteResult, dailyResult] = await Promise.all([
  21. minuteRateLimit.limit(ip),
  22. dailyRateLimit.limit(ip),
  23. ]);
  24. if (!minuteResult.success || !dailyResult.success) {
  25. const isMinuteLimit = !minuteResult.success;
  26. return new Response(
  27. JSON.stringify({
  28. error: "Rate limit exceeded",
  29. message: isMinuteLimit
  30. ? "Too many requests. Please wait a moment before trying again."
  31. : "Daily limit reached. Please try again tomorrow.",
  32. }),
  33. {
  34. status: 429,
  35. headers: { "Content-Type": "application/json" },
  36. },
  37. );
  38. }
  39. const { prompt, context } = await req.json();
  40. const previousSpec = context?.previousSpec;
  41. const sanitizedPrompt = String(prompt || "").slice(0, MAX_PROMPT_LENGTH);
  42. // Build the user prompt, including previous tree for iteration
  43. let userPrompt = sanitizedPrompt;
  44. if (
  45. previousSpec &&
  46. previousSpec.root &&
  47. Object.keys(previousSpec.elements || {}).length > 0
  48. ) {
  49. userPrompt = `CURRENT UI STATE (already loaded, DO NOT recreate existing elements):
  50. ${JSON.stringify(previousSpec, null, 2)}
  51. USER REQUEST: ${sanitizedPrompt}
  52. IMPORTANT: The current UI is already loaded. Output ONLY the patches needed to make the requested change:
  53. - To add a new element: {"op":"add","path":"/elements/new-key","value":{...}}
  54. - To modify an existing element: {"op":"replace","path":"/elements/existing-key","value":{...}}
  55. - To remove an element: {"op":"remove","path":"/elements/old-key"}
  56. - To update the root: {"op":"replace","path":"/root","value":"new-root-key"}
  57. - To add children: update the parent element with new children array
  58. DO NOT output patches for elements that don't need to change. Only output what's necessary for the requested modification.`;
  59. }
  60. const result = streamText({
  61. model: process.env.AI_GATEWAY_MODEL || DEFAULT_MODEL,
  62. system: SYSTEM_PROMPT,
  63. prompt: userPrompt,
  64. temperature: 0.7,
  65. });
  66. return result.toTextStreamResponse();
  67. }