Files
lead-scraper/app/api/ai-search/route.ts
Timo Uttenweiler 0f5d18dac7 Add KI-Suche via OpenRouter GPT-4o-mini
- /api/ai-search: sends user description to GPT-4o-mini, returns 2-4
  structured query/region pairs as JSON
- AiSearchModal: textarea, generates previews, user selects queries to run
- KI-Suche button in hero section of /suche page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 14:00:54 +02:00

91 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { NextRequest, NextResponse } from "next/server";
const SYSTEM_PROMPT = `Du bist ein Experte für B2B-Lead-Generierung im deutschsprachigen Raum.
Deine Aufgabe: Wandle die Beschreibung des Nutzers in 24 konkrete Google-Suchanfragen um, die lokale Unternehmen und Dienstleister finden.
Regeln:
- Jede Query besteht aus einem kurzen Suchbegriff (Branche/Tätigkeit) und einer Region (Bundesland, Stadt oder Gebiet)
- Suchbegriffe sind konkret, auf Deutsch, wie ein Mensch bei Google suchen würde
- Keine Firmennamen, keine Websites, keine Social-Media-Begriffe
- Wenn der Nutzer keine Region nennt, verteile auf sinnvolle deutsche Regionen (z.B. Bayern, NRW, Baden-Württemberg)
- Wenn der Nutzer eine spezifische Region nennt, halte dich daran — teile ggf. in Städte auf für mehr Abdeckung
- count immer 50 außer der Nutzer nennt explizit eine Zahl (dann zwischen 25 und 100)
- Maximal 4 Queries zurückgeben
- Keine Erklärungen, nur JSON
Antworte ausschließlich mit einem JSON-Array, kein Markdown, kein Text drumherum:
[
{ "query": "Dachdecker", "region": "Bayern", "count": 50 },
{ "query": "Dachdecker", "region": "NRW", "count": 50 }
]`;
export async function POST(req: NextRequest) {
try {
const { description } = await req.json() as { description: string };
if (!description?.trim()) {
return NextResponse.json({ error: "Beschreibung fehlt" }, { status: 400 });
}
const apiKey = process.env.OPENROUTER_API_KEY;
if (!apiKey) {
return NextResponse.json({ error: "OpenRouter API Key nicht konfiguriert" }, { status: 500 });
}
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://onvyaleads.app",
"X-Title": "OnyvaLeads",
},
body: JSON.stringify({
model: "openai/gpt-4o-mini",
temperature: 0.4,
max_tokens: 512,
messages: [
{ role: "system", content: SYSTEM_PROMPT },
{ role: "user", content: description.trim() },
],
}),
});
if (!res.ok) {
const err = await res.text();
console.error("[ai-search] 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 queries: Array<{ query: string; region: string; count: number }>;
try {
queries = JSON.parse(raw);
} catch {
const match = raw.match(/\[[\s\S]*\]/);
if (!match) throw new Error("Kein JSON in Antwort");
queries = JSON.parse(match[0]);
}
queries = queries
.filter(q => typeof q.query === "string" && q.query.trim())
.slice(0, 4)
.map(q => ({
query: q.query.trim(),
region: (q.region ?? "").trim(),
count: Math.min(Math.max(Number(q.count) || 50, 25), 100),
}));
return NextResponse.json({ queries });
} catch (err) {
console.error("[ai-search] error:", err);
return NextResponse.json({ error: "Fehler bei der KI-Anfrage" }, { status: 500 });
}
}