"use client"; 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 useStore from "@/lib/store"; import modelAdapter from "@/lib/services/adapter-instance"; import { Megaphone, Copy, Loader2, CheckCircle2, Settings, Plus, X, ChevronDown, ChevronUp, Wand2, Target, TrendingUp, ShieldAlert, BarChart3, Users, Rocket, Download, FileSpreadsheet } from "lucide-react"; import { cn } from "@/lib/utils"; import { GoogleAdsResult } from "@/types"; import { downloadFile, generateGoogleAdsCSV, generateGoogleAdsHTML } from "@/lib/export-utils"; import { translations } from "@/lib/i18n/translations"; export default function GoogleAdsGenerator() { const { googleAdsResult, magicWandResult, selectedProvider, selectedModels, availableModels, apiKeys, isProcessing, error, language, setGoogleAdsResult, setMagicWandResult, setProcessing, setError, setAvailableModels, setSelectedModel, setSelectedProvider, } = useStore(); const t = translations[language].googleAds; const common = translations[language].common; // Input states const [websiteUrl, setWebsiteUrl] = useState(""); const [products, setProducts] = useState<{ name: string; url: string }[]>([{ name: "", url: "" }]); const [targetAudience, setTargetAudience] = useState(""); const [budgetMin, setBudgetMin] = useState("500"); const [budgetMax, setBudgetMax] = useState("2000"); const [duration, setDuration] = useState("30 days"); const [industry, setIndustry] = useState(""); const [specialInstructions, setSpecialInstructions] = useState(""); const [copied, setCopied] = useState(false); const [expandedSections, setExpandedSections] = useState(["keywords"]); const [isMagicThinking, setIsMagicThinking] = useState(false); const [isEnhancing, setIsEnhancing] = useState(false); const [progressMessage, setProgressMessage] = useState(""); const [progressIndex, setProgressIndex] = useState(0); const selectedModel = selectedModels[selectedProvider]; const models = availableModels[selectedProvider] || modelAdapter.getAvailableModels(selectedProvider); // Fun progress messages const progressMessages = t.progressMessages; 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]); // Cycle through progress messages while generating useEffect(() => { if (isProcessing || isMagicThinking) { setProgressMessage(progressMessages[0]); setProgressIndex(0); const interval = setInterval(() => { setProgressIndex(prev => { const nextIndex = (prev + 1) % progressMessages.length; setProgressMessage(progressMessages[nextIndex]); return nextIndex; }); }, 2500); return () => clearInterval(interval); } else { setProgressMessage(""); setProgressIndex(0); } }, [isProcessing, isMagicThinking, language]); const loadAvailableModels = async () => { const fallbackModels = modelAdapter.getAvailableModels(selectedProvider); setAvailableModels(selectedProvider, fallbackModels); try { const result = await modelAdapter.listModels(selectedProvider); if (result.success && result.data) { setAvailableModels(selectedProvider, result.data[selectedProvider] || fallbackModels); } } catch (error) { console.error("Failed to load models:", error); } }; const addProduct = () => setProducts([...products, { name: "", url: "" }]); const removeProduct = (index: number) => { const newProducts = products.filter((_, i) => i !== index); setProducts(newProducts.length ? newProducts : [{ name: "", url: "" }]); }; const updateProduct = (index: number, field: "name" | "url", value: string) => { const newProducts = [...products]; newProducts[index] = { ...newProducts[index], [field]: value }; setProducts(newProducts); }; const handleGenerate = async () => { if (!websiteUrl.trim()) { setError(t.errorWebsite); return; } const filteredProducts = products.filter(p => p.name.trim() !== ""); if (filteredProducts.length === 0) { setError(t.errorProducts); return; } const apiKey = apiKeys[selectedProvider]; const isQwenOAuth = selectedProvider === "qwen" && modelAdapter.hasQwenAuth(); if (!isQwenOAuth && (!apiKey || !apiKey.trim())) { setError(`${common.error}: ${common.configApiKey}`); return; } setProcessing(true); setError(null); setMagicWandResult(null); console.log("[GoogleAdsGenerator] Starting generation...", { selectedProvider, selectedModel }); try { // Convert products to strings with optional URLs for AI context const productStrings = filteredProducts.map(p => p.url ? `${p.name} (URL: ${p.url})` : p.name ); const result = await modelAdapter.generateGoogleAds(websiteUrl, { productsServices: productStrings, targetAudience, budgetRange: { min: parseInt(budgetMin), max: parseInt(budgetMax), currency: "USD" }, campaignDuration: duration, industry, competitors: [], language: language === "ru" ? "Russian" : language === "he" ? "Hebrew" : "English", specialInstructions: specialInstructions, }, selectedProvider, selectedModel); console.log("[GoogleAdsGenerator] Generation result:", result); if (result.success && result.data) { try { // Robust JSON extraction const extractJson = (text: string) => { try { return JSON.parse(text); } catch (e) { 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) { /* ignore */ } } const braceMatch = text.match(/(\{[\s\S]*\})/); if (braceMatch) { try { return JSON.parse(braceMatch[0].trim()); } catch (e3) { /* ignore */ } } throw new Error("Could not parse JSON from response"); } }; const rawData = typeof result.data === 'string' ? result.data : JSON.stringify(result.data); const parsedData = extractJson(rawData); const adsResult: GoogleAdsResult = { ...parsedData, id: Math.random().toString(36).substr(2, 9), websiteUrl, productsServices: filteredProducts, generatedAt: new Date(), rawContent: rawData }; setGoogleAdsResult(adsResult); setExpandedSections(["keywords"]); } catch (e) { console.error("Failed to parse ads data:", e); setError(t.errorParse || "Failed to parse the generated ads content. Please try again."); } } else { console.error("[GoogleAdsGenerator] Generation failed:", result.error); setError(result.error || t.errorGenerate); } } catch (err) { console.error("[GoogleAdsGenerator] Generation error:", err); setError(err instanceof Error ? err.message : t.errorGenerate); } finally { setProcessing(false); } }; const handleMagicWand = async () => { if (!websiteUrl.trim()) { setError(t.errorWebsite); return; } const firstProduct = products.find(p => p.name.trim() !== ""); if (!firstProduct) { setError(t.errorProducts); return; } const apiKey = apiKeys[selectedProvider]; const isQwenOAuth = selectedProvider === "qwen" && modelAdapter.hasQwenAuth(); if (!isQwenOAuth && (!apiKey || !apiKey.trim())) { setError(`${common.error}: ${common.configApiKey}`); return; } setIsMagicThinking(true); setError(null); setGoogleAdsResult(null); try { // Pass product with URL for enhanced AI research const productString = firstProduct.url ? `${firstProduct.name} (Product URL for research: ${firstProduct.url})` : firstProduct.name; const result = await modelAdapter.generateMagicWand( websiteUrl, firstProduct.url ? `${firstProduct.name} (URL: ${firstProduct.url})` : firstProduct.name, Number(budgetMax), specialInstructions, selectedProvider, selectedModel ); if (result.success && result.data) { const extractJson = (text: string) => { try { return JSON.parse(text); } catch (e) { const jsonMatch = text.match(/```json\s*([\s\S]*?)\s*```/i) || text.match(/```\s*([\s\S]*?)\s*```/i); if (jsonMatch) return JSON.parse(jsonMatch[1].trim()); const braceMatch = text.match(/(\{[\s\S]*\})/); if (braceMatch) return JSON.parse(braceMatch[0].trim()); throw e; } }; const data = extractJson(result.data); setMagicWandResult({ ...data, id: Math.random().toString(36).substr(2, 9), websiteUrl, product: firstProduct, budget: parseInt(budgetMax), generatedAt: new Date(), rawContent: result.data }); setExpandedSections(["market", "strategies"]); } else { setError(result.error || t.errorMagicWand); } } catch (err) { setError(err instanceof Error ? err.message : t.errorMagicWandGeneral); } finally { setIsMagicThinking(false); } }; const handleEnhanceInstructions = async () => { if (!specialInstructions.trim()) return; setIsEnhancing(true); setError(null); try { const result = await modelAdapter.enhancePrompt( specialInstructions, selectedProvider, selectedModel ); if (result.success && result.data) { setSpecialInstructions(result.data); } else { setError(result.error || "Failed to enhance instructions"); } } catch (err) { console.error("[GoogleAdsGenerator] Enhancement error:", err); setError(err instanceof Error ? err.message : "Instruction enhancement failed"); } finally { setIsEnhancing(false); } }; const handleCopy = async () => { const content = googleAdsResult?.rawContent || magicWandResult?.rawContent; if (content) { await navigator.clipboard.writeText(content); setCopied(true); setTimeout(() => setCopied(false), 2000); } }; const exportCSV = () => { if (!googleAdsResult && !magicWandResult) return; const csvContent = generateGoogleAdsCSV(googleAdsResult || undefined, magicWandResult || undefined); downloadFile(`google-ads-report-${new Date().toISOString().split('T')[0]}.csv`, csvContent, 'text/csv;charset=utf-8;'); }; const exportHTML = () => { if (!googleAdsResult && !magicWandResult) return; const htmlContent = generateGoogleAdsHTML(googleAdsResult || undefined, magicWandResult || undefined); downloadFile(`google-ads-report-${new Date().toISOString().split('T')[0]}.html`, htmlContent, 'text/html'); return; /* parts.push(`Google Ads Strategy Report`); parts.push(`
`); parts.push(`

Google Ads Strategy Report

Generated by PromptArch on ${new Date().toLocaleDateString()}

`); if (googleAdsResult) { // Keywords parts.push(`

🎯 Keyword Research

`); if (googleAdsResult.keywords) { if (Array.isArray(googleAdsResult.keywords.primary)) { parts.push(`

Primary Keywords

`); for (const k of googleAdsResult.keywords.primary) { parts.push(`${k.keyword} (${k.cpc || 'N/A'})`); } parts.push(`
`); } if (Array.isArray(googleAdsResult.keywords.longTail)) { parts.push(`

Long-tail Opportunities

`); for (const k of googleAdsResult.keywords.longTail) { parts.push(`${k.keyword} (${k.cpc || 'N/A'})`); } parts.push(`
`); } if (Array.isArray(googleAdsResult.keywords.negative)) { parts.push(`

Negative Keywords

`); for (const k of googleAdsResult.keywords.negative) { parts.push(`${k.keyword} (${k.cpc || 'N/A'})`); } parts.push(`
`); } } parts.push(`
`); // Ad Copies if (Array.isArray(googleAdsResult.adCopies)) { parts.push(`

✍️ Ad Copy Variations

`); let i = 0; for (const ad of googleAdsResult.adCopies) { i++; parts.push(`
Variation ${i}
`); if (Array.isArray(ad.headlines)) { for (const h of ad.headlines) { parts.push(`
${h}
`); } } if (Array.isArray(ad.descriptions)) { parts.push(`
${(ad.descriptions || []).join('
')}
`); } if (ad.callToAction) { parts.push(`
${ad.callToAction}
`); } parts.push(`
`); } parts.push(`
`); } // Campaigns if (Array.isArray(googleAdsResult.campaigns)) { parts.push(`

🏗️ Campaign Structure

`); for (const c of googleAdsResult.campaigns) { parts.push(`

${c.name}

`); parts.push(`

${c.type.toUpperCase()} • ${c.budget?.daily} ${c.budget?.currency}/day

`); const locs = c.targeting?.locations ? c.targeting.locations.join(', ') : 'Global'; parts.push(`
Locations: ${locs}
Ad Groups: ${c.adGroups ? c.adGroups.length : 0}
`); } parts.push(`
`); } // Implementation & Predictions parts.push(`

🚀 Implementation & Forecast

`); if (googleAdsResult.implementation && Array.isArray(googleAdsResult.implementation.setupSteps)) { parts.push(`

Setup Steps

    `); for (const s of googleAdsResult.implementation.setupSteps) { parts.push(`
  • ${s}
  • `); } parts.push(`
`); } if (googleAdsResult.predictions) { const p = googleAdsResult.predictions; parts.push(`

Monthly Estimations

`); parts.push(`
${p.estimatedClicks || '-'}
Clicks
`); parts.push(`
${p.estimatedCtr || '-'}
CTR
`); parts.push(`
${p.estimatedConversions || '-'}
Convs
`); parts.push(`
`); } parts.push(`
`); } if (magicWandResult) { parts.push(`

🧠 Market Intelligence

`); parts.push(`

Strategy Rationale

${magicWandResult.rationale}

`); const ma = magicWandResult.marketAnalysis; parts.push(`

Market Data

Growth Rate: ${ma?.growthRate || 'N/A'}

Top Competitors

    `); if (ma && Array.isArray(ma.topCompetitors)) { for (const c of ma.topCompetitors) { parts.push(`
  • ${c}
  • `); } } else { parts.push(`
  • None identified
  • `); } parts.push(`
`); } parts.push(`
`); const blob = new Blob([parts.join('')], { type: 'text/html' }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", `google-ads-report-${new Date().toISOString().split('T')[0]}.html`); document.body.appendChild(link); link.click(); document.body.removeChild(link); */ }; const sections = [ { id: "keywords", title: t.keywordsResearch }, { id: "adcopies", title: t.adCopyVariations }, { id: "campaigns", title: t.campaignStructure }, { id: "implementation", title: t.implementationGuide }, ]; const renderSectionContent = (sectionId: string) => { if (!googleAdsResult) return null; switch (sectionId) { case "keywords": return (
{googleAdsResult.keywords?.primary?.length > 0 && (

{t.labels.primaryKeywords}

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

{t.labels.longTail}

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

{t.labels.negative}

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

"{d}"

))}
))}
); case "campaigns": return (
{googleAdsResult.campaigns?.map((camp, i) => (
{camp.type} {t.labels.strategy}

{camp.name}

{camp.budget && (
${camp.budget.monthly}
{t.adGuide.budgetMonth}
)}
{camp.adGroups?.length > 0 && (
{t.adGuide.targetGroups}
{camp.adGroups.map((g, j) => (
{g.name}
))}
)}
))}
); case "implementation": return (
{googleAdsResult.implementation?.setupSteps?.length > 0 && (

1
{t.labels.config}

    {googleAdsResult.implementation.setupSteps.map((step, i) => (
  1. {String(i + 1).padStart(2, '0')}

    {step}

  2. ))}
)} {googleAdsResult.implementation?.qualityScoreTips?.length > 0 && (

2
{t.labels.quality}

    {googleAdsResult.implementation.qualityScoreTips.map((tip, i) => (
  • {tip}

  • ))}
)}
); default: return
{googleAdsResult.rawContent}
; } }; const renderMagicWandSectionContent = (sectionId: string) => { if (!magicWandResult) return null; switch (sectionId) { case "market": return (
{t.metrics.industrySize}
{magicWandResult.marketAnalysis.industrySize}
{t.metrics.growthRate}
{magicWandResult.marketAnalysis.growthRate}

{t.metrics.marketLeaders}

{magicWandResult.marketAnalysis.topCompetitors.map((c, i) => ( {c} ))}

{t.metrics.emergingTrends}

    {magicWandResult.marketAnalysis.marketTrends.map((t, i) => (
  • {i + 1} {t}
  • ))}
); case "competitors": return (
{magicWandResult.competitorInsights.map((comp, i) => (
{comp.competitor.charAt(0)}

{comp.competitor}

{t.metrics.competitorIntel}
{t.metrics.strengths}
    {comp.strengths.map((s, j) => (
  • {s}
  • ))}
{t.metrics.weaknesses}
    {comp.weaknesses.map((w, j) => (
  • !
    {w}
  • ))}
{t.spy}

{t.metrics.intelligence}: "{comp.adStrategy}"

))}
); case "strategies": return (
{magicWandResult.strategies.map((strat, i) => (

{strat.direction}

{strat.targetAudience}

{t.metrics.risk(strat.riskLevel)} {strat.timeToResults}

THE "WHY": {strat.rationale}

EDGE: {strat.competitiveAdvantage}
{strat.keyMessages.map((msg, j) => ( {msg} ))}
{/* BEGINNER-FRIENDLY AD MANAGER GUIDE */} {strat.adCopyGuide && (
{t.adGuide.title}
{t.adGuide.ready}
1 {t.adGuide.headlines}
{strat.adCopyGuide.headlines.map((h, j) => (
navigator.clipboard.writeText(h)}> {h}
))}
2 {t.adGuide.descriptions}
{strat.adCopyGuide.descriptions.map((d, j) => (
navigator.clipboard.writeText(d)}> "{d}"
))}
3 {t.adGuide.keywords}
{strat.adCopyGuide.keywords.map((k, j) => ( {k} ))}
{t.adGuide.tip}

"{strat.adCopyGuide.setupGuide}"

)}
{t.adGuide.channelMix}
{strat.recommendedChannels.map((c, j) => ( {c} ))}
{t.metrics.roi}
{strat.expectedROI}
))}
); default: return
{magicWandResult.rawContent}
; } }; return (
{t.title} {t.description}
{(["qwen", "ollama", "zai"] as const).map((provider) => ( ))}
setWebsiteUrl(e.target.value)} className="text-sm" />
{products.map((product, index) => (
updateProduct(index, "name", e.target.value)} className="text-sm" /> {products.length > 1 && ( )}
updateProduct(index, "url", e.target.value)} className="text-xs text-muted-foreground" />
))}
setBudgetMin(e.target.value)} className="text-sm" /> - setBudgetMax(e.target.value)} className="text-sm" />
setIndustry(e.target.value)} className="text-sm" />