From 7a8e6f7027f6f67c17ffb80479e5e2d0a8c7294b Mon Sep 17 00:00:00 2001 From: pycomet Date: Thu, 10 Jul 2025 22:55:52 +0100 Subject: [PATCH 1/3] refactor: remove fal.ai provider and update thumbnail generation logic to focus on stability and huggingface --- app/api/generate-thumbnail/route.ts | 6 +- app/api/test-ai/route.ts | 88 +-- app/dashboard/page.tsx | 1049 ++++++++------------------- lib/ai/fal.ts | 277 ------- lib/ai/huggingface.ts | 4 +- lib/ai/index.ts | 2 +- lib/ai/providers.ts | 65 +- lib/ai/stability.ts | 20 +- 8 files changed, 339 insertions(+), 1172 deletions(-) delete mode 100644 lib/ai/fal.ts diff --git a/app/api/generate-thumbnail/route.ts b/app/api/generate-thumbnail/route.ts index e5acc75..76f8290 100644 --- a/app/api/generate-thumbnail/route.ts +++ b/app/api/generate-thumbnail/route.ts @@ -57,7 +57,7 @@ export async function POST(request: NextRequest) { } // Validate parameters - const validProviders = ["huggingface", "stability", "fal"]; + const validProviders = ["huggingface", "stability"]; const validQualities = ["fast", "balanced", "high"]; const validStyles = ["tech", "gaming", "tutorial", "lifestyle"]; @@ -98,10 +98,6 @@ export async function POST(request: NextRequest) { apiKeyMissing = !process.env.STABILITY_API_KEY; apiKeyName = "STABILITY_API_KEY"; break; - case "fal": - apiKeyMissing = !process.env.FAL_KEY; - apiKeyName = "FAL_KEY"; - break; } if (apiKeyMissing) { diff --git a/app/api/test-ai/route.ts b/app/api/test-ai/route.ts index cd42eff..6fb6a5a 100644 --- a/app/api/test-ai/route.ts +++ b/app/api/test-ai/route.ts @@ -1,65 +1,41 @@ -import { NextRequest, NextResponse } from "next/server"; -import { testConnection, testThumbnailGeneration } from "@/lib/ai/huggingface"; +import { NextResponse } from "next/server"; +import { testAllProviders, getProviderStatus } from "@/lib/ai"; -export async function POST(request: NextRequest) { +export async function GET() { try { - console.log("๐Ÿ” Starting AI service test..."); - console.log("Request body:", await request.text()); - - // Check if API key is available - if (!process.env.HUGGINGFACE_API_KEY) { - return NextResponse.json( - { - success: false, - error: "HUGGINGFACE_API_KEY environment variable is not set", - type: "configuration", - }, - { status: 500 } - ); - } - - // Test basic connection - console.log("Testing basic connection..."); - const connectionTest = await testConnection(); - - if (!connectionTest) { - return NextResponse.json( - { - success: false, - error: "HuggingFace API connection failed", - type: "connection", - }, - { status: 503 } - ); - } - - // Test thumbnail generation - console.log("Testing thumbnail generation..."); - await testThumbnailGeneration(); - + console.log("๐Ÿงช Testing AI Providers..."); + + // Test environment variables + const envStatus = { + HUGGINGFACE_API_KEY: process.env.HUGGINGFACE_API_KEY ? "Set" : "Not Set", + STABILITY_API_KEY: process.env.STABILITY_API_KEY ? "Set" : "Not Set", + }; + + console.log("Environment variables:", envStatus); + + // Test all providers + const providerResults = await testAllProviders(); + console.log("Provider test results:", providerResults); + + // Get detailed provider status + const providerStatus = await getProviderStatus(); + console.log("Provider status:", providerStatus); + return NextResponse.json({ success: true, - message: "AI service is working correctly", + environmentVariables: envStatus, + providerResults, + providerStatus, timestamp: new Date().toISOString(), }); + } catch (error) { - console.error("โŒ AI service test failed:", error); - - return NextResponse.json( - { - success: false, - error: error instanceof Error ? error.message : "Unknown error", - type: "test_failed", - timestamp: new Date().toISOString(), - }, - { status: 500 } - ); + console.error("โŒ Test failed:", error); + + return NextResponse.json({ + success: false, + error: error instanceof Error ? error.message : "Unknown error", + timestamp: new Date().toISOString(), + }, { status: 500 }); } } - -export async function GET() { - return NextResponse.json({ - message: "AI Test API", - usage: "Send a POST request to test the AI service", - }); -} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 8589a9f..4acb059 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -8,10 +8,13 @@ import { CardHeader, Image, Chip, - Divider, + Select, + SelectItem, Progress, - Badge, Spinner, + RadioGroup, + Radio, + Textarea, } from "@nextui-org/react"; import { PageLayout } from "@/components/layouts/pageLayout"; import { useUser } from "@/contexts/userContext"; @@ -19,19 +22,12 @@ import { useMessage } from "@/contexts/messageContext"; import { title, subtitle, button } from "@/components/primitives"; import { useRouter, useSearchParams } from "next/navigation"; import { AnimatedDiv } from "@/components/motion"; -import { SearchIcon, RightArrowIcon } from "@/components/icons"; -import { motion } from "framer-motion"; -import { Cpu, Clock, AlertCircle, RefreshCw, Globe } from "lucide-react"; -import { - getAvailableProviders, - getModelsForProvider, - type AIProvider, - type AIModel, -} from "@/lib/ai"; +import { SearchIcon, RightArrowIcon, Download, Sparkles, Zap, Shield } from "lucide-react"; type ThumbnailStyle = "tech" | "gaming" | "tutorial" | "lifestyle"; interface GenerationResult { + success: boolean; imageUrl: string; prompt: string; style: string; @@ -40,216 +36,130 @@ interface GenerationResult { parameters: { steps: number; guidance_scale: number; - negative_prompt: string; + width: number; + height: number; }; - timestamp: string; } -// Error types for better error handling interface ErrorInfo { - type: "validation" | "network" | "api" | "quota" | "model" | "unknown"; + type: "validation" | "api" | "network" | "quota" | "model"; message: string; - suggestion?: string; retryable: boolean; } -// Error categorization helper -const categorizeError = (error: any): ErrorInfo => { - const errorMessage = error?.message || error?.toString() || "Unknown error"; - const errorLower = errorMessage.toLowerCase(); - - // Network errors - if ( - errorLower.includes("network") || - errorLower.includes("fetch") || - errorLower.includes("connection") - ) { - return { - type: "network", - message: "Unable to connect to our servers", - suggestion: "Please check your internet connection and try again", - retryable: true, - }; - } - - // API quota errors - if ( - errorLower.includes("quota") || - errorLower.includes("limit") || - errorLower.includes("rate") - ) { - return { - type: "quota", - message: "Generation limit reached", - suggestion: "Please try again in a few minutes or consider upgrading", - retryable: true, - }; - } - - // Model errors - if ( - errorLower.includes("model") || - errorLower.includes("unavailable") || - errorLower.includes("loading") - ) { - return { - type: "model", - message: "AI model is temporarily unavailable", - suggestion: "Try switching to a different model or wait a moment", - retryable: true, - }; - } - - // Validation errors - if ( - errorLower.includes("invalid") || - errorLower.includes("required") || - errorLower.includes("validation") - ) { - return { - type: "validation", - message: errorMessage, - suggestion: "Please check your input and try again", - retryable: false, - }; - } - - // API errors - if ( - errorLower.includes("api") || - errorLower.includes("server") || - errorLower.includes("500") - ) { - return { - type: "api", - message: "Server error occurred", - suggestion: - "Our servers are experiencing issues. Please try again shortly", - retryable: true, - }; - } - - // Unknown errors - return { - type: "unknown", - message: "Something went wrong", - suggestion: "Please try again or contact support if the problem persists", - retryable: true, - }; -}; +const authDisabled = process.env.NODE_ENV === "development"; +// Simplified style options with visual indicators const styleOptions = [ { key: "tech", - label: "Tech Review", - description: "Modern, clean, professional", - gradient: "from-blue-500 to-cyan-500", + label: "Tech & Reviews", icon: "๐Ÿ’ป", + description: "Modern, clean tech product presentations", + color: "primary" as const, }, { key: "gaming", label: "Gaming", - description: "Intense, dramatic, neon colors", - gradient: "from-purple-500 to-pink-500", icon: "๐ŸŽฎ", + description: "Vibrant gaming content with energy", + color: "secondary" as const, }, { key: "tutorial", label: "Tutorial", - description: "Educational, clear, step-by-step", - gradient: "from-green-500 to-teal-500", icon: "๐Ÿ“š", + description: "Educational and instructional content", + color: "success" as const, }, { key: "lifestyle", label: "Lifestyle", - description: "Warm, personal, authentic", - gradient: "from-orange-500 to-red-500", icon: "โœจ", + description: "Personal and lifestyle content", + color: "warning" as const, }, ]; -const modelOptions = [ - { - value: "sdxl", - label: "Stable Diffusion XL", - description: "Best balance of quality and speed", - icon: "โšก", - recommended: true, - }, +// Simplified provider options +const providerOptions = [ { - value: "sd15", - label: "Stable Diffusion 1.5", - description: "Fast and reliable generation", - icon: "๐Ÿš€", - recommended: false, + id: "stability", + name: "Stability AI", + description: "Best Quality โ€ข Free", + icon: , + badge: "Recommended", + badgeColor: "success" as const, }, { - value: "sd21", - label: "Stable Diffusion 2.1", - description: "Good quality output", - icon: "๐Ÿ“ธ", - recommended: false, + id: "huggingface", + name: "HuggingFace", + description: "Good Quality โ€ข Free with limits", + icon: , + badge: "Backup", + badgeColor: "primary" as const, }, ]; +// Simplified quality options const qualityOptions = [ - { - value: "fast", - label: "Fast", - description: "Quick generation (~15s)", - icon: "โšก", - recommended: false, - }, - { - value: "balanced", - label: "Balanced", - description: "Good quality (~25s)", - icon: "โš–๏ธ", - recommended: true, - }, - { - value: "high", - label: "High", - description: "Best quality (~40s)", - icon: "๐Ÿ’Ž", - recommended: false, - }, + { value: "fast", label: "Fast", description: "Quick generation (~10s)" }, + { value: "balanced", label: "Balanced", description: "Good quality (~20s)" }, + { value: "high", label: "High", description: "Best quality (~30s)" }, +]; + +// Quick prompt suggestions +const promptSuggestions = [ + "iPhone 15 Pro Max review with surprised reaction", + "Gaming setup tour with RGB lighting", + "How to cook perfect pasta tutorial", + "Morning routine lifestyle content", + "Unboxing the latest tech gadget", + "Minecraft building tutorial castle", ]; -// Intelligent suggestion system - creates short, punchy suggestions -const generateSmartSuggestions = (input: string): string[] => { - const inputLower = input.toLowerCase(); - const suggestions: string[] = []; +function categorizeError(error: any): ErrorInfo { + const errorMessage = error?.message || error?.toString() || "Unknown error"; + const lowerMessage = errorMessage.toLowerCase(); - // Content type suggestions - if (inputLower.includes("review") || inputLower.includes("unboxing")) { - suggestions.push("Tech product review with shocked reaction"); - suggestions.push("Unboxing video with surprised face"); - } - if (inputLower.includes("tutorial") || inputLower.includes("how to")) { - suggestions.push("Step-by-step tutorial with clear instructions"); - suggestions.push("Easy tutorial for beginners"); + if (lowerMessage.includes("enter a video description")) { + return { + type: "validation", + message: "Please enter a description for your content", + retryable: false, + }; } - if (inputLower.includes("gaming") || inputLower.includes("game")) { - suggestions.push("Epic gaming moment with intense action"); - suggestions.push("Gaming highlight reel compilation"); + + if (lowerMessage.includes("rate") || lowerMessage.includes("quota")) { + return { + type: "quota", + message: "Too many requests. Please wait a moment and try again.", + retryable: true, + }; } - if (inputLower.includes("lifestyle") || inputLower.includes("vlog")) { - suggestions.push("Daily lifestyle vlog with authentic moments"); - suggestions.push("Personal story with emotional journey"); + + if (lowerMessage.includes("network") || lowerMessage.includes("fetch")) { + return { + type: "network", + message: "Network error. Please check your connection and try again.", + retryable: true, + }; } - // Generic suggestions if no specific content detected - if (suggestions.length === 0) { - suggestions.push("Exciting content with dramatic reveal"); - suggestions.push("Before and after transformation"); - suggestions.push("Top 10 list with numbered items"); - suggestions.push("Challenge video with surprising outcome"); + if (lowerMessage.includes("api key") || lowerMessage.includes("unauthorized")) { + return { + type: "api", + message: "AI service not configured. Please try a different provider.", + retryable: false, + }; } - return suggestions.slice(0, 3); -}; + return { + type: "api", + message: "Something went wrong. Please try again.", + retryable: true, + }; +} function DashboardContent() { const { user, loading: userLoading } = useUser(); @@ -257,49 +167,15 @@ function DashboardContent() { const router = useRouter(); const searchParams = useSearchParams(); + // Simplified state management const [prompt, setPrompt] = useState(""); const [style, setStyle] = useState("tech"); - const [provider, setProvider] = useState("huggingface"); - const [model, setModel] = useState("sdxl"); + const [provider, setProvider] = useState("stability"); const [quality, setQuality] = useState("balanced"); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(0); const [result, setResult] = useState(null); const [error, setError] = useState(null); - const [retryCount, setRetryCount] = useState(0); - const [suggestions, setSuggestions] = useState([]); - const [refinementPrompt, setRefinementPrompt] = useState(""); - const [hasRefined, setHasRefined] = useState(false); - const [isRefining, setIsRefining] = useState(false); - const [availableProviders, setAvailableProviders] = useState( - [] - ); - const [currentModels, setCurrentModels] = useState([]); - - // Load available providers on component mount - useEffect(() => { - const providers = getAvailableProviders(); - setAvailableProviders(providers); - - // Set default provider based on availability - if (providers.length > 0) { - setProvider(providers[0].id); - } - }, []); - - // Update models when provider changes - useEffect(() => { - if (provider) { - const models = getModelsForProvider(provider); - setCurrentModels(models); - - // Set default model for the provider - if (models.length > 0) { - const recommendedModel = models.find((m) => m.recommended) || models[0]; - setModel(recommendedModel.id); - } - } - }, [provider]); // Handle search parameters useEffect(() => { @@ -309,52 +185,13 @@ function DashboardContent() { } }, [searchParams]); - // Generate suggestions based on user input - useEffect(() => { - if (prompt.trim().length >= 3) { - const newSuggestions = generateSmartSuggestions(prompt); - setSuggestions(newSuggestions); - } else { - setSuggestions([]); - } - }, [prompt]); - - // Simulate progress during generation - useEffect(() => { - if (loading || isRefining) { - setProgress(0); - const interval = setInterval(() => { - setProgress((prev) => { - if (prev >= 90) return prev; - return prev + Math.random() * 15; - }); - }, 500); - return () => clearInterval(interval); - } - }, [loading, isRefining]); - - // Show toast notifications for errors - useEffect(() => { - if (error) { - message(error.message, "error"); - } - }, [error, message]); - - // Redirect if not authenticated (unless auth is disabled) - const authDisabled = process.env.NEXT_PUBLIC_DISABLE_AUTH === "true"; - if (!authDisabled && !userLoading && !user) { - router.push("/"); - return null; - } - const handleGenerate = async () => { if (!prompt.trim()) { - const validationError = { - type: "validation" as const, - message: "Please enter a video description", + setError({ + type: "validation", + message: "Please enter a description for your content", retryable: false, - }; - setError(validationError); + }); return; } @@ -362,13 +199,11 @@ function DashboardContent() { setError(null); setResult(null); setProgress(0); - setHasRefined(false); - setRefinementPrompt(""); - // Simulate progress updates + // Smooth progress animation const progressInterval = setInterval(() => { - setProgress((prev) => Math.min(prev + 10, 90)); - }, 800); + setProgress((prev) => Math.min(prev + 8, 90)); + }, 600); try { const userId = authDisabled ? "demo-user" : user?.uid; @@ -380,7 +215,6 @@ function DashboardContent() { body: JSON.stringify({ prompt, style, - model, quality, provider, userId, @@ -398,8 +232,7 @@ function DashboardContent() { if (data.success) { setResult(data); - message("Thumbnail generated successfully!", "success"); - setRetryCount(0); + message("๐ŸŽ‰ Thumbnail generated successfully!", "success"); } else { const errorInfo = categorizeError(data.error); setError(errorInfo); @@ -415,88 +248,19 @@ function DashboardContent() { } }; - const handleRetry = () => { - setRetryCount((prev) => prev + 1); - setError(null); - setTimeout(() => { - handleGenerate(); - }, 1000); - }; - - const handleRefine = async () => { - if (!refinementPrompt.trim()) { - const validationError = { - type: "validation" as const, - message: "Please enter a refinement prompt", - retryable: false, - }; - setError(validationError); - return; - } - - setIsRefining(true); - setError(null); - setProgress(0); - - const progressInterval = setInterval(() => { - setProgress((prev) => Math.min(prev + 10, 90)); - }, 800); - - try { - const userId = authDisabled ? "demo-user" : user?.uid; - const refinedPrompt = `${prompt} ${refinementPrompt}`.trim(); - - const response = await fetch("/api/generate-thumbnail", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - prompt: refinedPrompt, - style, - model, - quality, - provider, - userId, - refinementPrompt, - }), - }); - - clearInterval(progressInterval); - setProgress(100); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - const data = await response.json(); - - if (data.success) { - setResult(data); - setHasRefined(true); - setRefinementPrompt(""); - message("Thumbnail refined successfully!", "success"); - } else { - const errorInfo = categorizeError(data.error); - setError(errorInfo); - } - } catch (err) { - clearInterval(progressInterval); - const errorInfo = categorizeError(err); - setError(errorInfo); - console.error("Refinement error:", err); - } finally { - setIsRefining(false); - setTimeout(() => setProgress(0), 2000); - } - }; - const handleSuggestionClick = (suggestion: string) => { setPrompt(suggestion); - setSuggestions([]); setError(null); }; + const handleDownload = () => { + if (!result) return; + const link = document.createElement("a"); + link.href = result.imageUrl; + link.download = `thumbnail-${Date.now()}.png`; + link.click(); + }; + if (userLoading) { return ( @@ -509,324 +273,199 @@ function DashboardContent() { return ( -
- {/* Header Section */} - -

Create Your Perfect 

-

Thumbnail

-

- Transform your video ideas into eye-catching thumbnails that boost - clicks and engagement +

+ {/* Header */} + +

Create Perfect 

+

Thumbnails

+

+ Generate eye-catching thumbnails in seconds with AI

-
- {/* Main Generation Section */} -
- {/* Input Section */} +
+ {/* Generation Form */} +
+ + {/* Step 1: Describe Your Content */} - - -
-

- Describe Your Content -

-

- Tell us what your content is about and we'll create - the perfect thumbnail -

+ + +
+
1
+
+

Describe Your Content

+

What's your video about?

+
- - + +