since3-portal/app/api/ai/generate/route.ts
2026-10-05 08:23:02 +02:00

68 lines
3.4 KiB
TypeScript

import { betaZodOutputFormat } from "@anthropic-ai/sdk/helpers/beta/zod"
import type * as z from "zod"
import { contentSystem, emailSystem, quoteSystem } from "@/modules/templates/ai/prompts"
import { ContentSchema, GenerateRequestSchema, QuoteSchema, type AiEvent } from "@/modules/templates/ai/schemas"
import { AI_MODEL, FALLBACK, aiErrorMessage, getAnthropic, ndjsonResponse, resolveAiAccess } from "@/modules/templates/ai/server"
export const maxDuration = 90
async function structured<T extends z.ZodType>(schema: T, system: string, prompt: string) {
const response = await getAnthropic().beta.messages.parse({
...FALLBACK,
model: AI_MODEL,
max_tokens: 16000,
output_config: { effort: "medium", format: betaZodOutputFormat(schema) },
system,
messages: [{ role: "user", content: prompt }],
})
return response.stop_reason === "refusal" ? null : response.parsed_output
}
// Angebots- und E-Mail-Assistent sowie Content-Generator
export async function POST(request: Request) {
const parsed = GenerateRequestSchema.safeParse(await request.json().catch(() => null))
if (!parsed.success) return Response.json({ error: "Bitte die Eingabe prüfen (mindestens ein paar Worte)." }, { status: 400 })
const input = parsed.data
const ctx = await resolveAiAccess(input.access, input.task === "content" ? "content" : "assistant")
if ("error" in ctx) return Response.json({ error: ctx.error }, { status: ctx.status })
if (input.task === "email") {
const encoder = new TextEncoder()
const body = new ReadableStream<Uint8Array>({
async start(controller) {
const send = (event: AiEvent) => controller.enqueue(encoder.encode(`${JSON.stringify(event)}\n`))
try {
const stream = getAnthropic().beta.messages.stream({
...FALLBACK,
model: AI_MODEL,
max_tokens: 8000,
output_config: { effort: "medium" },
system: emailSystem(ctx),
messages: [{ role: "user", content: `<anfrage>\n${input.inquiry}\n</anfrage>` }],
})
stream.on("text", (text) => send({ type: "text", text }))
const message = await stream.finalMessage()
send(message.stop_reason === "refusal" ? { type: "error", message: "Dazu kann ich keine Antwort schreiben." } : { type: "done" })
} catch (error) {
console.error("AI email failed", error)
send({ type: "error", message: aiErrorMessage(error) })
}
controller.close()
},
})
return ndjsonResponse(body)
}
try {
const result =
input.task === "quote"
? await structured(QuoteSchema, quoteSystem(ctx), `<anfrage>\n${input.inquiry}\n</anfrage>`)
: await structured(ContentSchema, contentSystem(ctx, input.channel, input.tone), `Thema: ${input.topic}`)
if (!result) return Response.json({ error: "Dazu konnte die KI nichts erstellen. Bitte die Eingabe umformulieren." }, { status: 422 })
return Response.json({ result })
} catch (error) {
console.error("AI generate failed", error)
return Response.json({ error: aiErrorMessage(error) }, { status: 502 })
}
}