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>
This commit is contained in:
84
app/api/stadtwerke-cities/route.ts
Normal file
84
app/api/stadtwerke-cities/route.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user