Files
lead-scraper/app/api/stadtwerke-cities/route.ts
Timo Uttenweiler 7db914084e Stitch redesign, Energieversorger-Kampagne, UI improvements
- Apply Stitch design system to leadspeicher, suche, TopBar, globals.css
- Add Energieversorger queue campaign (Netzbetreiber, Fernwärme, Industriepark)
  with BW + Bayern priority, tracks usage per term+location combo
- Remove TopBar right-side actions (Leads finden, bell, settings)
- Remove mode tabs from manual search, rename KI button
- Fix Google Fonts @import order (move to <link> in layout.tsx)
- Add cursor-pointer globally via globals.css
- Responsive fixes for campaign buttons and KI button
- Fix .dockerignore to exclude .env from image build
- Add stadtwerke-cities API + city data (50 cities per Bundesland)

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

85 lines
3.2 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/db";
import { ALL_CITIES } from "@/lib/data/stadtwerke-cities";
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY || "";
const OPENROUTER_BASE = "https://openrouter.ai/api/v1";
export async function GET(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const count = Math.min(parseInt(searchParams.get("count") || "20", 10), 100);
// Get all regions already used for stadtwerke mode
const used = await prisma.searchHistory.findMany({
where: { searchMode: "stadtwerke" },
select: { region: true },
});
const usedRegions = new Set(used.map(u => u.region.toLowerCase().trim()));
// Filter static list — cities not yet searched
const remaining = ALL_CITIES.filter(city => !usedRegions.has(city.toLowerCase().trim()));
if (remaining.length === 0) {
// All static cities exhausted — ask AI for new suggestions
const newCities = await generateNewCities(usedRegions);
return NextResponse.json({ cities: newCities.slice(0, count), exhausted: true, totalRemaining: newCities.length });
}
return NextResponse.json({
cities: remaining.slice(0, count),
exhausted: false,
totalRemaining: remaining.length,
});
} catch (err) {
console.error("GET /api/stadtwerke-cities error:", err);
return NextResponse.json({ error: "Failed to fetch cities" }, { status: 500 });
}
}
async function generateNewCities(usedRegions: Set<string>): Promise<string[]> {
if (!OPENROUTER_API_KEY) return [];
const usedList = Array.from(usedRegions)
.filter(r => r.length > 0)
.slice(0, 150)
.join(", ");
try {
const res = await fetch(`${OPENROUTER_BASE}/chat/completions`, {
method: "POST",
headers: {
"Authorization": `Bearer ${OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://mein-solar.de",
},
body: JSON.stringify({
model: "openai/gpt-4o-mini",
messages: [
{
role: "system",
content:
"Du bist ein Experte für kommunale Energieversorger in Deutschland. Antworte ausschließlich mit einem gültigen JSON-Array aus Städtenamen, ohne Erklärungen.",
},
{
role: "user",
content: `Generiere 40 deutsche Städte und Gemeinden, in denen es lokale Stadtwerke oder kommunale Energieversorger gibt. Keine dieser Städte darf in der folgenden Liste enthalten sein:\n${usedList}\n\nGib nur ein JSON-Array zurück, z.B.: ["Landsberg am Lech", "Bühl", "Leutkirch"]`,
},
],
max_tokens: 600,
temperature: 0.7,
}),
});
const data = await res.json() as { choices?: Array<{ message?: { content?: string } }> };
const content = data.choices?.[0]?.message?.content?.trim() || "[]";
// Strip markdown code blocks if present
const cleaned = content.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, "").trim();
const cities = JSON.parse(cleaned) as unknown;
return Array.isArray(cities) ? (cities as string[]).filter(c => typeof c === "string") : [];
} catch {
return [];
}
}