mein-solar: full feature set
- 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>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
|
||||
const SYSTEM_PROMPT = `Du bist ein spezialisierter Assistent für B2B-Lead-Generierung im deutschsprachigen Raum.
|
||||
const SYSTEM_PROMPT_DEFAULT = `Du bist ein spezialisierter Assistent für B2B-Lead-Generierung im deutschsprachigen Raum.
|
||||
|
||||
## Was passiert mit deiner Ausgabe
|
||||
|
||||
@@ -36,45 +36,110 @@ Nutze mehrere Queries wenn:
|
||||
|
||||
Maximal 4 Queries. Keine Duplikate (gleiche query + gleiche region).
|
||||
|
||||
## Beispiele
|
||||
|
||||
Eingabe: "Dachdecker in Bayern"
|
||||
Ausgabe:
|
||||
[
|
||||
{ "query": "Dachdecker", "region": "München" },
|
||||
{ "query": "Dachdecker", "region": "Nürnberg" }
|
||||
]
|
||||
|
||||
Eingabe: "Steuerberater in ganz Deutschland"
|
||||
Ausgabe:
|
||||
[
|
||||
{ "query": "Steuerberater", "region": "Bayern" },
|
||||
{ "query": "Steuerberater", "region": "NRW" },
|
||||
{ "query": "Steuerberater", "region": "Hamburg" },
|
||||
{ "query": "Steuerberater", "region": "Berlin" }
|
||||
]
|
||||
|
||||
Eingabe: "Solaranlagen Installateure und Elektriker in Stuttgart"
|
||||
Ausgabe:
|
||||
[
|
||||
{ "query": "Solaranlage", "region": "Stuttgart" },
|
||||
{ "query": "Elektriker", "region": "Stuttgart" }
|
||||
]
|
||||
|
||||
Eingabe: "Metallbaubetriebe in Süddeutschland"
|
||||
Ausgabe:
|
||||
[
|
||||
{ "query": "Metallbau", "region": "Bayern" },
|
||||
{ "query": "Metallbau", "region": "Baden-Württemberg" }
|
||||
]
|
||||
|
||||
## Ausgabeformat
|
||||
|
||||
Antworte ausschließlich mit einem JSON-Array. Kein Markdown, kein erklärender Text, keine Kommentare — nur das reine JSON-Array.`;
|
||||
|
||||
const SYSTEM_PROMPT_STADTWERKE = `Du bist ein spezialisierter Assistent für B2B-Lead-Generierung im deutschen Energiesektor.
|
||||
|
||||
## Kontext
|
||||
Das Tool sucht deutsche Stadtwerke und kommunale Energieversorger als potenzielle Kunden für ein Solarunternehmen (mein-solar.com). Stadtwerke betreiben lokale Energie-, Wasser- und Verkehrsinfrastruktur und sind starke Abnehmer für Photovoltaik-Großanlagen, Batteriespeicher und Ladeinfrastruktur.
|
||||
|
||||
## Was passiert mit deiner Ausgabe
|
||||
Die Queries werden an die Google Maps Places API und Google SERP übergeben. Gesucht werden Stadtwerke-Websites, aus denen dann Entscheidungsträger-E-Mails extrahiert werden.
|
||||
|
||||
## Deine Aufgabe
|
||||
Generiere 2–4 Suchanfragen die Stadtwerke, Gemeindewerke und kommunale Energieversorger in Deutschland finden. Decke verschiedene Bundesländer oder Regionen ab.
|
||||
|
||||
## Suchbegriffe die funktionieren
|
||||
- "Stadtwerke" — findet kommunale Versorger
|
||||
- "Gemeindewerk" — kleinere Gemeindebetriebe
|
||||
- "Stadtwerk" — Variante
|
||||
- "kommunaler Energieversorger" — für SERP
|
||||
- Niemals: LinkedIn, XING, Verzeichnisse, Social Media
|
||||
|
||||
## Priorisierung
|
||||
"Stadtwerke" schlägt immer "Gemeindewerk". Große Bundesländer (NRW, Bayern, BW) zuerst wenn keine Region angegeben.
|
||||
|
||||
## Bereits verwendete Queries (diese NICHT nochmal verwenden — generiere neue Regionen/Varianten):
|
||||
[HISTORY_PLACEHOLDER]
|
||||
|
||||
## Beispiel
|
||||
Eingabe: "Stadtwerke in Norddeutschland"
|
||||
Ausgabe:
|
||||
[
|
||||
{ "query": "Stadtwerke", "region": "Hamburg" },
|
||||
{ "query": "Stadtwerke", "region": "Schleswig-Holstein" },
|
||||
{ "query": "Gemeindewerk", "region": "Niedersachsen" }
|
||||
]
|
||||
|
||||
## Ausgabeformat
|
||||
Nur reines JSON-Array, kein Markdown, keine Erklärungen.`;
|
||||
|
||||
const SYSTEM_PROMPT_INDUSTRIE = `Du bist ein spezialisierter Assistent für B2B-Lead-Generierung im deutschen Industriesektor.
|
||||
|
||||
## Kontext
|
||||
Das Tool sucht deutsche Industriebetriebe als Kunden für ein Solarunternehmen (mein-solar.com), das PV-Großanlagen (5 kW bis 10+ MW), Batteriespeicher und Ladeinfrastruktur anbietet. Zielkunden sind Firmen mit hohem Energieverbrauch, großen Dachflächen oder Freiflächen, und Interesse an Kostensenkung durch Eigenerzeugung.
|
||||
|
||||
## Was passiert mit deiner Ausgabe
|
||||
Die Queries werden an Google Maps und SERP übergeben um Unternehmenswebsites zu finden, aus denen Entscheidungsträger-E-Mails extrahiert werden.
|
||||
|
||||
## Deine Aufgabe
|
||||
Generiere 2–4 Suchanfragen die energieintensive Industriebetriebe, Produktionsunternehmen, Logistiker oder Gewerbeparks finden — ideale Kandidaten für Solar-Großanlagen.
|
||||
|
||||
## Gut geeignete Branchen
|
||||
Produktion/Fertigung, Logistik/Lager, Lebensmittelindustrie, Metallverarbeitung, Automobilzulieferer, Landwirtschaft (Agri-PV), Gewerbeparks, Einzelhandel (große Flächen)
|
||||
|
||||
## Suchbegriffe die funktionieren
|
||||
Kurze branchenbezogene Begriffe die bei Google Maps echte Betriebe finden. Keine Adjektive.
|
||||
|
||||
## Priorisierung
|
||||
Wähle immer den geläufigsten Begriff (z.B. "Logistikzentrum" vor "Lagerhaus"). Branchen mit hohem Energieverbrauch und Dachfläche zuerst.
|
||||
|
||||
## Bereits verwendete Queries (diese NICHT nochmal verwenden):
|
||||
[HISTORY_PLACEHOLDER]
|
||||
|
||||
## Beispiel
|
||||
Eingabe: "Industriebetriebe in Bayern mit großen Dachflächen"
|
||||
Ausgabe:
|
||||
[
|
||||
{ "query": "Produktionsbetrieb", "region": "München" },
|
||||
{ "query": "Logistikzentrum", "region": "Augsburg" },
|
||||
{ "query": "Lebensmittelproduktion", "region": "Bayern" }
|
||||
]
|
||||
|
||||
## Ausgabeformat
|
||||
Nur reines JSON-Array, kein Markdown, keine Erklärungen.`;
|
||||
|
||||
function buildSystemPrompt(
|
||||
searchMode: string,
|
||||
history: Array<{ query: string; region: string }>
|
||||
): string {
|
||||
const historyText =
|
||||
history.length > 0
|
||||
? history
|
||||
.slice(0, 20)
|
||||
.map(h => `- "${h.query}" in "${h.region}"`)
|
||||
.join("\n")
|
||||
: "Keine bisherigen Suchen.";
|
||||
|
||||
if (searchMode === "stadtwerke") {
|
||||
return SYSTEM_PROMPT_STADTWERKE.replace("[HISTORY_PLACEHOLDER]", historyText);
|
||||
}
|
||||
if (searchMode === "industrie") {
|
||||
return SYSTEM_PROMPT_INDUSTRIE.replace("[HISTORY_PLACEHOLDER]", historyText);
|
||||
}
|
||||
return SYSTEM_PROMPT_DEFAULT;
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { description } = await req.json() as { description: string };
|
||||
const body = await req.json() as {
|
||||
description: string;
|
||||
searchMode?: string;
|
||||
history?: Array<{ query: string; region: string }>;
|
||||
};
|
||||
const { description, searchMode = "custom", history = [] } = body;
|
||||
|
||||
if (!description?.trim()) {
|
||||
return NextResponse.json({ error: "Beschreibung fehlt" }, { status: 400 });
|
||||
@@ -85,20 +150,22 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: "OpenRouter API Key nicht konfiguriert" }, { status: 500 });
|
||||
}
|
||||
|
||||
const systemPrompt = buildSystemPrompt(searchMode, history);
|
||||
|
||||
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",
|
||||
"HTTP-Referer": "https://mein-solar.com",
|
||||
"X-Title": "MeinSolar Leads",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "openai/gpt-4o-mini",
|
||||
temperature: 0.4,
|
||||
max_tokens: 512,
|
||||
messages: [
|
||||
{ role: "system", content: SYSTEM_PROMPT },
|
||||
{ role: "system", content: systemPrompt },
|
||||
{ role: "user", content: description.trim() },
|
||||
],
|
||||
}),
|
||||
@@ -134,6 +201,18 @@ export async function POST(req: NextRequest) {
|
||||
count: 50,
|
||||
}));
|
||||
|
||||
// Save to SearchHistory (fire and forget)
|
||||
if (searchMode && searchMode !== "custom" && queries.length > 0) {
|
||||
const baseUrl = req.url.replace(/\/api\/ai-search.*/, "");
|
||||
for (const q of queries) {
|
||||
fetch(`${baseUrl}/api/search-history`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ query: q.query, region: q.region, searchMode }),
|
||||
}).catch(err => console.error("[ai-search] search-history save error:", err));
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ queries });
|
||||
} catch (err) {
|
||||
console.error("[ai-search] error:", err);
|
||||
|
||||
Reference in New Issue
Block a user