diff --git a/components/GoogleAdsGenerator.tsx b/components/GoogleAdsGenerator.tsx index c3735c4..1e7c5b1 100644 --- a/components/GoogleAdsGenerator.tsx +++ b/components/GoogleAdsGenerator.tsx @@ -1,39 +1,15 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; -import { Badge } from "@/components/ui/badge"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import useStore from "@/lib/store"; import modelAdapter from "@/lib/services/adapter-instance"; -import { - Search, - Target, - DollarSign, - Globe, - Calendar, - Zap, - BarChart3, - Layers, - ShieldCheck, - Loader2, - Copy, - Download, - ExternalLink, - MousePointer2, - CheckCircle2, - AlertCircle, - Megaphone, - Briefcase, - TrendingUp, - X, - Plus -} from "lucide-react"; +import { Megaphone, Copy, Loader2, CheckCircle2, Settings, Plus, X, ChevronDown, ChevronUp } from "lucide-react"; import { cn } from "@/lib/utils"; -import { GoogleAdsResult, GoogleAdsKeyword, GoogleAdCopy, GoogleAdsCampaign } from "@/types"; +import { GoogleAdsResult } from "@/types"; export default function GoogleAdsGenerator() { const { @@ -58,19 +34,35 @@ export default function GoogleAdsGenerator() { const [targetAudience, setTargetAudience] = useState(""); const [budgetMin, setBudgetMin] = useState("500"); const [budgetMax, setBudgetMax] = useState("2000"); - const [currency, setCurrency] = useState("USD"); const [duration, setDuration] = useState("30 days"); const [industry, setIndustry] = useState(""); - const [activeTab, setActiveTab] = useState("input"); - const [copied, setCopied] = useState(null); + const [copied, setCopied] = useState(false); + const [expandedSections, setExpandedSections] = useState(["keywords"]); const selectedModel = selectedModels[selectedProvider]; const models = availableModels[selectedProvider] || modelAdapter.getAvailableModels(selectedProvider); + const toggleSection = (section: string) => { + setExpandedSections((prev) => + prev.includes(section) ? prev.filter((s) => s !== section) : [...prev, section] + ); + }; + useEffect(() => { if (typeof window !== "undefined") { loadAvailableModels(); + const saved = localStorage.getItem("promptarch-api-keys"); + if (saved) { + try { + const keys = JSON.parse(saved); + if (keys.qwen) modelAdapter.updateQwenApiKey(keys.qwen); + if (keys.ollama) modelAdapter.updateOllamaApiKey(keys.ollama); + if (keys.zai) modelAdapter.updateZaiApiKey(keys.zai); + } catch (e) { + console.error("Failed to load API keys:", e); + } + } } }, [selectedProvider]); @@ -99,24 +91,11 @@ export default function GoogleAdsGenerator() { setProducts(newProducts); }; - const validateUrl = (url: string) => { - try { - new URL(url.startsWith("http") ? url : `https://${url}`); - return true; - } catch (e) { - return false; - } - }; - const handleGenerate = async () => { if (!websiteUrl.trim()) { setError("Please enter a website URL"); return; } - if (!validateUrl(websiteUrl)) { - setError("Please enter a valid URL"); - return; - } const filteredProducts = products.filter(p => p.trim() !== ""); if (filteredProducts.length === 0) { setError("Please add at least one product or service"); @@ -133,49 +112,42 @@ export default function GoogleAdsGenerator() { setProcessing(true); setError(null); - setActiveTab("input"); + + console.log("[GoogleAdsGenerator] Starting generation...", { selectedProvider, selectedModel }); try { const result = await modelAdapter.generateGoogleAds(websiteUrl, { productsServices: filteredProducts, targetAudience, - budgetRange: { min: parseInt(budgetMin), max: parseInt(budgetMax), currency }, + budgetRange: { min: parseInt(budgetMin), max: parseInt(budgetMax), currency: "USD" }, campaignDuration: duration, industry, language: "English" }, selectedProvider, selectedModel); + console.log("[GoogleAdsGenerator] Generation result:", result); + if (result.success && result.data) { try { - // Optimized parsing helper + // Robust JSON extraction const extractJson = (text: string) => { try { - // 1. Direct parse return JSON.parse(text); } catch (e) { - // 2. Extract from markdown code blocks const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/i) || text.match(/```\s*([\s\S]*?)\s*```/i); - if (jsonMatch && jsonMatch[1]) { try { return JSON.parse(jsonMatch[1].trim()); - } catch (e2) { - console.error("Failed to parse extracted JSON block", e2); - } + } catch (e2) { /* ignore */ } } - - // 3. Last resort: extract anything between the first { and last } const braceMatch = text.match(/(\{[\s\S]*\})/); if (braceMatch) { try { return JSON.parse(braceMatch[0].trim()); - } catch (e3) { - console.error("Failed to parse content between braces", e3); - } + } catch (e3) { /* ignore */ } } - - throw new Error("Invalid format: AI response did not contain valid JSON"); + throw new Error("Could not parse JSON from response"); } }; @@ -191,722 +163,371 @@ export default function GoogleAdsGenerator() { rawContent: rawData }; setGoogleAdsResult(adsResult); - setActiveTab("keywords"); + setExpandedSections(["keywords"]); } catch (e) { console.error("Failed to parse ads data:", e); - setError("Failed to parse the generated ads content. Please try again or switch AI providers."); + setError("Failed to parse the generated ads content. Please try again."); } } else { + console.error("[GoogleAdsGenerator] Generation failed:", result.error); setError(result.error || "Failed to generate Google Ads campaign"); } } catch (err) { - console.error("Ads Generation error:", err); + console.error("[GoogleAdsGenerator] Generation error:", err); setError(err instanceof Error ? err.message : "An error occurred"); } finally { setProcessing(false); } }; - const handleCopy = async (text: string, id: string) => { - await navigator.clipboard.writeText(text); - setCopied(id); - setTimeout(() => setCopied(null), 2000); + const handleCopy = async () => { + if (googleAdsResult?.rawContent) { + await navigator.clipboard.writeText(googleAdsResult.rawContent); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } }; - const exportToCsv = () => { - if (!googleAdsResult) return; + const sections = [ + { id: "keywords", title: "Keywords Research" }, + { id: "adcopies", title: "Ad Copy Variations" }, + { id: "campaigns", title: "Campaign Structure" }, + { id: "implementation", title: "Implementation Guide" }, + ]; - let csvContent = "data:text/csv;charset=utf-8,"; - csvContent += "Type,Category,Headline/Keyword,Description/Value,Details\n"; + const renderSectionContent = (sectionId: string) => { + if (!googleAdsResult) return null; - // Keywords - googleAdsResult.keywords.primary.forEach(k => { - csvContent += `Keyword,Primary,"${k.keyword}","${k.searchVolume || ''}","${k.competition || ''}"\n`; - }); - googleAdsResult.keywords.longTail.forEach(k => { - csvContent += `Keyword,Long-Tail,"${k.keyword}","${k.searchVolume || ''}","${k.competition || ''}"\n`; - }); - googleAdsResult.keywords.negative.forEach(k => { - csvContent += `Keyword,Negative,"${k.keyword}","",""\n`; - }); - - // Ads - googleAdsResult.adCopies.forEach((ad, i) => { - ad.headlines.forEach((h, j) => { - csvContent += `Ad Copy,Headline ${j + 1},"${h}","",""\n`; - }); - ad.descriptions.forEach((d, j) => { - csvContent += `Ad Copy,Description ${j + 1},"${d}","",""\n`; - }); - }); - - const encodedUri = encodeURI(csvContent); - const link = document.createElement("a"); - link.setAttribute("href", encodedUri); - link.setAttribute("download", `google_ads_${googleAdsResult.id}.csv`); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); + switch (sectionId) { + case "keywords": + return ( +
+ {googleAdsResult.keywords?.primary?.length > 0 && ( +
+

Primary Keywords

+
+ {googleAdsResult.keywords.primary.map((k, i) => ( + + {k.keyword} {k.cpc && ({k.cpc})} + + ))} +
+
+ )} + {googleAdsResult.keywords?.longTail?.length > 0 && ( +
+

Long-Tail Keywords

+
+ {googleAdsResult.keywords.longTail.map((k, i) => ( + + {k.keyword} + + ))} +
+
+ )} + {googleAdsResult.keywords?.negative?.length > 0 && ( +
+

Negative Keywords

+
+ {googleAdsResult.keywords.negative.map((k, i) => ( + + {k.keyword} + + ))} +
+
+ )} +
+ ); + case "adcopies": + return ( +
+ {googleAdsResult.adCopies?.map((ad, i) => ( +
+
Ad Variation {i + 1}
+
+ {ad.headlines?.map((h, j) => ( +
{h}
+ ))} +
+
+ {ad.descriptions?.map((d, j) => ( +

{d}

+ ))} +
+
+ ))} +
+ ); + case "campaigns": + return ( +
+ {googleAdsResult.campaigns?.map((camp, i) => ( +
+
+
+
{camp.name}
+
{camp.type}
+
+ {camp.budget && ( +
+
${camp.budget.monthly}/mo
+
${camp.budget.daily}/day
+
+ )} +
+ {camp.adGroups?.length > 0 && ( +
+
Ad Groups
+
+ {camp.adGroups.map((g, j) => ( + {g.name} + ))} +
+
+ )} +
+ ))} +
+ ); + case "implementation": + return ( +
+ {googleAdsResult.implementation?.setupSteps?.length > 0 && ( +
+

Setup Steps

+
    + {googleAdsResult.implementation.setupSteps.map((step, i) => ( +
  1. {step}
  2. + ))} +
+
+ )} + {googleAdsResult.implementation?.qualityScoreTips?.length > 0 && ( +
+

Quality Score Tips

+
    + {googleAdsResult.implementation.qualityScoreTips.map((tip, i) => ( +
  • {tip}
  • + ))} +
+
+ )} +
+ ); + default: + return
{googleAdsResult.rawContent}
; + } }; return ( -
-
-
-

- - Google Ads Strategist - Premium AI -

-

- Convert concepts into high-ROI Google Ads campaigns with precision-engineered keywords and copy. -

-
- - {googleAdsResult && ( -
- - -
- )} -
- -
- {/* Input Panel */} -
- -
- - - - Campaign Inputs - - - Configure your primary triggers and constraints. - - - -
- - setWebsiteUrl(e.target.value)} - className="bg-white/50 border-blue-100 focus:border-blue-500 focus-visible:ring-blue-500 h-11 text-base shadow-sm" - /> -
- -
- -
- {products.map((product, index) => ( -
- updateProduct(index, e.target.value)} - className="bg-muted/30 focus-visible:ring-blue-500" - /> - {products.length > 1 && ( - - )} -
- ))} - -
-
- -
-
-
- -
-
-
- $ - setBudgetMin(e.target.value)} - className="pl-7 bg-white h-9 text-sm" - /> -
- to -
- $ - setBudgetMax(e.target.value)} - className="pl-7 bg-white h-9 text-sm" - /> -
-
-
-
- -
-
- - setIndustry(e.target.value)} - className="h-9 text-sm" - /> -
-
- - setDuration(e.target.value)} - className="h-9 text-sm" - /> -
-
- -
- - setTargetAudience(e.target.value)} - className="h-9 text-sm" - /> -
- -
-
- {(["qwen", "ollama", "zai"] as const).map((provider) => ( - - ))} -
- - - - {error && ( -
- - {error} -
- )} - +
+ + + + + Google Ads Generator + + + Generate keywords, ad copy, and campaign structure for Google Ads + + + +
+ +
+ {(["qwen", "ollama", "zai"] as const).map((provider) => ( -
- - - -
-

- - Quality Assurance -

-
    - {[ - "Character limit enforcement", - "Keyword relevance matching >85%", - "Google Ads policy compliance", - "Mobile optimization focus" - ].map((item, i) => ( -
  • - - {item} -
  • ))} -
+
-
- {/* Results Panel */} -
- {!googleAdsResult && !isProcessing && ( -
-
- -
-

Ready to Launch?

-

- Enter your website URL and products on the left to generate keyword research, ad copy, and a full campaign structure. -

-
- 15+ Years Domain Knowledge - Quality Score Optimization -
-
- )} +
+ + +
- {isProcessing && ( -
- {/* Decorative background blur */} -
+
+ + setWebsiteUrl(e.target.value)} + className="text-sm" + /> +
-
-
-
-
- -
+
+ +
+ {products.map((product, index) => ( +
+ updateProduct(index, e.target.value)} + className="text-sm" + /> + {products.length > 1 && ( + + )}
-
+ ))} + +
+
-

Strategizing Your Campaign...

-

- Scouring {websiteUrl} for conversion triggers and competitive edges. -

+
+
+ +
+ setBudgetMin(e.target.value)} + className="text-sm" + /> + - + setBudgetMax(e.target.value)} + className="text-sm" + /> +
+
+
+ + setIndustry(e.target.value)} + className="text-sm" + /> +
+
-
-
-
-
- {[ - { label: "Keywords", delay: "0s" }, - { label: "Ad Copy", delay: "0.2s" }, - { label: "Targeting", delay: "0.4s" }, - { label: "ROI Modeling", delay: "0.6s" } - ].map((item, i) => ( -
-
- -
- {item.label} -
- ))} -
+
+ +