diff --git a/app/api/ai-search/route.ts b/app/api/ai-search/route.ts
new file mode 100644
index 0000000..1d08d56
--- /dev/null
+++ b/app/api/ai-search/route.ts
@@ -0,0 +1,90 @@
+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 2–4 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 });
+ }
+}
diff --git a/app/suche/page.tsx b/app/suche/page.tsx
index e5b63a2..448a19f 100644
--- a/app/suche/page.tsx
+++ b/app/suche/page.tsx
@@ -5,6 +5,7 @@ import Link from "next/link";
import { toast } from "sonner";
import { SearchCard } from "@/components/search/SearchCard";
import { LoadingCard, type LeadResult } from "@/components/search/LoadingCard";
+import { AiSearchModal } from "@/components/search/AiSearchModal";
export default function SuchePage() {
const [query, setQuery] = useState("");
@@ -18,6 +19,7 @@ export default function SuchePage() {
const [deleting, setDeleting] = useState(false);
const [onlyNew, setOnlyNew] = useState(false);
const [saveOnlyNew, setSaveOnlyNew] = useState(false);
+ const [aiOpen, setAiOpen] = useState(false);
function handleChange(field: "query" | "region" | "count", value: string | number) {
if (field === "query") setQuery(value as string);
@@ -130,12 +132,33 @@ export default function SuchePage() {
Lead-Suche
-
- Leads finden
-
-
- Suchbegriff eingeben — wir finden passende Unternehmen mit Kontaktdaten.
-
+
+
+
+ Leads finden
+
+
+ Suchbegriff eingeben — wir finden passende Unternehmen mit Kontaktdaten.
+
+
+
+
@@ -162,6 +185,41 @@ export default function SuchePage() {
/>
)}
+ {/* AI Modal */}
+ {aiOpen && (
+ {
+ setAiOpen(false);
+ if (!queries.length) return;
+ // Fill first query into the search fields and submit
+ const first = queries[0];
+ setQuery(first.query);
+ setRegion(first.region);
+ setCount(first.count);
+ setLoading(true);
+ setJobId(null);
+ setLeads([]);
+ setSearchDone(false);
+ setSelected(new Set());
+ fetch("/api/search", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ query: first.query, region: first.region, count: first.count }),
+ })
+ .then(r => r.json())
+ .then((d: { jobId?: string; error?: string }) => {
+ if (d.jobId) setJobId(d.jobId);
+ else throw new Error(d.error);
+ })
+ .catch(err => {
+ toast.error(err instanceof Error ? err.message : "Fehler");
+ setLoading(false);
+ });
+ }}
+ onClose={() => setAiOpen(false)}
+ />
+ )}
+
{/* Results */}
{searchDone && leads.length > 0 && (() => {
const newCount = leads.filter(l => l.isNew).length;
diff --git a/components/search/AiSearchModal.tsx b/components/search/AiSearchModal.tsx
new file mode 100644
index 0000000..32076a7
--- /dev/null
+++ b/components/search/AiSearchModal.tsx
@@ -0,0 +1,223 @@
+"use client";
+
+import { useState } from "react";
+
+interface Query {
+ query: string;
+ region: string;
+ count: number;
+}
+
+interface AiSearchModalProps {
+ onStart: (queries: Query[]) => void;
+ onClose: () => void;
+}
+
+export function AiSearchModal({ onStart, onClose }: AiSearchModalProps) {
+ const [description, setDescription] = useState("");
+ const [loading, setLoading] = useState(false);
+ const [queries, setQueries] = useState([]);
+ const [selected, setSelected] = useState>(new Set());
+ const [error, setError] = useState("");
+
+ async function generate() {
+ if (!description.trim() || loading) return;
+ setLoading(true);
+ setError("");
+ setQueries([]);
+ try {
+ const res = await fetch("/api/ai-search", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ description }),
+ });
+ const data = await res.json() as { queries?: Query[]; error?: string };
+ if (!res.ok || !data.queries) throw new Error(data.error || "Fehler");
+ setQueries(data.queries);
+ setSelected(new Set(data.queries.map((_, i) => i)));
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Unbekannter Fehler");
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ function toggle(i: number) {
+ setSelected(prev => {
+ const n = new Set(prev);
+ if (n.has(i)) n.delete(i); else n.add(i);
+ return n;
+ });
+ }
+
+ function handleStart() {
+ const chosen = queries.filter((_, i) => selected.has(i));
+ if (!chosen.length) return;
+ onStart(chosen);
+ }
+
+ return (
+
+
e.stopPropagation()}
+ >
+ {/* Header */}
+
+
+
+ ✨
+
+ KI-gestützte Suche
+
+
+
+ Beschreibe deine Zielgruppe — die KI generiert passende Suchanfragen.
+
+
+
+
+
+ {/* Textarea */}
+
+
+ {/* Generate button */}
+ {queries.length === 0 && (
+
+ )}
+
+ {/* Error */}
+ {error && (
+
+ {error}
+
+ )}
+
+ {/* Generated queries */}
+ {queries.length > 0 && (
+
+
+ Generierte Suchanfragen — wähle aus was starten soll
+
+ {queries.map((q, i) => (
+
+ ))}
+
+
+
+
+
+
+ )}
+
+
+
+
+ );
+}