- Schema: companyType, topics, salesScore, salesReason, offerPackage, approved, approvedAt, SearchHistory table - /api/search-history: GET (by mode) + POST (save query) - /api/ai-search: stadtwerke/industrie/custom prompts with history dedup - /api/enrich-leads: website scraping + GPT-4o-mini enrichment (fire-and-forget after each job) - /api/generate-email: personalized outreach via GPT-4o - Suche page: 3 mode tabs (Stadtwerke/Industrie/Freie Suche), Alle-Bundesländer queue button, AiSearchModal gets searchMode + history - Leadspeicher: Bewertung dots column, Paket badge column, Freigeben toggle button, email generator in SidePanel, approved-only export option - Leads API: approvedOnly + companyType filters, new fields in PATCH Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
109 lines
3.8 KiB
TypeScript
109 lines
3.8 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
||
import { prisma } from "@/lib/db";
|
||
|
||
const EMAIL_PROMPT_TEMPLATE = `Du schreibst eine professionelle Erstansprache-E-Mail für mein-solar.com an einen potenziellen Kunden.
|
||
|
||
mein-solar.com ist ein Full-Service EPC-Anbieter für Photovoltaik (5 kW bis 10+ MW), Batteriespeicher und Ladeinfrastruktur.
|
||
|
||
Firmendaten des Empfängers:
|
||
- Unternehmen: [companyName]
|
||
- Ansprechpartner: [contactName], [contactTitle]
|
||
- Unternehmenstyp: [companyType]
|
||
- Themen des Unternehmens: [topics]
|
||
- Empfohlenes Paket: [offerPackage]
|
||
|
||
Schreibe eine E-Mail auf Deutsch:
|
||
- Betreff: prägnant, bezieht sich auf konkretes Thema des Unternehmens
|
||
- Anrede: personalisiert wenn Name bekannt
|
||
- 2-3 kurze Absätze: Bezug auf das Unternehmen → Mehrwert von mein-solar → konkreter nächster Schritt (Erstgespräch)
|
||
- Ton: professionell, direkt, kein Marketing-Sprech
|
||
- Keine Buzzwords wie "innovativ" oder "nachhaltig"
|
||
- Signatur: [Vorname] von mein-solar.com
|
||
|
||
Antworte mit JSON: { "subject": "...", "body": "..." }`;
|
||
|
||
export async function POST(req: NextRequest) {
|
||
try {
|
||
const { leadId } = await req.json() as { leadId: string };
|
||
|
||
if (!leadId) {
|
||
return NextResponse.json({ error: "leadId fehlt" }, { status: 400 });
|
||
}
|
||
|
||
const apiKey = process.env.OPENROUTER_API_KEY;
|
||
if (!apiKey) {
|
||
return NextResponse.json({ error: "OpenRouter API Key nicht konfiguriert" }, { status: 500 });
|
||
}
|
||
|
||
const lead = await prisma.lead.findUnique({ where: { id: leadId } });
|
||
if (!lead) {
|
||
return NextResponse.json({ error: "Lead nicht gefunden" }, { status: 404 });
|
||
}
|
||
|
||
if (!lead.email) {
|
||
return NextResponse.json({ error: "Lead hat keine E-Mail-Adresse" }, { status: 400 });
|
||
}
|
||
|
||
let topicsText = "";
|
||
if (lead.topics) {
|
||
try {
|
||
const topicsArr = JSON.parse(lead.topics) as string[];
|
||
topicsText = topicsArr.join(", ");
|
||
} catch {
|
||
topicsText = lead.topics;
|
||
}
|
||
}
|
||
|
||
const prompt = EMAIL_PROMPT_TEMPLATE
|
||
.replace("[companyName]", lead.companyName || "Unbekannt")
|
||
.replace("[contactName]", lead.contactName || "–")
|
||
.replace("[contactTitle]", lead.contactTitle || "–")
|
||
.replace("[companyType]", lead.companyType || "–")
|
||
.replace("[topics]", topicsText || "–")
|
||
.replace("[offerPackage]", lead.offerPackage || "–");
|
||
|
||
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
||
method: "POST",
|
||
headers: {
|
||
"Authorization": `Bearer ${apiKey}`,
|
||
"Content-Type": "application/json",
|
||
"HTTP-Referer": "https://mein-solar.com",
|
||
"X-Title": "MeinSolar Leads",
|
||
},
|
||
body: JSON.stringify({
|
||
model: "openai/gpt-4o",
|
||
temperature: 0.5,
|
||
max_tokens: 600,
|
||
messages: [
|
||
{ role: "user", content: prompt },
|
||
],
|
||
}),
|
||
});
|
||
|
||
if (!res.ok) {
|
||
const err = await res.text();
|
||
console.error("[generate-email] OpenRouter error:", err);
|
||
return NextResponse.json({ error: "KI-Anfrage fehlgeschlagen" }, { status: 500 });
|
||
}
|
||
|
||
const data = await res.json() as { choices: Array<{ message: { content: string } }> };
|
||
const raw = data.choices[0]?.message?.content?.trim() ?? "";
|
||
|
||
let email: { subject: string; body: string };
|
||
try {
|
||
email = JSON.parse(raw) as { subject: string; body: string };
|
||
} catch {
|
||
const match = raw.match(/\{[\s\S]*\}/);
|
||
if (!match) {
|
||
return NextResponse.json({ error: "KI-Antwort konnte nicht geparst werden" }, { status: 500 });
|
||
}
|
||
email = JSON.parse(match[0]) as { subject: string; body: string };
|
||
}
|
||
|
||
return NextResponse.json({ subject: email.subject, body: email.body });
|
||
} catch (err) {
|
||
console.error("POST /api/generate-email error:", err);
|
||
return NextResponse.json({ error: "E-Mail-Generierung fehlgeschlagen" }, { status: 500 });
|
||
}
|
||
}
|