route.ts 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. import { streamText } from "ai";
  2. import { buildUserPrompt, type Spec } from "@json-render/core";
  3. import { emailCatalog } from "@/lib/catalog";
  4. import { minuteRateLimit, dailyRateLimit } from "@/lib/rate-limit";
  5. import { headers } from "next/headers";
  6. export const maxDuration = 60;
  7. const SYSTEM_PROMPT = emailCatalog.prompt();
  8. const DEFAULT_MODEL = "anthropic/claude-haiku-4.5";
  9. export async function POST(req: Request) {
  10. const headersList = await headers();
  11. const ip = headersList.get("x-forwarded-for")?.split(",")[0] ?? "anonymous";
  12. const [minuteResult, dailyResult] = await Promise.all([
  13. minuteRateLimit.limit(ip),
  14. dailyRateLimit.limit(ip),
  15. ]);
  16. if (!minuteResult.success || !dailyResult.success) {
  17. const isMinuteLimit = !minuteResult.success;
  18. return new Response(
  19. JSON.stringify({
  20. error: "Rate limit exceeded",
  21. message: isMinuteLimit
  22. ? "Too many requests. Please wait a moment before trying again."
  23. : "Daily limit reached. Please try again tomorrow.",
  24. }),
  25. {
  26. status: 429,
  27. headers: { "Content-Type": "application/json" },
  28. },
  29. );
  30. }
  31. const { prompt, startingSpec } = (await req.json()) as {
  32. prompt: string;
  33. startingSpec?: Spec | null;
  34. };
  35. if (!prompt || typeof prompt !== "string") {
  36. return Response.json({ error: "prompt is required" }, { status: 400 });
  37. }
  38. const userPrompt = buildUserPrompt({
  39. prompt,
  40. currentSpec: startingSpec,
  41. });
  42. const result = streamText({
  43. model: process.env.AI_GATEWAY_MODEL ?? DEFAULT_MODEL,
  44. system: SYSTEM_PROMPT,
  45. prompt: userPrompt,
  46. temperature: 0.7,
  47. });
  48. return result.toTextStreamResponse();
  49. }