Compare commits

...

5 Commits

  • refactor: Google Ads Gen UI to match PRDGenerator design pattern
    - Replaced flashy styling with simple two-column card layout
    - Added collapsible sections for Keywords, Ad Copy, Campaigns, Implementation
    - Consistent typography and spacing with other generators
    - Removed all gradients, glassmorphism, and excess animations
  • fix: Google Ads parsing and UI/UX refinement
    - Added robust JSON extraction logic to handle AI responses with markdown formatting.
    - Overhauled Google Ads Gen UI with glassmorphism and premium aesthetics.
    - Harmonized main content styling with the sidebar design ecosystem.
  • feat: Integrated Google Ads Generator feature
    - Added GoogleAdsGenerator component with comprehensive keyword research and ad copy generation.
    - Updated Sidebar and Home page for navigation.
    - Added necessary UI components (Badge, Tabs) and dependencies.
    - Added custom animations for progress indicators.
  • feat: Enhanced Slides Generator with animated presentations and file attachments
    Major improvements:
    - MODERN ANIMATED SLIDES: CSS3 animations (fadeIn, slideIn, scaleIn, pulse)
      SVG charts and data visualizations, glassmorphism effects, gradient backgrounds
      Professional typography, staggered reveal animations
    
    - FILE ATTACHMENT SUPPORT: Upload PowerPoint, PDFs, Docs, Images, Color Palettes
      Auto-extract colors from images for brand consistency
      Parse CSS/JSON files for color palettes
      Context-aware slide generation from attached documents
    
    - ENHANCED THEMING: 8 premium themes (Corporate Blue, Executive Dark, Tech Neon, etc.)
      4 animation styles (Professional, Dynamic, Minimal, Impressive)
      7 audience presets with style descriptions
    
    - IMPROVED UX: Drag-and-drop file upload zone
      Progress bar during presentation playback
      Enhanced HTML export with autoplay and keyboard navigation
      File size display and color palette preview
  • feat: Add Slides Generator tool with multi-language support and HTML5 presentation design
    - Added SlidesPresentation and Slide types
    - Implemented generateSlides method in all services (Qwen, Ollama, Z.AI)
    - Created stunning SlidesGenerator component with:
      - 18 language support (English, Chinese, Spanish, French, etc.)
      - 6 theme options (Corporate, Modern, Minimal, Dark, Vibrant, Gradient)
      - 7 audience presets (Executives, Investors, Technical, etc.)
      - HTML5 slide preview with navigation
      - Fullscreen presentation mode
      - Auto-play functionality
      - Export to standalone HTML file
      - Slide thumbnails and speaker notes
    - Updated sidebar navigation with new Slides Generator menu item
    - Updated store with slidesPresentation state management
15 changed files with 3229 additions and 16 deletions

View File

@@ -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;
}
}

View File

@@ -7,6 +7,8 @@ import PromptEnhancer from "@/components/PromptEnhancer";
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";
@@ -29,6 +31,10 @@ export default function Home() {
return <ActionPlanGenerator />;
case "uxdesigner":
return <UXDesignerPrompt />;
case "slides":
return <SlidesGenerator />;
case "googleads":
return <GoogleAdsGenerator />;
case "history":
return <HistoryPanel />;
case "settings":
@@ -49,3 +55,4 @@ export default function Home() {
</div>
);
}

View File

@@ -0,0 +1,533 @@
"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 } from "lucide-react";
import { cn } from "@/lib/utils";
import { GoogleAdsResult } 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<string[]>([""]);
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 [copied, setCopied] = useState(false);
const [expandedSections, setExpandedSections] = useState<string[]>(["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]);
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 handleGenerate = async () => {
if (!websiteUrl.trim()) {
setError("Please enter a website 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);
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: "USD" },
campaignDuration: duration,
industry,
language: "English"
}, 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("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("[GoogleAdsGenerator] Generation error:", err);
setError(err instanceof Error ? err.message : "An error occurred");
} finally {
setProcessing(false);
}
};
const handleCopy = async () => {
if (googleAdsResult?.rawContent) {
await navigator.clipboard.writeText(googleAdsResult.rawContent);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
const sections = [
{ id: "keywords", title: "Keywords Research" },
{ id: "adcopies", title: "Ad Copy Variations" },
{ id: "campaigns", title: "Campaign Structure" },
{ id: "implementation", title: "Implementation Guide" },
];
const renderSectionContent = (sectionId: string) => {
if (!googleAdsResult) return null;
switch (sectionId) {
case "keywords":
return (
<div className="space-y-3">
{googleAdsResult.keywords?.primary?.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-muted-foreground uppercase mb-2">Primary Keywords</h4>
<div className="flex flex-wrap gap-1.5">
{googleAdsResult.keywords.primary.map((k, i) => (
<span key={i} className="text-xs bg-primary/10 text-primary px-2 py-1 rounded-md">
{k.keyword} {k.cpc && <span className="opacity-60">({k.cpc})</span>}
</span>
))}
</div>
</div>
)}
{googleAdsResult.keywords?.longTail?.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-muted-foreground uppercase mb-2">Long-Tail Keywords</h4>
<div className="flex flex-wrap gap-1.5">
{googleAdsResult.keywords.longTail.map((k, i) => (
<span key={i} className="text-xs bg-blue-50 text-blue-700 px-2 py-1 rounded-md">
{k.keyword}
</span>
))}
</div>
</div>
)}
{googleAdsResult.keywords?.negative?.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-muted-foreground uppercase mb-2">Negative Keywords</h4>
<div className="flex flex-wrap gap-1.5">
{googleAdsResult.keywords.negative.map((k, i) => (
<span key={i} className="text-xs bg-red-50 text-red-700 px-2 py-1 rounded-md line-through">
{k.keyword}
</span>
))}
</div>
</div>
)}
</div>
);
case "adcopies":
return (
<div className="space-y-4">
{googleAdsResult.adCopies?.map((ad, i) => (
<div key={i} className="p-3 rounded-md border bg-muted/20">
<div className="text-[10px] uppercase text-muted-foreground mb-2">Ad Variation {i + 1}</div>
<div className="space-y-1 mb-2">
{ad.headlines?.map((h, j) => (
<div key={j} className="text-sm font-medium text-blue-600">{h}</div>
))}
</div>
<div className="space-y-1">
{ad.descriptions?.map((d, j) => (
<p key={j} className="text-xs text-muted-foreground">{d}</p>
))}
</div>
</div>
))}
</div>
);
case "campaigns":
return (
<div className="space-y-3">
{googleAdsResult.campaigns?.map((camp, i) => (
<div key={i} className="p-3 rounded-md border">
<div className="flex justify-between items-start mb-2">
<div>
<div className="font-medium text-sm">{camp.name}</div>
<div className="text-[10px] text-muted-foreground uppercase">{camp.type}</div>
</div>
{camp.budget && (
<div className="text-right text-xs">
<div className="font-semibold">${camp.budget.monthly}/mo</div>
<div className="text-muted-foreground">${camp.budget.daily}/day</div>
</div>
)}
</div>
{camp.adGroups?.length > 0 && (
<div className="mt-2 pt-2 border-t">
<div className="text-[10px] uppercase text-muted-foreground mb-1">Ad Groups</div>
<div className="flex flex-wrap gap-1">
{camp.adGroups.map((g, j) => (
<span key={j} className="text-[10px] bg-muted px-1.5 py-0.5 rounded">{g.name}</span>
))}
</div>
</div>
)}
</div>
))}
</div>
);
case "implementation":
return (
<div className="space-y-3">
{googleAdsResult.implementation?.setupSteps?.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-muted-foreground uppercase mb-2">Setup Steps</h4>
<ol className="list-decimal list-inside space-y-1 text-xs">
{googleAdsResult.implementation.setupSteps.map((step, i) => (
<li key={i}>{step}</li>
))}
</ol>
</div>
)}
{googleAdsResult.implementation?.qualityScoreTips?.length > 0 && (
<div>
<h4 className="text-xs font-semibold text-muted-foreground uppercase mb-2">Quality Score Tips</h4>
<ul className="list-disc list-inside space-y-1 text-xs">
{googleAdsResult.implementation.qualityScoreTips.map((tip, i) => (
<li key={i}>{tip}</li>
))}
</ul>
</div>
)}
</div>
);
default:
return <pre className="whitespace-pre-wrap text-xs">{googleAdsResult.rawContent}</pre>;
}
};
return (
<div className="mx-auto grid max-w-7xl gap-4 lg:gap-6 grid-cols-1 lg:grid-cols-2">
<Card className="h-fit">
<CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center gap-2 text-base lg:text-lg">
<Megaphone className="h-4 w-4 lg:h-5 lg:w-5" />
Google Ads Generator
</CardTitle>
<CardDescription className="text-xs lg:text-sm">
Generate keywords, ad copy, and campaign structure for Google Ads
</CardDescription>
</CardHeader>
<CardContent className="space-y-3 lg:space-y-4 p-4 lg:p-6 pt-0 lg:pt-0">
<div className="space-y-2">
<label className="text-xs lg:text-sm font-medium">AI Provider</label>
<div className="flex flex-wrap gap-1.5 lg:gap-2">
{(["qwen", "ollama", "zai"] as const).map((provider) => (
<Button
key={provider}
variant={selectedProvider === provider ? "default" : "outline"}
size="sm"
onClick={() => setSelectedProvider(provider)}
className="capitalize text-xs lg:text-sm h-8 lg:h-9 px-2.5 lg:px-3"
>
{provider === "qwen" ? "Qwen" : provider === "ollama" ? "Ollama" : "Z.AI"}
</Button>
))}
</div>
</div>
<div className="space-y-2">
<label className="text-xs lg:text-sm font-medium">Model</label>
<select
value={selectedModel}
onChange={(e) => setSelectedModel(selectedProvider, e.target.value)}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-xs lg:text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
{models.map((model) => (
<option key={model} value={model}>
{model}
</option>
))}
</select>
</div>
<div className="space-y-2">
<label className="text-xs lg:text-sm font-medium">Website URL</label>
<Input
placeholder="e.g., www.your-business.com"
value={websiteUrl}
onChange={(e) => setWebsiteUrl(e.target.value)}
className="text-sm"
/>
</div>
<div className="space-y-2">
<label className="text-xs lg:text-sm font-medium">Products / Services</label>
<div className="space-y-2">
{products.map((product, index) => (
<div key={index} className="flex gap-2">
<Input
placeholder={`Product ${index + 1}`}
value={product}
onChange={(e) => updateProduct(index, e.target.value)}
className="text-sm"
/>
{products.length > 1 && (
<Button
variant="ghost"
size="icon"
onClick={() => removeProduct(index)}
className="h-10 w-10 shrink-0"
>
<X className="h-4 w-4" />
</Button>
)}
</div>
))}
<Button variant="outline" size="sm" onClick={addProduct} className="w-full text-xs">
<Plus className="mr-1.5 h-3.5 w-3.5" />
Add Product
</Button>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="space-y-2">
<label className="text-xs lg:text-sm font-medium">Budget (USD/mo)</label>
<div className="flex items-center gap-1.5">
<Input
type="number"
placeholder="Min"
value={budgetMin}
onChange={(e) => setBudgetMin(e.target.value)}
className="text-sm"
/>
<span className="text-muted-foreground text-xs">-</span>
<Input
type="number"
placeholder="Max"
value={budgetMax}
onChange={(e) => setBudgetMax(e.target.value)}
className="text-sm"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-xs lg:text-sm font-medium">Industry</label>
<Input
placeholder="e.g., SaaS"
value={industry}
onChange={(e) => setIndustry(e.target.value)}
className="text-sm"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-xs lg:text-sm font-medium">Target Audience</label>
<Textarea
placeholder="e.g., Small business owners in USA looking for productivity tools"
value={targetAudience}
onChange={(e) => setTargetAudience(e.target.value)}
className="min-h-[80px] lg:min-h-[100px] resize-y text-sm"
/>
</div>
{error && (
<div className="rounded-md bg-destructive/10 p-2.5 lg:p-3 text-xs lg:text-sm text-destructive">
{error}
{!apiKeys[selectedProvider] && (
<div className="mt-1.5 lg:mt-2 flex items-center gap-2">
<Settings className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
<span className="text-[10px] lg:text-xs">Configure API key in Settings</span>
</div>
)}
</div>
)}
<Button onClick={handleGenerate} disabled={isProcessing || !websiteUrl.trim()} className="w-full h-9 lg:h-10 text-xs lg:text-sm">
{isProcessing ? (
<>
<Loader2 className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4 animate-spin" />
Generating Ads...
</>
) : (
<>
<Megaphone className="mr-1.5 lg:mr-2 h-3.5 w-3.5 lg:h-4 lg:w-4" />
Generate Google Ads
</>
)}
</Button>
</CardContent>
</Card>
<Card className={cn(!googleAdsResult && "opacity-50")}>
<CardHeader className="p-4 lg:p-6">
<CardTitle className="flex items-center justify-between text-base lg:text-lg">
<span className="flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 lg:h-5 lg:w-5 text-green-500" />
Generated Campaign
</span>
{googleAdsResult && (
<Button variant="ghost" size="icon" onClick={handleCopy} className="h-8 w-8 lg:h-9 lg:w-9">
{copied ? (
<CheckCircle2 className="h-3.5 w-3.5 lg:h-4 lg:w-4 text-green-500" />
) : (
<Copy className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)}
</Button>
)}
</CardTitle>
<CardDescription className="text-xs lg:text-sm">
Keywords, ad copy, and campaign structure ready for Google Ads
</CardDescription>
</CardHeader>
<CardContent className="p-4 lg:p-6 pt-0 lg:pt-0">
{googleAdsResult ? (
<div className="space-y-2 lg:space-y-3">
{sections.map((section) => (
<div key={section.id} className="rounded-md border bg-muted/30">
<button
onClick={() => toggleSection(section.id)}
className="flex w-full items-center justify-between px-3 lg:px-4 py-2.5 lg:py-3 text-left font-medium transition-colors hover:bg-muted/50 text-xs lg:text-sm"
>
<span>{section.title}</span>
{expandedSections.includes(section.id) ? (
<ChevronUp className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
) : (
<ChevronDown className="h-3.5 w-3.5 lg:h-4 lg:w-4" />
)}
</button>
{expandedSections.includes(section.id) && (
<div className="border-t bg-background px-3 lg:px-4 py-2.5 lg:py-3">
{renderSectionContent(section.id)}
</div>
)}
</div>
))}
</div>
) : (
<div className="flex h-[200px] lg:h-[300px] items-center justify-center text-center text-xs lg:text-sm text-muted-foreground">
Generated campaign will appear here
</div>
)}
</CardContent>
</Card>
</div>
);
}

View File

@@ -3,10 +3,10 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import useStore from "@/lib/store";
import { Sparkles, FileText, ListTodo, Palette, 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" | "history" | "settings";
export type View = "enhance" | "prd" | "action" | "uxdesigner" | "slides" | "googleads" | "history" | "settings";
interface SidebarProps {
currentView: View;
@@ -22,6 +22,8 @@ export default function Sidebar({ currentView, onViewChange }: SidebarProps) {
{ id: "prd" as View, label: "PRD Generator", icon: FileText },
{ 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 },
];

File diff suppressed because it is too large Load Diff

36
components/ui/badge.tsx Normal file
View File

@@ -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<HTMLDivElement>,
VariantProps<typeof badgeVariants> { }
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

55
components/ui/tabs.tsx Normal file
View File

@@ -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<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -186,6 +186,46 @@ export class ModelAdapter {
return this.callWithFallback((service) => service.generateUXDesignerPrompt(appDescription, model), providers);
}
async generateSlides(
topic: string,
options: {
language?: string;
theme?: string;
slideCount?: number;
audience?: string;
organization?: string;
animationStyle?: string;
audienceStyle?: string;
themeColors?: string[];
brandColors?: string[];
} = {},
provider?: ModelProvider,
model?: string
): Promise<APIResponse<string>> {
const fallback = this.buildFallbackProviders(this.preferredProvider, "qwen", "ollama", "zai");
const providers: ModelProvider[] = provider ? [provider] : fallback;
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<APIResponse<string>> {
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,
@@ -222,7 +262,7 @@ export class ModelAdapter {
zai: ["glm-4.7", "glm-4.5", "glm-4.5-air", "glm-4-flash", "glm-4-flashx"],
};
const models: Record<ModelProvider, string[]> = { ...fallbackModels };
if (provider === "ollama" || !provider) {
try {
const ollamaModels = await this.ollamaService.listModels();

View File

@@ -303,6 +303,231 @@ Make's prompt specific, inspiring, and comprehensive. Use professional UX termin
return this.chatCompletion([systemMessage, userMessage], model || "gpt-oss:120b");
}
async generateSlides(
topic: string,
options: {
language?: string;
theme?: string;
slideCount?: number;
audience?: string;
organization?: string;
animationStyle?: string;
audienceStyle?: string;
themeColors?: string[];
brandColors?: string[];
} = {},
model?: string
): Promise<APIResponse<string>> {
const {
language = "English",
theme = "executive-dark",
slideCount = 10,
audience = "Executives & C-Suite",
organization = "",
animationStyle = "Professional",
audienceStyle = "Sophisticated, data-driven, strategic focus",
themeColors = ["#09090b", "#6366f1", "#a855f7", "#fafafa"],
brandColors = []
} = options;
const [bgColor, primaryColor, secondaryColor, textColor] = themeColors;
const brandColorStr = brandColors.length > 0
? `\nBRAND COLORS TO USE: ${brandColors.join(", ")}`
: "";
const systemMessage: ChatMessage = {
role: "system",
content: `You are a WORLD-CLASS presentation designer who creates STUNNING, AWARD-WINNING slide decks that rival McKinsey, Apple, and TED presentations.
Your slides must be VISUALLY SPECTACULAR with:
- Modern CSS3 animations (fade-in, slide-in, scale, parallax effects)
- Sophisticated gradient backgrounds with depth
- SVG charts and data visualizations inline
- Glassmorphism and neumorphism effects
- Professional typography with Inter/SF Pro fonts
- Strategic use of whitespace
- Micro-animations on hover/focus states
- Progress indicators and visual hierarchy
OUTPUT FORMAT - Return ONLY valid JSON:
\`\`\`json
{
"title": "Presentation Title",
"subtitle": "Compelling Subtitle",
"theme": "${theme}",
"language": "${language}",
"slides": [
{
"id": "slide-1",
"title": "Slide Title",
"content": "Plain text content summary",
"htmlContent": "<div>FULL HTML with inline CSS and animations</div>",
"notes": "Speaker notes",
"layout": "title|content|two-column|chart|statistics|timeline|quote|comparison",
"order": 1
}
]
}
\`\`\`
DESIGN SYSTEM:
- Primary: ${brandColors[0] || primaryColor}
- Secondary: ${brandColors[1] || secondaryColor}
- Background: ${bgColor}
- Text: ${textColor}${brandColorStr}
ANIMATION STYLE: ${animationStyle}
- Professional: Subtle 0.3-0.5s ease transitions, fade and slide
- Dynamic: 0.5-0.8s spring animations, emphasis effects, stagger delays
- Impressive: Bold 0.8-1.2s animations, parallax, morphing, particle effects
CSS ANIMATIONS TO INCLUDE:
\`\`\`css
@keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-50px); } to { opacity: 1; transform: translateX(0); } }
@keyframes scaleIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
\`\`\`
SLIDE TYPES TO CREATE:
1. TITLE SLIDE: Hero-style with animated gradient background, large typography
2. AGENDA/OVERVIEW: Icon grid with staggered fade-in animations
3. DATA/CHARTS: Inline SVG bar/line/pie charts with animated drawing effects
4. KEY METRICS: Large animated numbers with KPI cards
5. TIMELINE: Horizontal/vertical timeline with sequential reveal animations
6. COMPARISON: Side-by-side cards with hover lift effects
7. QUOTE: Large typography with decorative quote marks
8. CALL-TO-ACTION: Bold CTA with pulsing button effect
TARGET AUDIENCE: ${audience}
AUDIENCE STYLE: ${audienceStyle}
${organization ? `ORGANIZATION BRANDING: ${organization}` : ""}
REQUIREMENTS:
- Create EXACTLY ${slideCount} slides
- ALL content in ${language}
- Each slide MUST have complete htmlContent with inline <style> tags
- Use animation-delay for staggered reveal effects
- Include decorative background elements (gradients, shapes)
- Ensure text contrast meets WCAG AA standards
- Add subtle shadow/glow effects for depth`,
};
const userMessage: ChatMessage = {
role: "user",
content: `Create a STUNNING, ANIMATED presentation about:
${topic}
SPECIFICATIONS:
- Language: ${language}
- Theme: ${theme}
- Slides: ${slideCount}
- Audience: ${audience} (${audienceStyle})
- Animation Style: ${animationStyle}
${organization ? `- Organization: ${organization}` : ""}
${brandColors.length > 0 ? `- Brand Colors: ${brandColors.join(", ")}` : ""}
Generate SPECTACULAR slides with CSS3 animations, SVG charts, modern gradients, and corporate-ready design!`,
};
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<APIResponse<string>> {
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;

View File

@@ -631,6 +631,229 @@ Make's prompt specific, inspiring, and comprehensive. Use professional UX termin
return this.chatCompletion([systemMessage, userMessage], model || "coder-model");
}
async generateSlides(
topic: string,
options: {
language?: string;
theme?: string;
slideCount?: number;
audience?: string;
organization?: string;
animationStyle?: string;
audienceStyle?: string;
themeColors?: string[];
brandColors?: string[];
} = {},
model?: string
): Promise<APIResponse<string>> {
const {
language = "English",
theme = "executive-dark",
slideCount = 10,
audience = "Executives & C-Suite",
organization = "",
animationStyle = "Professional",
audienceStyle = "Sophisticated, data-driven, strategic focus",
themeColors = ["#09090b", "#6366f1", "#a855f7", "#fafafa"],
brandColors = []
} = options;
const [bgColor, primaryColor, secondaryColor, textColor] = themeColors;
const brandColorStr = brandColors.length > 0
? `\nBRAND COLORS TO USE: ${brandColors.join(", ")}`
: "";
const systemMessage: ChatMessage = {
role: "system",
content: `You are a WORLD-CLASS presentation designer who creates STUNNING, AWARD-WINNING slide decks that rival McKinsey, Apple, and TED presentations.
Your slides must be VISUALLY SPECTACULAR with:
- Modern CSS3 animations (fade-in, slide-in, scale, parallax effects)
- Sophisticated gradient backgrounds with depth
- SVG charts and data visualizations inline
- Glassmorphism and neumorphism effects
- Professional typography with Inter/SF Pro fonts
- Strategic use of whitespace
- Micro-animations on hover/focus states
- Progress indicators and visual hierarchy
OUTPUT FORMAT - Return ONLY valid JSON:
\`\`\`json
{
"title": "Presentation Title",
"subtitle": "Compelling Subtitle",
"theme": "${theme}",
"language": "${language}",
"slides": [
{
"id": "slide-1",
"title": "Slide Title",
"content": "Plain text content summary",
"htmlContent": "<div>FULL HTML with inline CSS and animations</div>",
"notes": "Speaker notes",
"layout": "title|content|two-column|chart|statistics|timeline|quote|comparison",
"order": 1
}
]
}
\`\`\`
DESIGN SYSTEM:
- Primary: ${brandColors[0] || primaryColor}
- Secondary: ${brandColors[1] || secondaryColor}
- Background: ${bgColor}
- Text: ${textColor}${brandColorStr}
ANIMATION STYLE: ${animationStyle}
- Professional: Subtle 0.3-0.5s ease transitions, fade and slide
- Dynamic: 0.5-0.8s spring animations, emphasis effects, stagger delays
- Impressive: Bold 0.8-1.2s animations, parallax, morphing, particle effects
CSS ANIMATIONS TO INCLUDE:
\`\`\`css
@keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-50px); } to { opacity: 1; transform: translateX(0); } }
@keyframes scaleIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
\`\`\`
SLIDE TYPES TO CREATE:
1. TITLE SLIDE: Hero-style with animated gradient background, large typography
2. AGENDA/OVERVIEW: Icon grid with staggered fade-in animations
3. DATA/CHARTS: Inline SVG bar/line/pie charts with animated drawing effects
4. KEY METRICS: Large animated numbers with KPI cards
5. TIMELINE: Horizontal/vertical timeline with sequential reveal animations
6. COMPARISON: Side-by-side cards with hover lift effects
7. QUOTE: Large typography with decorative quote marks
8. CALL-TO-ACTION: Bold CTA with pulsing button effect
TARGET AUDIENCE: ${audience}
AUDIENCE STYLE: ${audienceStyle}
${organization ? `ORGANIZATION BRANDING: ${organization}` : ""}
REQUIREMENTS:
- Create EXACTLY ${slideCount} slides
- ALL content in ${language}
- Each slide MUST have complete htmlContent with inline <style> tags
- Use animation-delay for staggered reveal effects
- Include decorative background elements (gradients, shapes)
- Ensure text contrast meets WCAG AA standards
- Add subtle shadow/glow effects for depth`,
};
const userMessage: ChatMessage = {
role: "user",
content: `Create a STUNNING, ANIMATED presentation about:
${topic}
SPECIFICATIONS:
- Language: ${language}
- Theme: ${theme}
- Slides: ${slideCount}
- Audience: ${audience} (${audienceStyle})
- Animation Style: ${animationStyle}
${organization ? `- Organization: ${organization}` : ""}
${brandColors.length > 0 ? `- Brand Colors: ${brandColors.join(", ")}` : ""}
Generate SPECTACULAR slides with CSS3 animations, SVG charts, modern gradients, and corporate-ready design!`,
};
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<APIResponse<string>> {
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<APIResponse<string[]>> {
const models = [
"coder-model",
@@ -648,3 +871,5 @@ Make's prompt specific, inspiring, and comprehensive. Use professional UX termin
const qwenOAuthService = new QwenOAuthService();
export default qwenOAuthService;
export { qwenOAuthService };

View File

@@ -63,7 +63,7 @@ export class ZaiPlanService {
const data = await response.json();
console.log("[Z.AI] Response data:", data);
if (data.choices && data.choices[0] && data.choices[0].message) {
return { success: true, data: data.choices[0].message.content };
} else if (data.output && data.output.choices && data.output.choices[0]) {
@@ -168,7 +168,7 @@ Include specific recommendations for:
const data = await response.json();
const models = data.data?.map((m: any) => m.id) || [];
return { success: true, data: models };
} else {
console.log("[Z.AI] No API key, using fallback models");
@@ -251,6 +251,362 @@ Make the prompt specific, inspiring, and comprehensive. Use professional UX term
return this.chatCompletion([systemMessage, userMessage], model || "glm-4.7", true);
}
async generateSlides(
topic: string,
options: {
language?: string;
theme?: string;
slideCount?: number;
audience?: string;
organization?: string;
animationStyle?: string;
audienceStyle?: string;
themeColors?: string[];
brandColors?: string[];
} = {},
model?: string
): Promise<APIResponse<string>> {
const {
language = "English",
theme = "executive-dark",
slideCount = 10,
audience = "Executives & C-Suite",
organization = "",
animationStyle = "Professional",
audienceStyle = "Sophisticated, data-driven, strategic focus",
themeColors = ["#09090b", "#6366f1", "#a855f7", "#fafafa"],
brandColors = []
} = options;
const [bgColor, primaryColor, secondaryColor, textColor] = themeColors;
const brandColorStr = brandColors.length > 0
? `\nBRAND COLORS TO USE: ${brandColors.join(", ")}`
: "";
const systemMessage: ChatMessage = {
role: "system",
content: `You are a WORLD-CLASS presentation designer who creates STUNNING, AWARD-WINNING slide decks that rival McKinsey, Apple, and TED presentations.
Your slides must be VISUALLY SPECTACULAR with:
- Modern CSS3 animations (fade-in, slide-in, scale, parallax effects)
- Sophisticated gradient backgrounds with depth
- SVG charts and data visualizations inline
- Glassmorphism and neumorphism effects
- Professional typography with Inter/SF Pro fonts
- Strategic use of whitespace
- Micro-animations on hover/focus states
- Progress indicators and visual hierarchy
OUTPUT FORMAT - Return ONLY valid JSON:
\`\`\`json
{
"title": "Presentation Title",
"subtitle": "Compelling Subtitle",
"theme": "${theme}",
"language": "${language}",
"slides": [
{
"id": "slide-1",
"title": "Slide Title",
"content": "Plain text content summary",
"htmlContent": "<div>FULL HTML with inline CSS and animations</div>",
"notes": "Speaker notes",
"layout": "title|content|two-column|chart|statistics|timeline|quote|comparison",
"order": 1
}
]
}
\`\`\`
DESIGN SYSTEM:
- Primary: ${brandColors[0] || primaryColor}
- Secondary: ${brandColors[1] || secondaryColor}
- Background: ${bgColor}
- Text: ${textColor}${brandColorStr}
ANIMATION STYLE: ${animationStyle}
- Professional: Subtle 0.3-0.5s ease transitions, fade and slide
- Dynamic: 0.5-0.8s spring animations, emphasis effects, stagger delays
- Impressive: Bold 0.8-1.2s animations, parallax, morphing, particle effects
CSS ANIMATIONS TO INCLUDE:
\`\`\`css
@keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-50px); } to { opacity: 1; transform: translateX(0); } }
@keyframes scaleIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }
@keyframes gradientShift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
\`\`\`
SLIDE TYPES TO CREATE:
1. TITLE SLIDE: Hero-style with animated gradient background, large typography, subtle floating elements
2. AGENDA/OVERVIEW: Icon grid with staggered fade-in animations
3. DATA/CHARTS: Inline SVG bar/line/pie charts with animated drawing effects
4. KEY METRICS: Large animated numbers with counting effect styling, KPI cards with glassmorphism
5. TIMELINE: Horizontal/vertical timeline with sequential reveal animations
6. COMPARISON: Side-by-side cards with hover lift effects
7. QUOTE: Large typography with decorative quote marks, subtle background pattern
8. CALL-TO-ACTION: Bold CTA with pulsing button effect, clear next steps
SVG CHART EXAMPLE:
\`\`\`html
<svg viewBox="0 0 400 200" style="width:100%;max-width:400px;">
<defs>
<linearGradient id="barGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:${primaryColor}"/>
<stop offset="100%" style="stop-color:${secondaryColor}"/>
</linearGradient>
</defs>
<rect x="50" y="50" width="60" height="130" fill="url(#barGrad)" rx="8" style="animation: scaleIn 0.8s ease-out 0.2s both; transform-origin: bottom;"/>
<rect x="130" y="80" width="60" height="100" fill="url(#barGrad)" rx="8" style="animation: scaleIn 0.8s ease-out 0.4s both; transform-origin: bottom;"/>
<rect x="210" y="30" width="60" height="150" fill="url(#barGrad)" rx="8" style="animation: scaleIn 0.8s ease-out 0.6s both; transform-origin: bottom;"/>
</svg>
\`\`\`
TARGET AUDIENCE: ${audience}
AUDIENCE STYLE: ${audienceStyle}
${organization ? `ORGANIZATION BRANDING: ${organization}` : ""}
REQUIREMENTS:
- Create EXACTLY ${slideCount} slides
- ALL content in ${language}
- Each slide MUST have complete htmlContent with inline <style> tags
- Use animation-delay for staggered reveal effects
- Include decorative background elements (gradients, shapes, patterns)
- Ensure text contrast meets WCAG AA standards
- Add subtle shadow/glow effects for depth
- Include progress/slide number indicator styling`,
};
const userMessage: ChatMessage = {
role: "user",
content: `Create a STUNNING, ANIMATED presentation about:
${topic}
SPECIFICATIONS:
- Language: ${language}
- Theme: ${theme}
- Slides: ${slideCount}
- Audience: ${audience} (${audienceStyle})
- Animation Style: ${animationStyle}
${organization ? `- Organization: ${organization}` : ""}
${brandColors.length > 0 ? `- Brand Colors: ${brandColors.join(", ")}` : ""}
Generate SPECTACULAR slides with:
✨ Animated CSS3 transitions and keyframes
📊 SVG charts and data visualizations where relevant
🎨 Modern gradients and glassmorphism effects
💫 Staggered reveal animations
🏢 Corporate-ready, executive-level design
Return the complete JSON with full htmlContent for each slide. Make each slide VISUALLY IMPRESSIVE and memorable!`,
};
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<APIResponse<string>> {
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;

View File

@@ -1,11 +1,13 @@
import { create } from "zustand";
import type { ModelProvider, PromptEnhancement, PRD, ActionPlan } from "@/types";
import type { ModelProvider, PromptEnhancement, PRD, ActionPlan, SlidesPresentation, GoogleAdsResult } from "@/types";
interface AppState {
currentPrompt: string;
enhancedPrompt: string | null;
prd: PRD | null;
actionPlan: ActionPlan | null;
slidesPresentation: SlidesPresentation | null;
googleAdsResult: GoogleAdsResult | null;
selectedProvider: ModelProvider;
selectedModels: Record<ModelProvider, string>;
availableModels: Record<ModelProvider, string[]>;
@@ -27,6 +29,8 @@ interface AppState {
setEnhancedPrompt: (enhanced: string | null) => void;
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;
@@ -44,6 +48,8 @@ const useStore = create<AppState>((set) => ({
enhancedPrompt: null,
prd: null,
actionPlan: null,
slidesPresentation: null,
googleAdsResult: null,
selectedProvider: "qwen",
selectedModels: {
qwen: "coder-model",
@@ -68,6 +74,8 @@ const useStore = create<AppState>((set) => ({
setEnhancedPrompt: (enhanced) => set({ enhancedPrompt: enhanced }),
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) => ({
@@ -102,6 +110,8 @@ const useStore = create<AppState>((set) => ({
enhancedPrompt: null,
prd: null,
actionPlan: null,
slidesPresentation: null,
googleAdsResult: null,
error: null,
}),
}));

304
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -91,3 +91,114 @@ export interface ChatMessage {
role: "system" | "user" | "assistant";
content: string;
}
export interface Slide {
id: string;
title: string;
content: string;
htmlContent: string;
notes?: string;
layout: "title" | "content" | "two-column" | "image-left" | "image-right" | "quote" | "statistics" | "timeline" | "comparison";
order: number;
}
export interface SlidesPresentation {
id: string;
title: string;
subtitle?: string;
author?: string;
organization?: string;
theme: "corporate" | "modern" | "minimal" | "dark" | "vibrant" | "gradient";
language: string;
slides: Slide[];
rawContent: string;
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;
}