diff --git a/app/globals.css b/app/globals.css index 6d89ec7..6e50a59 100644 --- a/app/globals.css +++ b/app/globals.css @@ -63,53 +63,71 @@ * { @apply border-border; } + body { @apply bg-background text-foreground; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } - + /* Mobile optimizations */ html { scroll-behavior: smooth; -webkit-tap-highlight-color: transparent; } - + /* Better touch targets */ - button, a, [role="button"] { + button, + a, + [role="button"] { touch-action: manipulation; } - + /* Prevent text selection on buttons */ button { -webkit-user-select: none; user-select: none; } - + /* Safe area padding for notched devices */ .safe-area-inset { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); } - + /* Scrollbar styling for mobile-like experience */ ::-webkit-scrollbar { width: 6px; height: 6px; } - + ::-webkit-scrollbar-track { background: transparent; } - + ::-webkit-scrollbar-thumb { background: hsl(var(--border)); border-radius: 3px; } - + ::-webkit-scrollbar-thumb:hover { background: hsl(var(--muted-foreground)); } } +@layer utilities { + @keyframes progress-indeterminate { + 0% { + transform: translateX(-100%); + } + + 100% { + transform: translateX(100%); + } + } + + .animate-progress-indeterminate { + animation: progress-indeterminate 1.5s infinite linear; + } +} \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index ade7bd4..ab537fa 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -8,6 +8,7 @@ import PRDGenerator from "@/components/PRDGenerator"; import ActionPlanGenerator from "@/components/ActionPlanGenerator"; import UXDesignerPrompt from "@/components/UXDesignerPrompt"; import SlidesGenerator from "@/components/SlidesGenerator"; +import GoogleAdsGenerator from "@/components/GoogleAdsGenerator"; import HistoryPanel from "@/components/HistoryPanel"; import SettingsPanel from "@/components/SettingsPanel"; import modelAdapter from "@/lib/services/adapter-instance"; @@ -32,6 +33,8 @@ export default function Home() { return ; case "slides": return ; + case "googleads": + return ; case "history": return ; case "settings": diff --git a/components/GoogleAdsGenerator.tsx b/components/GoogleAdsGenerator.tsx new file mode 100644 index 0000000..6e12261 --- /dev/null +++ b/components/GoogleAdsGenerator.tsx @@ -0,0 +1,871 @@ +"use client"; + +import { useState, useEffect, useCallback } 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 { cn } from "@/lib/utils"; +import { GoogleAdsResult, GoogleAdsKeyword, GoogleAdCopy, GoogleAdsCampaign } from "@/types"; + +export default function GoogleAdsGenerator() { + const { + googleAdsResult, + selectedProvider, + selectedModels, + availableModels, + apiKeys, + isProcessing, + error, + setGoogleAdsResult, + setProcessing, + setError, + setAvailableModels, + setSelectedModel, + setSelectedProvider, + } = useStore(); + + // Input states + const [websiteUrl, setWebsiteUrl] = useState(""); + const [products, setProducts] = useState([""]); + 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 selectedModel = selectedModels[selectedProvider]; + const models = availableModels[selectedProvider] || modelAdapter.getAvailableModels(selectedProvider); + + useEffect(() => { + if (typeof window !== "undefined") { + loadAvailableModels(); + } + }, [selectedProvider]); + + 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, ""]); + const removeProduct = (index: number) => { + const newProducts = products.filter((_, i) => i !== index); + setProducts(newProducts.length ? newProducts : [""]); + }; + const updateProduct = (index: number, value: string) => { + const newProducts = [...products]; + newProducts[index] = value; + 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"); + return; + } + + const apiKey = apiKeys[selectedProvider]; + const isQwenOAuth = selectedProvider === "qwen" && modelAdapter.hasQwenAuth(); + + if (!isQwenOAuth && (!apiKey || !apiKey.trim())) { + setError(`Please configure your ${selectedProvider.toUpperCase()} API key in Settings`); + return; + } + + setProcessing(true); + setError(null); + setActiveTab("input"); + + try { + const result = await modelAdapter.generateGoogleAds(websiteUrl, { + productsServices: filteredProducts, + targetAudience, + budgetRange: { min: parseInt(budgetMin), max: parseInt(budgetMax), currency }, + campaignDuration: duration, + industry, + language: "English" + }, selectedProvider, selectedModel); + + if (result.success && result.data) { + try { + const parsedData = typeof result.data === 'string' ? JSON.parse(result.data) : result.data; + const adsResult: GoogleAdsResult = { + ...parsedData, + id: Math.random().toString(36).substr(2, 9), + websiteUrl, + productsServices: filteredProducts, + generatedAt: new Date(), + rawContent: typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2) + }; + setGoogleAdsResult(adsResult); + setActiveTab("keywords"); + } catch (e) { + console.error("Failed to parse ads data:", e); + setError("Failed to parse the generated ads content. Please try again."); + } + } else { + setError(result.error || "Failed to generate Google Ads campaign"); + } + } catch (err) { + console.error("Ads 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 exportToCsv = () => { + if (!googleAdsResult) return; + + let csvContent = "data:text/csv;charset=utf-8,"; + csvContent += "Type,Category,Headline/Keyword,Description/Value,Details\n"; + + // 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); + }; + + return ( +
+
+
+

+ + Google Ads Gen + PRO +

+

+ Generate high-converting keywords, ad copy, and campaign structures with AI. +

+
+ + {googleAdsResult && ( +
+ + +
+ )} +
+ +
+ {/* Input Panel */} +
+ +
+ + + + Campaign Parameters + + + Define your goals and target audience. + + + +
+ + setWebsiteUrl(e.target.value)} + className="bg-muted/30 focus-visible:ring-blue-500" + /> +
+ +
+ +
+ {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} +
+ )} + + +
+
+ + +
+

+ + 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 && ( +
+
+
+
+ +
+
+

Analyzing Domain Content

+
+
+
+

+ Scanning {websiteUrl}, fetching search volumes, and drafting high-converting copy... +

+
+ {[ + { 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} +
+ ))} +
+
+ )} + + {googleAdsResult && ( + +
+ + + + Keywords + + + + Ad Templates + + + + Campaigns + + + + Launch Guide + + + + {googleAdsResult.predictions && ( +
+
+ Est. CTR + {googleAdsResult.predictions.estimatedCtr} +
+
+ Conversions + {googleAdsResult.predictions.estimatedConversions} +
+
+ )} +
+ +
+ +
+ + + + + + Primary Keywords + + + + + +
+ {googleAdsResult.keywords.primary.map((item, i) => ( +
+
+ {item.keyword} +
+ Vol: {item.searchVolume} + CPC: {item.cpc} +
+
+ + {item.competition} + +
+ ))} +
+
+
+ + + + + + + Long-Tail Opportunities + + + + + +
+ {googleAdsResult.keywords.longTail.map((item, i) => ( +
+
+ {item.keyword} + High Performance Match: {item.difficultyScore}% +
+ {item.cpc} +
+ ))} +
+
+
+
+ + + + + + + Negative Keyword List (Add these to Campaigns) + + + + Exclude these terms to prevent wasted spend on irrelevant clicks. + + +
+ {googleAdsResult.keywords.negative.map((item, i) => ( + + {item.keyword} + + ))} +
+
+
+
+ + +
+ {googleAdsResult.adCopies.map((ad, i) => ( + +
+
+ + Ad Variation {i + 1} • {ad.campaignType} +
+ {ad.mobileOptimized && ( + Mobile Ready + )} +
+
+
+ {ad.headlines.map((h, j) => ( +
+ {h} + {h.length}/30 +
+ ))} +
+
+ Ads • {ad.displayUrl} +
+
+ {ad.descriptions.map((d, j) => ( +
+

{d}

+ {d.length}/90 +
+ ))} +
+ +
+ + +
+
+
+ ))} +
+ +
+
+
+

+ + A/B Testing Recommendation +

+

+ Use dynamic keyword insertion for "Variation 1" and focus on emotion-based headlines for "Variation 2" to compare user engagement and CTR. +

+
+ +
+
+ +
+
+
+ + + {googleAdsResult.campaigns.map((camp, i) => ( + + +
+ {camp.name} + + {camp.type.toUpperCase()} + Targeting: {camp.targeting.locations?.join(', ') || 'Global'} + +
+
+
Monthly Budget
+
{camp.budget.currency} {camp.budget.monthly}
+
Approx. {camp.budget.currency}{camp.budget.daily}/day
+
+
+ +

Ad Group Organization

+
+ {camp.adGroups.map((group, j) => ( +
+
+
{group.name}
+ {group.biddingStrategy} +
+

{group.theme}

+
+ {group.keywords.map((kw, k) => ( + {kw} + ))} +
+
+ ))} +
+ +
+
+
Target Locations
+
{camp.targeting.locations?.join(', ') || 'All'}
+
+
+
Devices
+
{camp.targeting.devices?.join(', ') || 'All Devices'}
+
+
+
Demographics
+
{camp.targeting.demographics?.join(', ') || 'All Ages'}
+
+
+
Schedule
+
{camp.targeting.schedule?.join(', ') || '24/7'}
+
+
+
+
+ ))} + +
+
+
Bidding Strategy
+

+ We recommend Maximize Conversions with a target CPA to balance volume and ROI given your industry competition. +

+
+
+
Network Selection
+

+ Include Search Partners but disable Display Network for this campaign to ensure high search intent traffic. +

+
+
+
Ad Assets
+

+ Add Sitelinks, Callouts, and Image assets to improve your Ad Rank and Quality Score. +

+
+
+
+ + +
+
+

+ 1 + Launch Steps +

+
+ {googleAdsResult.implementation.setupSteps.map((step, i) => ( +
+
{i + 1}
+

{step}

+
+ ))} +
+
+ +
+
+

+ 2 + Quality Score Hacks +

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

+ 3 + Tracking & Analytics +

+
+
    + {googleAdsResult.implementation.trackingSetup.map((item, i) => ( +
  • +
    + {item} +
  • + ))} +
+ +
+
+
+
+ + + +
+ +
+
+

Scale Your Success

+

+ Success in Google Ads is about iterative optimization. Use these initial settings to gather data for 14 days, then begin aggressive A/B testing on headlines and landing page consistency. +

+
+ {googleAdsResult.implementation.optimizationTips.slice(0, 3).map((tip, i) => ( + + {tip} + + ))} +
+
+
+
+
+
+
+ )} +
+
+
+ ); +} diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index 69193a9..b2abec5 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -3,10 +3,10 @@ import { useState } from "react"; import { Button } from "@/components/ui/button"; import useStore from "@/lib/store"; -import { Sparkles, FileText, ListTodo, Palette, Presentation, History, Settings, Github, Menu, X } from "lucide-react"; +import { Sparkles, FileText, ListTodo, Palette, Presentation, History, Settings, Github, Menu, X, Megaphone } from "lucide-react"; import { cn } from "@/lib/utils"; -export type View = "enhance" | "prd" | "action" | "uxdesigner" | "slides" | "history" | "settings"; +export type View = "enhance" | "prd" | "action" | "uxdesigner" | "slides" | "googleads" | "history" | "settings"; interface SidebarProps { currentView: View; @@ -23,6 +23,7 @@ export default function Sidebar({ currentView, onViewChange }: SidebarProps) { { id: "action" as View, label: "Action Plan", icon: ListTodo }, { id: "uxdesigner" as View, label: "UX Designer", icon: Palette }, { id: "slides" as View, label: "Slides Generator", icon: Presentation }, + { id: "googleads" as View, label: "Google Ads Gen", icon: Megaphone }, { id: "history" as View, label: "History", icon: History, count: history.length }, { id: "settings" as View, label: "Settings", icon: Settings }, ]; diff --git a/components/ui/badge.tsx b/components/ui/badge.tsx new file mode 100644 index 0000000..44ca81a --- /dev/null +++ b/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps { } + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } diff --git a/components/ui/tabs.tsx b/components/ui/tabs.tsx new file mode 100644 index 0000000..6c01864 --- /dev/null +++ b/components/ui/tabs.tsx @@ -0,0 +1,55 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" + +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } diff --git a/lib/services/model-adapter.ts b/lib/services/model-adapter.ts index fdcf684..2c7aacf 100644 --- a/lib/services/model-adapter.ts +++ b/lib/services/model-adapter.ts @@ -207,6 +207,25 @@ export class ModelAdapter { return this.callWithFallback((service) => service.generateSlides(topic, options, model), providers); } + async generateGoogleAds( + websiteUrl: string, + options: { + productsServices: string[]; + targetAudience?: string; + budgetRange?: { min: number; max: number; currency: string }; + campaignDuration?: string; + industry?: string; + competitors?: string[]; + language?: string; + } = { productsServices: [] }, + provider?: ModelProvider, + model?: string + ): Promise> { + const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai"); + const providers: ModelProvider[] = provider ? [provider] : fallback; + return this.callWithFallback((service) => service.generateGoogleAds(websiteUrl, options, model), providers); + } + async chatCompletion( messages: ChatMessage[], model: string, diff --git a/lib/services/ollama-cloud.ts b/lib/services/ollama-cloud.ts index 4de53b2..886938d 100644 --- a/lib/services/ollama-cloud.ts +++ b/lib/services/ollama-cloud.ts @@ -434,7 +434,100 @@ Generate SPECTACULAR slides with CSS3 animations, SVG charts, modern gradients, return this.chatCompletion([systemMessage, userMessage], model || "gpt-oss:120b"); } + + async generateGoogleAds( + websiteUrl: string, + options: { + productsServices: string[]; + targetAudience?: string; + budgetRange?: { min: number; max: number; currency: string }; + campaignDuration?: string; + industry?: string; + competitors?: string[]; + language?: string; + } = { productsServices: [] }, + model?: string + ): Promise> { + const { + productsServices = [], + targetAudience = "General consumers", + budgetRange, + campaignDuration, + industry = "General", + competitors = [], + language = "English" + } = options; + + const systemMessage: ChatMessage = { + role: "system", + content: `You are an EXPERT Google Ads strategist. Create HIGH-CONVERTING campaigns with comprehensive keyword research, compelling ad copy, and optimized campaign structures. + +OUTPUT FORMAT - Return ONLY valid JSON with this structure: +\`\`\`json +{ + "keywords": { + "primary": [{"keyword": "term", "type": "primary", "searchVolume": 12000, "competition": "medium", "cpc": "$2.50"}], + "longTail": [{"keyword": "specific term", "type": "long-tail", "searchVolume": 1200, "competition": "low", "cpc": "$1.25"}], + "negative": [{"keyword": "exclude term", "type": "negative", "competition": "low"}] + }, + "adCopies": [{ + "id": "ad-1", + "campaignType": "search", + "headlines": ["Headline 1 (30 chars)", "Headline 2", "Headline 3"], + "descriptions": ["Description 1 (90 chars)", "Description 2"], + "callToAction": "Get Started", + "mobileOptimized": true + }], + "campaigns": [{ + "id": "campaign-1", + "name": "Campaign Name", + "type": "search", + "budget": {"daily": 50, "monthly": 1500, "currency": "USD"}, + "targeting": {"locations": [], "demographics": [], "devices": []}, + "adGroups": [{"id": "adgroup-1", "name": "Group", "theme": "Theme", "keywords": [], "biddingStrategy": "Maximize conversions"}] + }], + "implementation": { + "setupSteps": [], + "qualityScoreTips": [], + "trackingSetup": [], + "optimizationTips": [] + }, + "predictions": { + "estimatedClicks": "500-800/month", + "estimatedImpressions": "15,000-25,000/month", + "estimatedCtr": "3.2%-4.5%", + "estimatedConversions": "25-50/month" + } +} +\`\`\` + +Requirements: +- 10-15 primary keywords, 15-20 long-tail, 5-10 negative +- Headlines max 30 chars, descriptions max 90 chars +- 3-5 ad variations per campaign +- Include budget and targeting recommendations`, + }; + + const userMessage: ChatMessage = { + role: "user", + content: `Create a Google Ads campaign for: + +WEBSITE: ${websiteUrl} +PRODUCTS/SERVICES: ${productsServices.join(", ")} +TARGET AUDIENCE: ${targetAudience} +INDUSTRY: ${industry} +LANGUAGE: ${language} +${budgetRange ? `BUDGET: ${budgetRange.min}-${budgetRange.max} ${budgetRange.currency}/month` : ""} +${campaignDuration ? `DURATION: ${campaignDuration}` : ""} +${competitors.length > 0 ? `COMPETITORS: ${competitors.join(", ")}` : ""} + +Generate complete Google Ads package with keywords, ad copy, campaigns, and implementation guidance.`, + }; + + return this.chatCompletion([systemMessage, userMessage], model || "gpt-oss:120b"); + } } export default OllamaCloudService; + diff --git a/lib/services/qwen-oauth.ts b/lib/services/qwen-oauth.ts index cb50f06..769b93e 100644 --- a/lib/services/qwen-oauth.ts +++ b/lib/services/qwen-oauth.ts @@ -762,6 +762,98 @@ Generate SPECTACULAR slides with CSS3 animations, SVG charts, modern gradients, return this.chatCompletion([systemMessage, userMessage], model || "coder-model"); } + async generateGoogleAds( + websiteUrl: string, + options: { + productsServices: string[]; + targetAudience?: string; + budgetRange?: { min: number; max: number; currency: string }; + campaignDuration?: string; + industry?: string; + competitors?: string[]; + language?: string; + } = { productsServices: [] }, + model?: string + ): Promise> { + const { + productsServices = [], + targetAudience = "General consumers", + budgetRange, + campaignDuration, + industry = "General", + competitors = [], + language = "English" + } = options; + + const systemMessage: ChatMessage = { + role: "system", + content: `You are an EXPERT Google Ads strategist. Create HIGH-CONVERTING campaigns with comprehensive keyword research, compelling ad copy, and optimized campaign structures. + +OUTPUT FORMAT - Return ONLY valid JSON with this structure: +\`\`\`json +{ + "keywords": { + "primary": [{"keyword": "term", "type": "primary", "searchVolume": 12000, "competition": "medium", "cpc": "$2.50"}], + "longTail": [{"keyword": "specific term", "type": "long-tail", "searchVolume": 1200, "competition": "low", "cpc": "$1.25"}], + "negative": [{"keyword": "exclude term", "type": "negative", "competition": "low"}] + }, + "adCopies": [{ + "id": "ad-1", + "campaignType": "search", + "headlines": ["Headline 1 (30 chars)", "Headline 2", "Headline 3"], + "descriptions": ["Description 1 (90 chars)", "Description 2"], + "callToAction": "Get Started", + "mobileOptimized": true + }], + "campaigns": [{ + "id": "campaign-1", + "name": "Campaign Name", + "type": "search", + "budget": {"daily": 50, "monthly": 1500, "currency": "USD"}, + "targeting": {"locations": [], "demographics": [], "devices": []}, + "adGroups": [{"id": "adgroup-1", "name": "Group", "theme": "Theme", "keywords": [], "biddingStrategy": "Maximize conversions"}] + }], + "implementation": { + "setupSteps": [], + "qualityScoreTips": [], + "trackingSetup": [], + "optimizationTips": [] + }, + "predictions": { + "estimatedClicks": "500-800/month", + "estimatedImpressions": "15,000-25,000/month", + "estimatedCtr": "3.2%-4.5%", + "estimatedConversions": "25-50/month" + } +} +\`\`\` + +Requirements: +- 10-15 primary keywords, 15-20 long-tail, 5-10 negative +- Headlines max 30 chars, descriptions max 90 chars +- 3-5 ad variations per campaign +- Include budget and targeting recommendations`, + }; + + const userMessage: ChatMessage = { + role: "user", + content: `Create a Google Ads campaign for: + +WEBSITE: ${websiteUrl} +PRODUCTS/SERVICES: ${productsServices.join(", ")} +TARGET AUDIENCE: ${targetAudience} +INDUSTRY: ${industry} +LANGUAGE: ${language} +${budgetRange ? `BUDGET: ${budgetRange.min}-${budgetRange.max} ${budgetRange.currency}/month` : ""} +${campaignDuration ? `DURATION: ${campaignDuration}` : ""} +${competitors.length > 0 ? `COMPETITORS: ${competitors.join(", ")}` : ""} + +Generate complete Google Ads package with keywords, ad copy, campaigns, and implementation guidance.`, + }; + + return this.chatCompletion([systemMessage, userMessage], model || "coder-model"); + } + async listModels(): Promise> { const models = [ "coder-model", @@ -780,3 +872,4 @@ const qwenOAuthService = new QwenOAuthService(); export default qwenOAuthService; export { qwenOAuthService }; + diff --git a/lib/services/zai-plan.ts b/lib/services/zai-plan.ts index f4bcdea..66fe3c2 100644 --- a/lib/services/zai-plan.ts +++ b/lib/services/zai-plan.ts @@ -407,7 +407,206 @@ Return the complete JSON with full htmlContent for each slide. Make each slide V return this.chatCompletion([systemMessage, userMessage], model || "glm-4.7", true); } + + async generateGoogleAds( + websiteUrl: string, + options: { + productsServices: string[]; + targetAudience?: string; + budgetRange?: { min: number; max: number; currency: string }; + campaignDuration?: string; + industry?: string; + competitors?: string[]; + language?: string; + } = { productsServices: [] }, + model?: string + ): Promise> { + const { + productsServices = [], + targetAudience = "General consumers", + budgetRange, + campaignDuration, + industry = "General", + competitors = [], + language = "English" + } = options; + + const systemMessage: ChatMessage = { + role: "system", + content: `You are an EXPERT Google Ads strategist with 15+ years of experience managing $100M+ in ad spend. You create HIGH-CONVERTING campaigns that consistently outperform industry benchmarks. + +Your expertise includes: +- Keyword research and competitive analysis +- Ad copywriting that drives clicks and conversions +- Campaign structure optimization +- Quality Score improvement strategies +- ROI maximization techniques + +OUTPUT FORMAT - Return ONLY valid JSON: +\`\`\`json +{ + "keywords": { + "primary": [ + { + "keyword": "exact keyword phrase", + "type": "primary", + "searchVolume": 12000, + "competition": "medium", + "difficultyScore": 65, + "relevanceScore": 95, + "cpc": "$2.50" + } + ], + "longTail": [ + { + "keyword": "longer specific keyword phrase", + "type": "long-tail", + "searchVolume": 1200, + "competition": "low", + "difficultyScore": 35, + "relevanceScore": 90, + "cpc": "$1.25" + } + ], + "negative": [ + { + "keyword": "irrelevant term to exclude", + "type": "negative", + "competition": "low" + } + ] + }, + "adCopies": [ + { + "id": "ad-1", + "campaignType": "search", + "headlines": [ + "Headline 1 (max 30 chars)", + "Headline 2 (max 30 chars)", + "Headline 3 (max 30 chars)" + ], + "descriptions": [ + "Description line 1 - compelling copy under 90 chars", + "Description line 2 - call to action under 90 chars" + ], + "callToAction": "Get Started Today", + "displayUrl": "example.com/offers", + "mobileOptimized": true + } + ], + "campaigns": [ + { + "id": "campaign-1", + "name": "Campaign Name", + "type": "search", + "budget": { + "daily": 50, + "monthly": 1500, + "currency": "USD" + }, + "targeting": { + "locations": ["United States", "Canada"], + "demographics": ["25-54", "All genders"], + "devices": ["Desktop", "Mobile", "Tablet"], + "schedule": ["Mon-Fri 8am-8pm"] + }, + "adGroups": [ + { + "id": "adgroup-1", + "name": "Product Category Group", + "theme": "Main product focus", + "keywords": ["keyword1", "keyword2"], + "biddingStrategy": "Maximize conversions" + } + ] + } + ], + "implementation": { + "setupSteps": [ + "Step 1: Create Google Ads account...", + "Step 2: Set up conversion tracking..." + ], + "qualityScoreTips": [ + "Tip 1: Match keywords to ad copy...", + "Tip 2: Optimize landing pages..." + ], + "trackingSetup": [ + "Install Google Tag Manager...", + "Set up conversion goals..." + ], + "optimizationTips": [ + "Monitor search terms weekly...", + "A/B test ad variations..." + ] + }, + "predictions": { + "estimatedClicks": "500-800 per month", + "estimatedImpressions": "15,000-25,000 per month", + "estimatedCtr": "3.2%-4.5%", + "estimatedConversions": "25-50 per month" + } +} +\`\`\` + +KEYWORD RESEARCH REQUIREMENTS: +- Generate 10-15 PRIMARY keywords (high-volume, highly relevant) +- Generate 15-20 LONG-TAIL keywords (specific, lower-competition) +- Generate 5-10 NEGATIVE keywords (terms to exclude) +- Include realistic search volume estimates +- Provide competition level and CPC estimates + +AD COPY REQUIREMENTS: +- Headlines MUST be 30 characters or less +- Descriptions MUST be 90 characters or less +- Create 3-5 unique ad variations per campaign type +- Include strong calls-to-action +- Focus on benefits and unique value propositions +- Mobile-optimized versions required + +CAMPAIGN STRUCTURE: +- Organize by product/service theme +- Recommend appropriate bidding strategies +- Include targeting recommendations +- Suggest budget allocation + +QUALITY STANDARDS: +- All keywords must be relevant (>85% match) +- Ad copy must comply with Google Ads policies +- No trademark violations +- Professional, compelling language +- Clear value propositions`, + }; + + const userMessage: ChatMessage = { + role: "user", + content: `Create a COMPREHENSIVE Google Ads campaign for: + +WEBSITE: ${websiteUrl} + +PRODUCTS/SERVICES TO PROMOTE: +${productsServices.map((p, i) => `${i + 1}. ${p}`).join("\n")} + +TARGET AUDIENCE: ${targetAudience} +INDUSTRY: ${industry} +LANGUAGE: ${language} +${budgetRange ? `BUDGET: ${budgetRange.min}-${budgetRange.max} ${budgetRange.currency}/month` : ""} +${campaignDuration ? `DURATION: ${campaignDuration}` : ""} +${competitors.length > 0 ? `COMPETITORS: ${competitors.join(", ")}` : ""} + +Generate a COMPLETE Google Ads package including: +🔍 Comprehensive keyword research (primary, long-tail, negative) +✍️ High-converting ad copy (multiple variations) +📊 Optimized campaign structure +📈 Performance predictions +🎯 Implementation guidance + +Make this campaign READY TO LAUNCH with copy-paste ready content!`, + }; + + return this.chatCompletion([systemMessage, userMessage], model || "glm-4.7", true); + } } export default ZaiPlanService; + diff --git a/lib/store.ts b/lib/store.ts index bbe789f..27cab9d 100644 --- a/lib/store.ts +++ b/lib/store.ts @@ -1,5 +1,5 @@ import { create } from "zustand"; -import type { ModelProvider, PromptEnhancement, PRD, ActionPlan, SlidesPresentation } from "@/types"; +import type { ModelProvider, PromptEnhancement, PRD, ActionPlan, SlidesPresentation, GoogleAdsResult } from "@/types"; interface AppState { currentPrompt: string; @@ -7,6 +7,7 @@ interface AppState { prd: PRD | null; actionPlan: ActionPlan | null; slidesPresentation: SlidesPresentation | null; + googleAdsResult: GoogleAdsResult | null; selectedProvider: ModelProvider; selectedModels: Record; availableModels: Record; @@ -29,6 +30,7 @@ interface AppState { setPRD: (prd: PRD) => void; setActionPlan: (plan: ActionPlan) => void; setSlidesPresentation: (slides: SlidesPresentation | null) => void; + setGoogleAdsResult: (result: GoogleAdsResult | null) => void; setSelectedProvider: (provider: ModelProvider) => void; setSelectedModel: (provider: ModelProvider, model: string) => void; setAvailableModels: (provider: ModelProvider, models: string[]) => void; @@ -47,6 +49,7 @@ const useStore = create((set) => ({ prd: null, actionPlan: null, slidesPresentation: null, + googleAdsResult: null, selectedProvider: "qwen", selectedModels: { qwen: "coder-model", @@ -72,6 +75,7 @@ const useStore = create((set) => ({ setPRD: (prd) => set({ prd }), setActionPlan: (plan) => set({ actionPlan: plan }), setSlidesPresentation: (slides) => set({ slidesPresentation: slides }), + setGoogleAdsResult: (result) => set({ googleAdsResult: result }), setSelectedProvider: (provider) => set({ selectedProvider: provider }), setSelectedModel: (provider, model) => set((state) => ({ @@ -107,6 +111,7 @@ const useStore = create((set) => ({ prd: null, actionPlan: null, slidesPresentation: null, + googleAdsResult: null, error: null, }), })); diff --git a/package-lock.json b/package-lock.json index fdb2d81..3dca7bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@radix-ui/react-tabs": "^1.1.13", "@types/node": "^22.10.1", "@types/react": "^19.0.1", "@types/react-dom": "^19.0.2", "autoprefixer": "^10.4.20", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "eslint": "^9.16.0", "eslint-config-next": "^15.0.3", @@ -930,6 +932,294 @@ "node": ">=12.4.0" } }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", @@ -1145,7 +1435,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -2044,6 +2334,18 @@ "node": ">= 6" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", diff --git a/package.json b/package.json index 8f58dbe..d3933fe 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,12 @@ "lint": "next lint" }, "dependencies": { + "@radix-ui/react-tabs": "^1.1.13", "@types/node": "^22.10.1", "@types/react": "^19.0.1", "@types/react-dom": "^19.0.2", "autoprefixer": "^10.4.20", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "eslint": "^9.16.0", "eslint-config-next": "^15.0.3", diff --git a/types/index.ts b/types/index.ts index deec110..d3aee5a 100644 --- a/types/index.ts +++ b/types/index.ts @@ -115,3 +115,90 @@ export interface SlidesPresentation { createdAt: Date; updatedAt: Date; } + +export interface GoogleAdsKeyword { + keyword: string; + type: "primary" | "long-tail" | "negative"; + searchVolume?: number; + competition: "low" | "medium" | "high"; + difficultyScore?: number; + relevanceScore?: number; + cpc?: string; +} + +export interface GoogleAdCopy { + id: string; + campaignType: "search" | "display" | "shopping" | "video" | "performance-max"; + headlines: string[]; + descriptions: string[]; + callToAction: string; + displayUrl?: string; + finalUrl?: string; + mobileOptimized: boolean; +} + +export interface GoogleAdGroup { + id: string; + name: string; + theme: string; + keywords: string[]; + ads: GoogleAdCopy[]; + biddingStrategy?: string; +} + +export interface GoogleAdsCampaign { + id: string; + name: string; + type: "search" | "display" | "shopping" | "video" | "performance-max"; + budget: { + daily?: number; + monthly?: number; + currency: string; + }; + targeting: { + locations?: string[]; + demographics?: string[]; + devices?: string[]; + schedule?: string[]; + }; + adGroups: GoogleAdGroup[]; +} + +export interface GoogleAdsResult { + id: string; + websiteUrl: string; + productsServices: string[]; + generatedAt: Date; + + // Keyword Research Package + keywords: { + primary: GoogleAdsKeyword[]; + longTail: GoogleAdsKeyword[]; + negative: GoogleAdsKeyword[]; + }; + + // Ad Copy Suite + adCopies: GoogleAdCopy[]; + + // Campaign Structure + campaigns: GoogleAdsCampaign[]; + + // Implementation Guidance + implementation: { + setupSteps: string[]; + qualityScoreTips: string[]; + trackingSetup: string[]; + optimizationTips: string[]; + }; + + // Performance Predictions + predictions?: { + estimatedClicks?: string; + estimatedImpressions?: string; + estimatedCtr?: string; + estimatedConversions?: string; + }; + + rawContent: string; +} +