"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 { 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 [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 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; let rows: string[][] = []; if (googleAdsResult) { // Keywords rows.push(["KEYWORDS RESEARCH"]); rows.push(["Type", "Keyword", "CPC"]); const addKw = (type: string, list?: any[]) => { if (list) list.forEach(k => rows.push([type, k.keyword, k.cpc || 'N/A'])); }; addKw("Primary", googleAdsResult.keywords?.primary); addKw("Long-tail", googleAdsResult.keywords?.longTail); addKw("Negative", googleAdsResult.keywords?.negative); rows.push([]); // Ad Copies rows.push(["AD COPIES"]); rows.push(["Headlines", "Descriptions", "CTA"]); googleAdsResult.adCopies?.forEach(ad => { rows.push([ ad.headlines?.join(' | ') || '', ad.descriptions?.join(' | ') || '', ad.callToAction || '' ]); }); rows.push([]); // Campaigns rows.push(["CAMPAIGN STRUCTURE"]); rows.push(["Name", "Type", "Budget", "Locations", "Schedule"]); googleAdsResult.campaigns?.forEach(c => { rows.push([ c.name, c.type, `${c.budget?.daily || 0} ${c.budget?.currency}`, c.targeting?.locations?.join('; ') || 'All', c.targeting?.schedule?.join('; ') || 'All' ]); }); rows.push([]); // Implementation rows.push(["IMPLEMENTATION GUIDE"]); const impl = googleAdsResult.implementation; if (impl) { rows.push(["Setup Steps", impl.setupSteps?.join('; ') || '']); rows.push(["Quality Score Tips", impl.qualityScoreTips?.join('; ') || '']); rows.push(["Optimization Tips", impl.optimizationTips?.join('; ') || '']); } rows.push([]); // Predictions if (googleAdsResult.predictions) { rows.push(["PERFORMANCE PREDICTIONS"]); const p = googleAdsResult.predictions; rows.push(["Metric", "Estimate"]); rows.push(["Clicks", p.estimatedClicks || "N/A"]); rows.push(["Impressions", p.estimatedImpressions || "N/A"]); rows.push(["CTR", p.estimatedCtr || "N/A"]); rows.push(["Conversions", p.estimatedConversions || "N/A"]); rows.push([]); } } if (magicWandResult) { rows.push(["MARKET ANALYSIS"]); const ma = magicWandResult.marketAnalysis; rows.push(["Growth Rate", ma?.growthRate || 'N/A']); rows.push(["Top Competitors", ma?.topCompetitors?.join('; ') || 'N/A']); rows.push(["Market Trends", ma?.marketTrends?.join('; ') || 'N/A']); } // CSV String Construction with proper escaping const csvContent = "data:text/csv;charset=utf-8," + rows.map(row => row.map(cell => `"${(cell || '').replace(/"/g, '""')}"`).join(",")).join("\n"); const encodedUri = encodeURI(csvContent); const link = document.createElement("a"); link.setAttribute("href", encodedUri); link.setAttribute("download", `google-ads-report-${new Date().toISOString().split('T')[0]}.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const exportHTML = () => { if (!googleAdsResult && !magicWandResult) return; let html = ` Google Ads Strategy Report

Google Ads Strategy Report

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

`; if (googleAdsResult) { // Keywords html += `

🎯 Keyword Research

`; const renderKw = (title: string, list?: any[]) => { if (!list?.length) return ''; return `

${title}

${list.map(k => `${k.keyword} (${k.cpc || 'N/A'})`).join('')}
`; }; html += renderKw("Primary Keywords", googleAdsResult.keywords?.primary); html += renderKw("Long-tail Opportunities", googleAdsResult.keywords?.longTail); html += renderKw("Negative Keywords", googleAdsResult.keywords?.negative); html += `
`; // Ad Copies if (googleAdsResult.adCopies?.length) { html += `

✍️ Ad Copy Variations

`; googleAdsResult.adCopies.forEach((ad, i) => { html += `
Variation ${i + 1}
${ad.headlines.map(h => `
${h}
`).join('')}
${ad.descriptions.join('
')}
${ad.callToAction ? `
${ad.callToAction}
` : ''}
`; }); html += `
`; } // Campaigns if (googleAdsResult.campaigns?.length) { html += `

🏗️ Campaign Structure

${googleAdsResult.campaigns.map(c => `

${c.name}

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

Locations: ${c.targeting.locations?.join(', ') || 'Global'}
Ad Groups: ${c.adGroups.length}
`).join('')}
`; } // Implementation & Predictions html += `

🚀 Implementation & Forecast

Setup Steps

    ${googleAdsResult.implementation.setupSteps.map(s => `
  • ${s}
  • `).join('')}
${googleAdsResult.predictions ? `

Monthly Estimations

${googleAdsResult.predictions.estimatedClicks || '-'}
Clicks
${googleAdsResult.predictions.estimatedCtr || '-'}
CTR
${googleAdsResult.predictions.estimatedConversions || '-'}
Convs
` : ''}
`; } if (magicWandResult) { html += `

🧠 Market Intelligence

Strategy Rationale

${magicWandResult.rationale}

Market Data

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

Top Competitors

    ${magicWandResult.marketAnalysis?.topCompetitors?.map(c => `
  • ${c}
  • `).join('') || '
  • None identified
  • '}
`; } html += `
`; const blob = new Blob([html], { 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); URL.revokeObjectURL(url); }; 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" />