mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 19:51:17 +00:00
68 lines
3.4 KiB
TypeScript
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 })
|
|
}
|
|
}
|