diff --git a/app/api/generate-thumbnail/route.ts b/app/api/generate-thumbnail/route.ts index df24406..e5acc75 100644 --- a/app/api/generate-thumbnail/route.ts +++ b/app/api/generate-thumbnail/route.ts @@ -1,18 +1,15 @@ import { NextRequest, NextResponse } from "next/server"; -import { - generateThumbnail, - ThumbnailGenerationOptions, -<<<<<<< HEAD -} from "@/lib/ai/huggingface"; -======= -} from "@/lib/ai"; ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) +import { generateThumbnail, ThumbnailGenerationOptions } from "@/lib/ai"; // Enhanced error response helper -function createErrorResponse(message: string, status: number = 500, type?: string) { +function createErrorResponse( + message: string, + status: number = 500, + type?: string +) { return NextResponse.json( - { - error: message, + { + error: message, type: type || "api", success: false, timestamp: new Date().toISOString(), @@ -29,13 +26,9 @@ export async function POST(request: NextRequest) { style = "tech", model = "sdxl", quality = "balanced", -<<<<<<< HEAD - userId, -======= provider = "huggingface", userId, refinementPrompt, ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) } = body; // Enhanced input validation @@ -63,16 +56,6 @@ export async function POST(request: NextRequest) { ); } -<<<<<<< HEAD - // Validate model and quality - const validModels = ["sdxl", "flux", "realistic"]; - const validQualities = ["fast", "balanced", "high"]; - const validStyles = ["tech", "gaming", "tutorial", "lifestyle"]; - - if (!validModels.includes(model)) { - return createErrorResponse( - "Invalid AI model selected. Please choose a valid model.", -======= // Validate parameters const validProviders = ["huggingface", "stability", "fal"]; const validQualities = ["fast", "balanced", "high"]; @@ -81,7 +64,6 @@ export async function POST(request: NextRequest) { if (!validProviders.includes(provider)) { return createErrorResponse( "Invalid AI provider selected. Please choose a valid provider.", ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) 400, "validation" ); @@ -103,35 +85,28 @@ export async function POST(request: NextRequest) { ); } -<<<<<<< HEAD - // Check for API key - if (!process.env.HUGGINGFACE_API_KEY) { - return createErrorResponse( - "AI service is not configured. Please contact support.", -======= // Check for API key based on provider let apiKeyMissing = false; let apiKeyName = ""; - + switch (provider) { - case 'huggingface': + case "huggingface": apiKeyMissing = !process.env.HUGGINGFACE_API_KEY; apiKeyName = "HUGGINGFACE_API_KEY"; break; - case 'stability': + case "stability": apiKeyMissing = !process.env.STABILITY_API_KEY; apiKeyName = "STABILITY_API_KEY"; break; - case 'fal': + case "fal": apiKeyMissing = !process.env.FAL_KEY; apiKeyName = "FAL_KEY"; break; } - + if (apiKeyMissing) { return createErrorResponse( `AI service is not configured (${apiKeyName} missing). Please contact support.`, ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) 500, "api" ); @@ -143,13 +118,9 @@ export async function POST(request: NextRequest) { style, model, quality, -<<<<<<< HEAD - userId, -======= provider, userId, refinementPrompt: refinementPrompt?.trim(), ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) }; console.log("Generating thumbnail with options:", { @@ -157,13 +128,9 @@ export async function POST(request: NextRequest) { style, model, quality, -<<<<<<< HEAD - userId: userId ? "***" : "none", -======= provider, userId: userId ? "***" : "none", refinement: refinementPrompt ? "yes" : "no", ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) }); const result = await generateThumbnail(options); @@ -179,10 +146,7 @@ export async function POST(request: NextRequest) { prompt: result.prompt, style: result.style, model: result.model, -<<<<<<< HEAD -======= provider: result.provider, ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) parameters: result.parameters, timestamp: new Date().toISOString(), }); @@ -192,7 +156,7 @@ export async function POST(request: NextRequest) { // Enhanced error categorization if (error instanceof Error) { const errorMessage = error.message.toLowerCase(); - + // Network/connection errors if (errorMessage.includes("fetch") || errorMessage.includes("network")) { return createErrorResponse( @@ -203,7 +167,11 @@ export async function POST(request: NextRequest) { } // Rate limiting errors - if (errorMessage.includes("rate") || errorMessage.includes("limit") || errorMessage.includes("quota")) { + if ( + errorMessage.includes("rate") || + errorMessage.includes("limit") || + errorMessage.includes("quota") + ) { return createErrorResponse( "Too many requests. Please wait a moment before trying again.", 429, @@ -221,7 +189,10 @@ export async function POST(request: NextRequest) { } // Authentication errors - if (errorMessage.includes("unauthorized") || errorMessage.includes("forbidden")) { + if ( + errorMessage.includes("unauthorized") || + errorMessage.includes("forbidden") + ) { return createErrorResponse( "AI service authentication failed. Please contact support.", 401, @@ -230,7 +201,10 @@ export async function POST(request: NextRequest) { } // Timeout errors - if (errorMessage.includes("timeout") || errorMessage.includes("aborted")) { + if ( + errorMessage.includes("timeout") || + errorMessage.includes("aborted") + ) { return createErrorResponse( "Request timed out. Please try again with a shorter description.", 408, diff --git a/app/api/test-ai/route.ts b/app/api/test-ai/route.ts index 5f299f4..cd42eff 100644 --- a/app/api/test-ai/route.ts +++ b/app/api/test-ai/route.ts @@ -4,47 +4,56 @@ import { testConnection, testThumbnailGeneration } from "@/lib/ai/huggingface"; export async function POST(request: NextRequest) { 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 }); + 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 }); + return NextResponse.json( + { + success: false, + error: "HuggingFace API connection failed", + type: "connection", + }, + { status: 503 } + ); } - + // Test thumbnail generation console.log("Testing thumbnail generation..."); await testThumbnailGeneration(); - + return NextResponse.json({ success: true, message: "AI service is working correctly", 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 }); + + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : "Unknown error", + type: "test_failed", + timestamp: new Date().toISOString(), + }, + { status: 500 } + ); } } @@ -53,4 +62,4 @@ export async function GET() { message: "AI Test API", usage: "Send a POST request to test the AI service", }); -} \ No newline at end of file +} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 555742b..8589a9f 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -21,12 +21,13 @@ import { useRouter, useSearchParams } from "next/navigation"; import { AnimatedDiv } from "@/components/motion"; import { SearchIcon, RightArrowIcon } from "@/components/icons"; import { motion } from "framer-motion"; -<<<<<<< HEAD -import { Cpu, Clock, AlertCircle, RefreshCw } from "lucide-react"; -======= import { Cpu, Clock, AlertCircle, RefreshCw, Globe } from "lucide-react"; -import { getAvailableProviders, getModelsForProvider, type AIProvider, type AIModel } from "@/lib/ai"; ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) +import { + getAvailableProviders, + getModelsForProvider, + type AIProvider, + type AIModel, +} from "@/lib/ai"; type ThumbnailStyle = "tech" | "gaming" | "tutorial" | "lifestyle"; @@ -35,10 +36,7 @@ interface GenerationResult { prompt: string; style: string; model: string; -<<<<<<< HEAD -======= provider: string; ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) parameters: { steps: number; guidance_scale: number; @@ -125,7 +123,8 @@ const categorizeError = (error: any): ErrorInfo => { return { type: "api", message: "Server error occurred", - suggestion: "Our servers are experiencing issues. Please try again shortly", + suggestion: + "Our servers are experiencing issues. Please try again shortly", retryable: true, }; } @@ -179,28 +178,16 @@ const modelOptions = [ recommended: true, }, { -<<<<<<< HEAD - value: "flux", - label: "FLUX Schnell", - description: "Latest model with superior quality", -======= value: "sd15", label: "Stable Diffusion 1.5", description: "Fast and reliable generation", ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) icon: "๐Ÿš€", recommended: false, }, { -<<<<<<< HEAD - value: "realistic", - label: "Realistic SD", - description: "More photorealistic outputs", -======= value: "sd21", label: "Stable Diffusion 2.1", description: "Good quality output", ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) icon: "๐Ÿ“ธ", recommended: false, }, @@ -232,220 +219,6 @@ const qualityOptions = [ // Intelligent suggestion system - creates short, punchy suggestions const generateSmartSuggestions = (input: string): string[] => { -<<<<<<< HEAD - const trimmedInput = input.trim().toLowerCase(); - - if (trimmedInput.length < 3) return []; - - // Keywords and patterns for different types of content - const patterns = { - tech: [ - "iphone", - "android", - "laptop", - "pc", - "review", - "unboxing", - "setup", - "build", - "coding", - "app", - "software", - "hardware", - "gadget", - "phone", - "computer", - "tech", - "ai", - "robot", - ], - gaming: [ - "game", - "gaming", - "play", - "stream", - "twitch", - "fps", - "rpg", - "minecraft", - "fortnite", - "valorant", - "league", - "cod", - "setup", - "build", - "pc gaming", - "console", - ], - tutorial: [ - "how to", - "tutorial", - "guide", - "learn", - "teach", - "explain", - "step", - "beginner", - "advanced", - "tips", - "tricks", - "method", - "way to", - ], - lifestyle: [ - "morning", - "routine", - "day in", - "vlog", - "life", - "travel", - "food", - "cooking", - "workout", - "fitness", - "home", - "room", - "outfit", - "style", - ], - entertainment: [ - "funny", - "comedy", - "react", - "reaction", - "challenge", - "prank", - "story", - "storytime", - "drama", - "expose", - "truth", - ], - }; - - // Detect content type - let contentType = "general"; - - for (const [type, keywords] of Object.entries(patterns)) { - const matches = keywords.filter((keyword) => - trimmedInput.includes(keyword) - ); - if (matches.length > 0) { - contentType = type; - break; - } - } - - const suggestions: string[] = []; - - // Generate shorter, punchier suggestions based on content type - switch (contentType) { - case "tech": - if (trimmedInput.includes("iphone") || trimmedInput.includes("phone")) { - suggestions.push( - "iPhone camera test results", - "Phone battery life review", - "iPhone vs Android comparison" - ); - } else if (trimmedInput.includes("review")) { - suggestions.push( - "Tech review honest verdict", - "Gadget review pros cons", - "Review after 30 days" - ); - } else if (trimmedInput.includes("unbox")) { - suggestions.push( - "Unboxing first impressions setup", - "Unboxing hidden features revealed", - "Unboxing build quality test" - ); - } else { - suggestions.push( - "Tech tutorial complete guide", - "Tech breakdown analysis", - "Tech secrets exposed" - ); - } - break; - - case "gaming": - if (trimmedInput.includes("setup") || trimmedInput.includes("build")) { - suggestions.push( - "Gaming setup complete guide", - "Budget gaming build guide", - "Max FPS gaming setup" - ); - } else if (trimmedInput.includes("review")) { - suggestions.push( - "Game review honest verdict", - "Gaming gear review test", - "Game worth buying" - ); - } else { - suggestions.push( - "Gaming highlights epic moments", - "Gaming pro strategies tips", - "Gaming skills showcase" - ); - } - break; - - case "tutorial": - suggestions.push( - "Complete beginner tutorial guide", - "Advanced techniques made simple", - "Expert secrets tutorial" - ); - break; - - case "lifestyle": - if (trimmedInput.includes("routine")) { - suggestions.push( - "Morning routine that works", - "Life routine for success", - "Routine secrets revealed" - ); - } else if ( - trimmedInput.includes("vlog") || - trimmedInput.includes("day") - ) { - suggestions.push( - "Day in life vlog", - "Behind scenes moments", - "Real life unfiltered" - ); - } else { - suggestions.push( - "Lifestyle transformation results", - "Life journey lessons", - "Lifestyle experience insights" - ); - } - break; - - case "entertainment": - suggestions.push( - "Funny moments hilarious reactions", - "Reaction video genuine emotions", - "Entertainment content results" - ); - break; - - default: - // Generic suggestions that work for any content - suggestions.push( - "Complete step by step", - "Honest review results", - "Detailed analysis secrets" - ); - } - - // Return unique suggestions, max 3 - return suggestions - .map((s) => s.replace(/\s+/g, " ").trim()) - .filter((s, index, arr) => arr.indexOf(s) === index) - .slice(0, 3); -======= const inputLower = input.toLowerCase(); const suggestions: string[] = []; @@ -476,7 +249,6 @@ const generateSmartSuggestions = (input: string): string[] => { } return suggestions.slice(0, 3); ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) }; function DashboardContent() { @@ -487,10 +259,7 @@ function DashboardContent() { const [prompt, setPrompt] = useState(""); const [style, setStyle] = useState("tech"); -<<<<<<< HEAD -======= const [provider, setProvider] = useState("huggingface"); ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) const [model, setModel] = useState("sdxl"); const [quality, setQuality] = useState("balanced"); const [loading, setLoading] = useState(false); @@ -499,19 +268,19 @@ function DashboardContent() { const [error, setError] = useState(null); const [retryCount, setRetryCount] = useState(0); const [suggestions, setSuggestions] = useState([]); -<<<<<<< HEAD -======= const [refinementPrompt, setRefinementPrompt] = useState(""); const [hasRefined, setHasRefined] = useState(false); const [isRefining, setIsRefining] = useState(false); - const [availableProviders, setAvailableProviders] = useState([]); + 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); @@ -523,15 +292,14 @@ function DashboardContent() { 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]; + const recommendedModel = models.find((m) => m.recommended) || models[0]; setModel(recommendedModel.id); } } }, [provider]); ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) // Handle search parameters useEffect(() => { @@ -553,11 +321,7 @@ function DashboardContent() { // Simulate progress during generation useEffect(() => { -<<<<<<< HEAD - if (loading) { -======= if (loading || isRefining) { ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) setProgress(0); const interval = setInterval(() => { setProgress((prev) => { @@ -567,11 +331,7 @@ function DashboardContent() { }, 500); return () => clearInterval(interval); } -<<<<<<< HEAD - }, [loading]); -======= }, [loading, isRefining]); ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) // Show toast notifications for errors useEffect(() => { @@ -592,7 +352,7 @@ function DashboardContent() { const validationError = { type: "validation" as const, message: "Please enter a video description", - retryable: false + retryable: false, }; setError(validationError); return; @@ -602,11 +362,8 @@ function DashboardContent() { setError(null); setResult(null); setProgress(0); -<<<<<<< HEAD -======= setHasRefined(false); setRefinementPrompt(""); ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) // Simulate progress updates const progressInterval = setInterval(() => { @@ -625,10 +382,7 @@ function DashboardContent() { style, model, quality, -<<<<<<< HEAD -======= provider, ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) userId, }), }); @@ -669,14 +423,12 @@ function DashboardContent() { }, 1000); }; -<<<<<<< HEAD -======= const handleRefine = async () => { if (!refinementPrompt.trim()) { const validationError = { type: "validation" as const, message: "Please enter a refinement prompt", - retryable: false + retryable: false, }; setError(validationError); return; @@ -693,7 +445,7 @@ function DashboardContent() { try { const userId = authDisabled ? "demo-user" : user?.uid; const refinedPrompt = `${prompt} ${refinementPrompt}`.trim(); - + const response = await fetch("/api/generate-thumbnail", { method: "POST", headers: { @@ -739,7 +491,6 @@ function DashboardContent() { } }; ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) const handleSuggestionClick = (suggestion: string) => { setPrompt(suggestion); setSuggestions([]); @@ -761,25 +512,11 @@ function DashboardContent() {
{/* Header Section */} -<<<<<<< HEAD

Create Your Perfect 

-

- YouTube Thumbnail -

-

- Transform your video ideas into eye-catching thumbnails that boost - clicks and views -======= -

- Create Your Perfect  -

-

- Thumbnail -

+

Thumbnail

Transform your video ideas into eye-catching thumbnails that boost clicks and engagement ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)

@@ -792,28 +529,17 @@ function DashboardContent() {

-<<<<<<< HEAD - Describe Your Video -

-

- Tell us what your video is about and we'll create the -======= Describe Your Content

- Tell us what your content is about and we'll create the ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) - perfect thumbnail + Tell us what your content is about and we'll create + the perfect thumbnail

>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) placeholder="e.g., iPhone 15 Pro Max review with surprised reaction" value={prompt} onChange={(e) => setPrompt(e.target.value)} @@ -861,7 +587,8 @@ function DashboardContent() { ))}

- Click a suggestion to use it, or keep typing for more ideas + Click a suggestion to use it, or keep typing for more + ideas

)} @@ -874,13 +601,9 @@ function DashboardContent() {
-<<<<<<< HEAD -

Choose Your Style

-=======

Choose Your Style

->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)

Select the aesthetic that matches your content

@@ -919,8 +642,6 @@ function DashboardContent() { -<<<<<<< HEAD -======= {/* AI Provider Selection */} @@ -964,10 +685,13 @@ function DashboardContent() {

{providerOption.name}

-

- {providerOption.models.length} model{providerOption.models.length !== 1 ? 's' : ''} + {providerOption.models.length} model + {providerOption.models.length !== 1 ? "s" : ""}

@@ -990,7 +715,6 @@ function DashboardContent() {
->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) {/* Model and Quality Settings */} @@ -1014,48 +738,27 @@ function DashboardContent() {
-<<<<<<< HEAD - {modelOptions.map((option) => ( + {currentModels.map((option) => ( setModel(option.value)} -======= - {currentModels.map((option) => ( - - setModel(option.id)} ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) + onClick={() => setModel(option.id)} >
-<<<<<<< HEAD - {option.label} -======= {option.name} ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) {option.recommended && ( @@ -1139,13 +842,7 @@ function DashboardContent() { isLoading={loading} fullWidth endContent={ -<<<<<<< HEAD - !loading && ( - - ) -======= !loading && ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) } > {loading ? "Creating Your Thumbnail..." : "Generate Thumbnail"} @@ -1153,23 +850,15 @@ function DashboardContent() { {/* Progress Bar */} -<<<<<<< HEAD - {loading && ( -======= {(loading || isRefining) && ( ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
-<<<<<<< HEAD -

Generating...

-=======

{isRefining ? "Refining..." : "Generating..."}

->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)

{Math.round(progress)}%

@@ -1181,14 +870,14 @@ function DashboardContent() { size="sm" />
-<<<<<<< HEAD - Using {modelOptions.find((m) => m.value === model)?.label} โ€ข {qualityOptions.find((q) => q.value === quality)?.label} quality -======= - Using{" "} - {currentModels.find((m) => m.id === model)?.name} โ€ข{" "} + Using {currentModels.find((m) => m.id === model)?.name}{" "} + โ€ข{" "} {qualityOptions.find((q) => q.value === quality)?.label}{" "} - quality โ€ข {availableProviders.find((p) => p.id === provider)?.name} ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality) + quality โ€ข{" "} + { + availableProviders.find((p) => p.id === provider) + ?.name + }
@@ -1272,18 +961,12 @@ function DashboardContent() {
-<<<<<<< HEAD Parameters: - - {result.parameters.steps} steps, {result.parameters.guidance_scale} guidance -======= - Parameters: {result.parameters.steps} steps,{" "} {result.parameters.guidance_scale} guidance ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
@@ -1301,8 +984,6 @@ function DashboardContent() { > Download Thumbnail -<<<<<<< HEAD -======= {/* Refinement Section */} {!hasRefined && ( @@ -1319,7 +1000,9 @@ function DashboardContent() { size="sm" placeholder="e.g., make it more colorful, add text, change lighting..." value={refinementPrompt} - onChange={(e) => setRefinementPrompt(e.target.value)} + onChange={(e) => + setRefinementPrompt(e.target.value) + } maxLength={200} description={`${refinementPrompt.length}/200 characters`} classNames={{ @@ -1354,7 +1037,6 @@ function DashboardContent() {
)} ->>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
) : (
diff --git a/app/page.tsx b/app/page.tsx index a33a829..48e3595 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,3 +1,5 @@ +"use client"; + import { RightArrowIcon } from "@/components/icons"; import { title, subtitle, button } from "@/components/primitives"; import { Button } from "@nextui-org/button"; diff --git a/components/error-boundary.tsx b/components/error-boundary.tsx index 8a854b6..2a9c0d1 100644 --- a/components/error-boundary.tsx +++ b/components/error-boundary.tsx @@ -12,7 +12,7 @@ interface ErrorBoundaryState { interface ErrorBoundaryProps { children: React.ReactNode; - fallback?: React.ComponentType<{ error: Error; reset: () => void }>; + fallback?: React.FC<{ error: Error; reset: () => void }>; } export class ErrorBoundary extends React.Component< @@ -39,7 +39,7 @@ export class ErrorBoundary extends React.Component< console.error("Error Boundary caught an error:", error, errorInfo); this.setState({ error, - errorInfo: errorInfo.componentStack, + errorInfo: errorInfo.componentStack ?? null, }); } @@ -78,15 +78,16 @@ export class ErrorBoundary extends React.Component< An unexpected error occurred while loading this page.

- - {process.env.NODE_ENV === "development" && this.state.error && ( -
-

- {this.state.error.message} -

-
- )} - + + {process.env.NODE_ENV === "development" && + this.state.error && ( +
+

+ {this.state.error.message} +

+
+ )} +
@@ -139,4 +140,4 @@ export function useErrorBoundary() { return { captureError, resetError }; } -export default ErrorBoundary; \ No newline at end of file +export default ErrorBoundary; diff --git a/lib/ai/fal.ts b/lib/ai/fal.ts index 199cfb0..3f7f630 100644 --- a/lib/ai/fal.ts +++ b/lib/ai/fal.ts @@ -1,54 +1,63 @@ // fal.ai Provider Implementation -import { ThumbnailGenerationOptions, ThumbnailResult, GenerationParameters } from './providers'; +import { + ThumbnailGenerationOptions, + ThumbnailResult, + GenerationParameters, +} from "./providers"; // Note: fal.ai client should be installed: npm install @fal-ai/client // For now, we'll use fetch directly to avoid adding dependencies -const FAL_API_URL = 'https://fal.run/fal-ai'; +const FAL_API_URL = "https://fal.run/fal-ai"; // Style prompts optimized for fal.ai models const stylePrompts = { tech: { - positive: "professional tech product, modern design, clean lighting, high quality, tech review thumbnail, sharp focus", + positive: + "professional tech product, modern design, clean lighting, high quality, tech review thumbnail, sharp focus", negative: "blurry, low quality, amateur, watermark, text overlay", basePrompt: "tech product showcase", }, gaming: { - positive: "gaming setup, colorful RGB lighting, exciting atmosphere, gaming thumbnail, high energy, dramatic", + positive: + "gaming setup, colorful RGB lighting, exciting atmosphere, gaming thumbnail, high energy, dramatic", negative: "boring, dull, poor lighting, low quality", basePrompt: "gaming content thumbnail", }, tutorial: { - positive: "educational content, clear presentation, professional layout, tutorial thumbnail, instructional, clean", + positive: + "educational content, clear presentation, professional layout, tutorial thumbnail, instructional, clean", negative: "confusing, cluttered, messy, poor quality", basePrompt: "tutorial content thumbnail", }, lifestyle: { - positive: "lifestyle photo, natural lighting, authentic, warm, lifestyle thumbnail, personal content, candid", + positive: + "lifestyle photo, natural lighting, authentic, warm, lifestyle thumbnail, personal content, candid", negative: "artificial, fake, poor lighting, low quality", basePrompt: "lifestyle content thumbnail", }, }; -const universalNegativePrompt = "low quality, blurry, amateur, watermark, text overlay, logos, copyright, distorted"; +const universalNegativePrompt = + "low quality, blurry, amateur, watermark, text overlay, logos, copyright, distorted"; // Model configurations for fal.ai const models = { - 'flux-schnell': { - id: 'flux/schnell', - name: 'FLUX.1 Schnell', + "flux-schnell": { + id: "flux/schnell", + name: "FLUX.1 Schnell", steps: { fast: 1, balanced: 2, high: 4 }, guidance: { fast: 3.0, balanced: 5.0, high: 7.0 }, }, - 'flux-dev': { - id: 'flux/dev', - name: 'FLUX.1 Dev', + "flux-dev": { + id: "flux/dev", + name: "FLUX.1 Dev", steps: { fast: 20, balanced: 30, high: 50 }, guidance: { fast: 5.0, balanced: 7.0, high: 8.0 }, }, - 'hidream-fast': { - id: 'hidream-i1-fast', - name: 'HiDream I1 Fast', + "hidream-fast": { + id: "hidream-i1-fast", + name: "HiDream I1 Fast", steps: { fast: 8, balanced: 16, high: 25 }, guidance: { fast: 3.0, balanced: 5.0, high: 7.0 }, }, @@ -77,7 +86,7 @@ export async function generateThumbnail( } const styleConfig = stylePrompts[style]; - + // Build the prompt with optional refinement let finalPrompt = `${styleConfig.basePrompt}, ${prompt}, ${styleConfig.positive}`; if (refinementPrompt) { @@ -112,7 +121,7 @@ export async function generateThumbnail( }); // Prepare request body based on model - let requestBody: any = { + const requestBody: any = { prompt: finalPrompt, negative_prompt: negativePrompt, image_size: { @@ -125,21 +134,21 @@ export async function generateThumbnail( }; // Add model-specific parameters - if (model === 'flux-schnell') { + if (model === "flux-schnell") { requestBody.num_inference_steps = parameters.steps; - } else if (model === 'flux-dev') { + } else if (model === "flux-dev") { requestBody.num_inference_steps = parameters.steps; requestBody.guidance_scale = parameters.guidance_scale; - } else if (model === 'hidream-fast') { + } else if (model === "hidream-fast") { requestBody.num_inference_steps = parameters.steps; requestBody.guidance_scale = parameters.guidance_scale; } const response = await fetch(`${FAL_API_URL}/${selectedModel.id}`, { - method: 'POST', + method: "POST", headers: { - 'Authorization': `Key ${process.env.FAL_KEY}`, - 'Content-Type': 'application/json', + Authorization: `Key ${process.env.FAL_KEY}`, + "Content-Type": "application/json", }, body: JSON.stringify(requestBody), }); @@ -147,7 +156,7 @@ export async function generateThumbnail( if (!response.ok) { const errorText = await response.text(); console.error("fal.ai API error:", errorText); - + if (response.status === 401) { throw new Error("Invalid fal.ai API key"); } @@ -158,14 +167,18 @@ export async function generateThumbnail( throw new Error("Invalid request to fal.ai. Please check your prompt."); } if (response.status === 402) { - throw new Error("fal.ai payment required. Please add credits to your account."); + throw new Error( + "fal.ai payment required. Please add credits to your account." + ); } - - throw new Error(`fal.ai API error: ${response.status} ${response.statusText}`); + + throw new Error( + `fal.ai API error: ${response.status} ${response.statusText}` + ); } const data = await response.json(); - + if (!data.images || data.images.length === 0) { throw new Error("No image generated by fal.ai"); } @@ -173,7 +186,7 @@ export async function generateThumbnail( // Download the image from the URL const imageUrl = data.images[0].url; const imageResponse = await fetch(imageUrl); - + if (!imageResponse.ok) { throw new Error("Failed to download generated image"); } @@ -187,16 +200,16 @@ export async function generateThumbnail( prompt: finalPrompt, style, model: selectedModel.name, - provider: 'fal', + provider: "fal", parameters, }; } catch (error) { console.error("โŒ fal.ai error:", error); - + if (error instanceof Error) { throw error; } - + throw new Error(`Failed to generate thumbnail with fal.ai: ${error}`); } } @@ -208,13 +221,13 @@ export async function testConnection(): Promise { } console.log("๐Ÿ” Testing fal.ai connection..."); - + // Test with a simple request to flux-schnell (fastest model) const response = await fetch(`${FAL_API_URL}/flux/schnell`, { - method: 'POST', + method: "POST", headers: { - 'Authorization': `Key ${process.env.FAL_KEY}`, - 'Content-Type': 'application/json', + Authorization: `Key ${process.env.FAL_KEY}`, + "Content-Type": "application/json", }, body: JSON.stringify({ prompt: "test image", @@ -226,7 +239,7 @@ export async function testConnection(): Promise { num_inference_steps: 1, }), }); - + if (response.ok) { console.log("โœ… fal.ai connection test successful"); return true; @@ -245,7 +258,7 @@ export async function testConnection(): Promise { export async function testThumbnailGeneration(): Promise { console.log("๐Ÿงช Testing fal.ai thumbnail generation..."); - + try { const result = await generateThumbnail({ prompt: "iPhone review", @@ -253,7 +266,7 @@ export async function testThumbnailGeneration(): Promise { model: "flux-schnell", quality: "fast", }); - + console.log("โœ… fal.ai test generation successful"); console.log("Prompt used:", result.prompt); console.log("Image blob size:", result.imageBlob.size, "bytes"); @@ -261,4 +274,4 @@ export async function testThumbnailGeneration(): Promise { console.error("โŒ fal.ai test generation failed:", error); throw error; } -} \ No newline at end of file +} diff --git a/lib/ai/huggingface.ts b/lib/ai/huggingface.ts index 9c15f43..c90be30 100644 --- a/lib/ai/huggingface.ts +++ b/lib/ai/huggingface.ts @@ -6,7 +6,7 @@ export interface ThumbnailGenerationOptions { prompt: string; style?: "tech" | "gaming" | "tutorial" | "lifestyle"; userId?: string; - model?: "sdxl" | "sd15" | "sd21"; + model?: string; quality?: "fast" | "balanced" | "high"; } @@ -72,12 +72,14 @@ const qualityPresets = { // Simple, effective prompts that work well with free models const stylePrompts = { tech: { - positive: "professional tech product, modern design, clean lighting, high quality", + positive: + "professional tech product, modern design, clean lighting, high quality", negative: "blurry, low quality, amateur", basePrompt: "tech thumbnail", }, gaming: { - positive: "gaming setup, colorful lights, exciting atmosphere, high quality", + positive: + "gaming setup, colorful lights, exciting atmosphere, high quality", negative: "boring, dull, poor lighting", basePrompt: "gaming thumbnail", }, @@ -111,10 +113,17 @@ export async function generateThumbnail( throw new Error("HUGGINGFACE_API_KEY environment variable is not set"); } - // Get model and quality settings - const selectedModel = models[model]; - const qualitySettings = qualityPresets[quality]; - const styleConfig = stylePrompts[style]; + // Map model to valid model keys and provide fallback + const modelKey = + model && models[model as keyof typeof models] + ? (model as keyof typeof models) + : "sdxl"; + const selectedModel = models[modelKey]; + const qualitySettings = + qualityPresets[quality as keyof typeof qualityPresets] || + qualityPresets.balanced; + const styleConfig = + stylePrompts[style as keyof typeof stylePrompts] || stylePrompts.tech; // Build simple, effective prompt const optimizedPrompt = `${styleConfig.basePrompt}, ${prompt}, ${styleConfig.positive}`; @@ -125,7 +134,7 @@ export async function generateThumbnail( // Generation parameters optimized for free tier const parameters: GenerationParameters = { model: selectedModel.id, - width: 768, // Smaller size for free tier + width: 768, // Smaller size for free tier height: 432, // 16:9 ratio steps: qualitySettings.steps, guidance_scale: qualitySettings.guidance_scale, @@ -162,29 +171,53 @@ export async function generateThumbnail( prompt: optimizedPrompt, style, model: selectedModel.name, - provider: 'huggingface', + provider: "huggingface", parameters, }; } catch (error) { console.error("โŒ HuggingFace API error:", error); - + // Enhanced error handling for common free tier issues if (error instanceof Error) { - if (error.message.includes("unauthorized") || error.message.includes("401")) { - throw new Error("Invalid HuggingFace API key. Please check your API key."); + if ( + error.message.includes("unauthorized") || + error.message.includes("401") + ) { + throw new Error( + "Invalid HuggingFace API key. Please check your API key." + ); } - if (error.message.includes("rate") || error.message.includes("limit") || error.message.includes("quota")) { - throw new Error("HuggingFace quota exceeded. Try again in a few minutes or upgrade your account."); + if ( + error.message.includes("rate") || + error.message.includes("limit") || + error.message.includes("quota") + ) { + throw new Error( + "HuggingFace quota exceeded. Try again in a few minutes or upgrade your account." + ); } - if (error.message.includes("gated") || error.message.includes("restricted") || error.message.includes("access")) { - throw new Error(`Model ${selectedModel.name} requires special access. Using alternative model.`); + if ( + error.message.includes("gated") || + error.message.includes("restricted") || + error.message.includes("access") + ) { + throw new Error( + `Model ${selectedModel.name} requires special access. Using alternative model.` + ); } - if (error.message.includes("503") || error.message.includes("unavailable")) { - throw new Error("HuggingFace service temporarily unavailable. Try again in a few minutes."); + if ( + error.message.includes("503") || + error.message.includes("unavailable") + ) { + throw new Error( + "HuggingFace service temporarily unavailable. Try again in a few minutes." + ); } } - - throw new Error(`Failed to generate thumbnail: ${error instanceof Error ? error.message : "Unknown error"}`); + + throw new Error( + `Failed to generate thumbnail: ${error instanceof Error ? error.message : "Unknown error"}` + ); } } @@ -207,7 +240,7 @@ export function getQualityPresets() { export async function testConnection(): Promise { try { console.log("๐Ÿ” Testing HuggingFace connection with free tier model..."); - + const response = await hf.textToImage({ model: models.sd15.id, // Use SD 1.5 for testing (most reliable) inputs: "simple test image", @@ -218,7 +251,7 @@ export async function testConnection(): Promise { guidance_scale: 7.5, }, }); - + console.log("โœ… HuggingFace connection test successful"); return response !== null; } catch (error) { @@ -230,7 +263,7 @@ export async function testConnection(): Promise { // Test function optimized for free tier export async function testThumbnailGeneration(): Promise { console.log("๐Ÿงช Testing thumbnail generation with free tier settings..."); - + try { const result = await generateThumbnail({ prompt: "iPhone review", @@ -238,7 +271,7 @@ export async function testThumbnailGeneration(): Promise { model: "sd15", // Use most reliable model quality: "fast", // Use fastest setting }); - + console.log("โœ… Test thumbnail generation successful"); console.log("Prompt used:", result.prompt); console.log("Image blob size:", result.imageBlob.size, "bytes"); diff --git a/lib/ai/index.ts b/lib/ai/index.ts index 06455f6..e440ebc 100644 --- a/lib/ai/index.ts +++ b/lib/ai/index.ts @@ -1,23 +1,23 @@ // Central AI Provider Manager -import { - getProvider, - getAvailableProviders, - getModelsForProvider, +import { + getProvider, + getAvailableProviders, + getModelsForProvider, providerSupportsRefinement, ThumbnailGenerationOptions, ThumbnailResult, AIProvider, - AIModel -} from './providers'; + AIModel, +} from "./providers"; -export { - getAvailableProviders, - getModelsForProvider, +export { + getAvailableProviders, + getModelsForProvider, providerSupportsRefinement, type AIProvider, type AIModel, type ThumbnailGenerationOptions, - type ThumbnailResult + type ThumbnailResult, }; /** @@ -26,15 +26,15 @@ export { export async function generateThumbnail( options: ThumbnailGenerationOptions ): Promise { - const { provider = 'huggingface' } = options; - + const { provider = "huggingface" } = options; + const selectedProvider = getProvider(provider); if (!selectedProvider) { throw new Error(`Provider ${provider} not found`); } console.log(`๐ŸŽฏ Using provider: ${selectedProvider.name}`); - + try { return await selectedProvider.generateThumbnail(options); } catch (error) { @@ -53,11 +53,14 @@ export async function testConnection(provider: string): Promise { } console.log(`๐Ÿ” Testing connection to ${selectedProvider.name}...`); - + try { return await selectedProvider.testConnection(); } catch (error) { - console.error(`โŒ Connection test failed for ${selectedProvider.name}:`, error); + console.error( + `โŒ Connection test failed for ${selectedProvider.name}:`, + error + ); return false; } } @@ -68,9 +71,9 @@ export async function testConnection(provider: string): Promise { export async function testAllProviders(): Promise> { const providers = getAvailableProviders(); const results: Record = {}; - - console.log('๐Ÿงช Testing all AI providers...'); - + + console.log("๐Ÿงช Testing all AI providers..."); + for (const provider of providers) { try { results[provider.id] = await testConnection(provider.id); @@ -79,7 +82,7 @@ export async function testAllProviders(): Promise> { results[provider.id] = false; } } - + return results; } @@ -88,12 +91,12 @@ export async function testAllProviders(): Promise> { */ export async function getBestAvailableProvider(): Promise { const providers = getAvailableProviders(); - + // Test providers in order of preference - const preferenceOrder = ['stability', 'fal', 'huggingface']; - + const preferenceOrder = ["stability", "fal", "huggingface"]; + for (const providerId of preferenceOrder) { - const provider = providers.find(p => p.id === providerId); + const provider = providers.find((p) => p.id === providerId); if (provider) { const isAvailable = await testConnection(providerId); if (isAvailable) { @@ -102,31 +105,39 @@ export async function getBestAvailableProvider(): Promise { } } } - + // Fallback to first provider - console.log('โš ๏ธ No providers available, falling back to HuggingFace'); - return 'huggingface'; + console.log("โš ๏ธ No providers available, falling back to HuggingFace"); + return "huggingface"; } /** * Get provider availability status */ -export async function getProviderStatus(): Promise> { +export async function getProviderStatus(): Promise< + Record< + string, + { + available: boolean; + name: string; + description: string; + pricing: string; + models: AIModel[]; + } + > +> { const providers = getAvailableProviders(); - const status: Record = {}; - + const status: Record< + string, + { + available: boolean; + name: string; + description: string; + pricing: string; + models: AIModel[]; + } + > = {}; + for (const provider of providers) { const isAvailable = await testConnection(provider.id); status[provider.id] = { @@ -137,6 +148,6 @@ export async function getProviderStatus(): Promise Promise; + generateThumbnail: ( + options: ThumbnailGenerationOptions + ) => Promise; testConnection: () => Promise; } @@ -16,8 +18,8 @@ export interface AIModel { description: string; icon: string; recommended: boolean; - speed: 'fast' | 'medium' | 'slow'; - quality: 'good' | 'high' | 'excellent'; + speed: "fast" | "medium" | "slow"; + quality: "good" | "high" | "excellent"; } export interface ThumbnailGenerationOptions { @@ -51,137 +53,139 @@ export interface GenerationParameters { // Provider configurations export const AI_PROVIDERS: Record = { stability: { - id: 'stability', - name: 'Stability AI', - description: 'Free for personal & commercial use under $1M revenue', - pricing: 'FREE', + id: "stability", + name: "Stability AI", + description: "Free for personal & commercial use under $1M revenue", + pricing: "FREE", supportsRefinement: true, models: [ { - id: 'sd-3.5-large', - name: 'Stable Diffusion 3.5 Large', - description: '8B parameters, superior quality', - icon: '๐ŸŽฏ', + id: "sd-3.5-large", + name: "Stable Diffusion 3.5 Large", + description: "8B parameters, superior quality", + icon: "๐ŸŽฏ", recommended: true, - speed: 'medium', - quality: 'excellent' + speed: "medium", + quality: "excellent", }, { - id: 'sd-3.5-turbo', - name: 'Stable Diffusion 3.5 Turbo', - description: '4-step generation, ultra-fast', - icon: 'โšก', + id: "sd-3.5-turbo", + name: "Stable Diffusion 3.5 Turbo", + description: "4-step generation, ultra-fast", + icon: "โšก", recommended: false, - speed: 'fast', - quality: 'high' + speed: "fast", + quality: "high", }, { - id: 'sdxl', - name: 'Stable Diffusion XL', - description: 'Proven quality, reliable', - icon: '๐Ÿ–ผ๏ธ', + id: "sdxl", + name: "Stable Diffusion XL", + description: "Proven quality, reliable", + icon: "๐Ÿ–ผ๏ธ", recommended: false, - speed: 'medium', - quality: 'high' - } + speed: "medium", + quality: "high", + }, ], generateThumbnail: async (options: ThumbnailGenerationOptions) => { - const { generateThumbnail: stabilityGenerate } = await import('./stability'); + const { generateThumbnail: stabilityGenerate } = await import( + "./stability" + ); return stabilityGenerate(options); }, testConnection: async () => { - const { testConnection: stabilityTest } = await import('./stability'); + const { testConnection: stabilityTest } = await import("./stability"); return stabilityTest(); - } + }, }, fal: { - id: 'fal', - name: 'fal.ai', - description: 'Pay-per-use, excellent value', - pricing: '~$0.003/image', + id: "fal", + name: "fal.ai", + description: "Pay-per-use, excellent value", + pricing: "~$0.003/image", supportsRefinement: true, models: [ { - id: 'flux-schnell', - name: 'FLUX.1 Schnell', - description: 'Ultra-fast, 333 images per $1', - icon: '๐Ÿš€', + id: "flux-schnell", + name: "FLUX.1 Schnell", + description: "Ultra-fast, 333 images per $1", + icon: "๐Ÿš€", recommended: true, - speed: 'fast', - quality: 'high' + speed: "fast", + quality: "high", }, { - id: 'flux-dev', - name: 'FLUX.1 Dev', - description: 'Premium quality, 40 images per $1', - icon: '๐Ÿ’Ž', + id: "flux-dev", + name: "FLUX.1 Dev", + description: "Premium quality, 40 images per $1", + icon: "๐Ÿ’Ž", recommended: false, - speed: 'medium', - quality: 'excellent' + speed: "medium", + quality: "excellent", }, { - id: 'hidream-fast', - name: 'HiDream I1 Fast', - description: 'New model, 16 steps', - icon: 'โœจ', + id: "hidream-fast", + name: "HiDream I1 Fast", + description: "New model, 16 steps", + icon: "โœจ", recommended: false, - speed: 'fast', - quality: 'high' - } + speed: "fast", + quality: "high", + }, ], generateThumbnail: async (options: ThumbnailGenerationOptions) => { - const { generateThumbnail: falGenerate } = await import('./fal'); + const { generateThumbnail: falGenerate } = await import("./fal"); return falGenerate(options); }, testConnection: async () => { - const { testConnection: falTest } = await import('./fal'); + const { testConnection: falTest } = await import("./fal"); return falTest(); - } + }, }, huggingface: { - id: 'huggingface', - name: 'HuggingFace', - description: 'Free tier with quota limits', - pricing: 'FREE (Limited)', + id: "huggingface", + name: "HuggingFace", + description: "Free tier with quota limits", + pricing: "FREE (Limited)", supportsRefinement: true, models: [ { - id: 'sdxl', - name: 'Stable Diffusion XL', - description: 'Best balance of quality and speed', - icon: 'โšก', + id: "sdxl", + name: "Stable Diffusion XL", + description: "Best balance of quality and speed", + icon: "โšก", recommended: true, - speed: 'medium', - quality: 'high' + speed: "medium", + quality: "high", }, { - id: 'sd15', - name: 'Stable Diffusion 1.5', - description: 'Fast and reliable generation', - icon: '๐Ÿš€', + id: "sd15", + name: "Stable Diffusion 1.5", + description: "Fast and reliable generation", + icon: "๐Ÿš€", recommended: false, - speed: 'fast', - quality: 'good' + speed: "fast", + quality: "good", }, { - id: 'sd21', - name: 'Stable Diffusion 2.1', - description: 'Good quality output', - icon: '๐Ÿ“ธ', + id: "sd21", + name: "Stable Diffusion 2.1", + description: "Good quality output", + icon: "๐Ÿ“ธ", recommended: false, - speed: 'medium', - quality: 'good' - } + speed: "medium", + quality: "good", + }, ], generateThumbnail: async (options: ThumbnailGenerationOptions) => { - const { generateThumbnail: hfGenerate } = await import('./huggingface'); + const { generateThumbnail: hfGenerate } = await import("./huggingface"); return hfGenerate(options); }, testConnection: async () => { - const { testConnection: hfTest } = await import('./huggingface'); + const { testConnection: hfTest } = await import("./huggingface"); return hfTest(); - } - } + }, + }, }; // Get available providers @@ -204,4 +208,4 @@ export function getModelsForProvider(providerId: string): AIModel[] { export function providerSupportsRefinement(providerId: string): boolean { const provider = getProvider(providerId); return provider?.supportsRefinement || false; -} \ No newline at end of file +} diff --git a/lib/ai/stability.ts b/lib/ai/stability.ts index d80dbd9..4dbe4ee 100644 --- a/lib/ai/stability.ts +++ b/lib/ai/stability.ts @@ -1,51 +1,60 @@ // Stability AI Provider Implementation -import { ThumbnailGenerationOptions, ThumbnailResult, GenerationParameters } from './providers'; +import { + ThumbnailGenerationOptions, + ThumbnailResult, + GenerationParameters, +} from "./providers"; -const STABILITY_API_URL = 'https://api.stability.ai/v1/generation'; +const STABILITY_API_URL = "https://api.stability.ai/v1/generation"; // Style prompts optimized for Stability AI const stylePrompts = { tech: { - positive: "professional tech product, modern design, clean lighting, high quality, tech review thumbnail", + positive: + "professional tech product, modern design, clean lighting, high quality, tech review thumbnail", negative: "blurry, low quality, amateur, watermark, text overlay", basePrompt: "tech product showcase", }, gaming: { - positive: "gaming setup, colorful RGB lighting, exciting atmosphere, gaming thumbnail, high energy", + positive: + "gaming setup, colorful RGB lighting, exciting atmosphere, gaming thumbnail, high energy", negative: "boring, dull, poor lighting, low quality", basePrompt: "gaming content thumbnail", }, tutorial: { - positive: "educational content, clear presentation, professional layout, tutorial thumbnail, instructional", + positive: + "educational content, clear presentation, professional layout, tutorial thumbnail, instructional", negative: "confusing, cluttered, messy, poor quality", basePrompt: "tutorial content thumbnail", }, lifestyle: { - positive: "lifestyle photo, natural lighting, authentic, warm, lifestyle thumbnail, personal content", + positive: + "lifestyle photo, natural lighting, authentic, warm, lifestyle thumbnail, personal content", negative: "artificial, fake, poor lighting, low quality", basePrompt: "lifestyle content thumbnail", }, }; -const universalNegativePrompt = "low quality, blurry, amateur, watermark, text overlay, logos, copyright"; +const universalNegativePrompt = + "low quality, blurry, amateur, watermark, text overlay, logos, copyright"; // Model configurations for Stability AI const models = { - 'sd-3.5-large': { - id: 'stable-diffusion-3-5-large', - name: 'Stable Diffusion 3.5 Large', + "sd-3.5-large": { + id: "stable-diffusion-3-5-large", + name: "Stable Diffusion 3.5 Large", steps: { fast: 20, balanced: 30, high: 50 }, guidance: { fast: 5.0, balanced: 7.0, high: 8.0 }, }, - 'sd-3.5-turbo': { - id: 'stable-diffusion-3-5-turbo', - name: 'Stable Diffusion 3.5 Turbo', + "sd-3.5-turbo": { + id: "stable-diffusion-3-5-turbo", + name: "Stable Diffusion 3.5 Turbo", steps: { fast: 4, balanced: 6, high: 8 }, guidance: { fast: 3.0, balanced: 5.0, high: 7.0 }, }, - 'sdxl': { - id: 'stable-diffusion-xl-1024-v1-0', - name: 'Stable Diffusion XL', + sdxl: { + id: "stable-diffusion-xl-1024-v1-0", + name: "Stable Diffusion XL", steps: { fast: 15, balanced: 25, high: 35 }, guidance: { fast: 6.0, balanced: 7.5, high: 8.5 }, }, @@ -74,7 +83,7 @@ export async function generateThumbnail( } const styleConfig = stylePrompts[style]; - + // Build the prompt with optional refinement let finalPrompt = `${styleConfig.basePrompt}, ${prompt}, ${styleConfig.positive}`; if (refinementPrompt) { @@ -108,61 +117,75 @@ export async function generateThumbnail( dimensions: `${parameters.width}x${parameters.height}`, }); - const response = await fetch(`${STABILITY_API_URL}/${selectedModel.id}/text-to-image`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${process.env.STABILITY_API_KEY}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - text_prompts: [ - { - text: finalPrompt, - weight: 1.0, - }, - { - text: negativePrompt, - weight: -1.0, - }, - ], - cfg_scale: parameters.guidance_scale, - height: parameters.height, - width: parameters.width, - samples: 1, - steps: parameters.steps, - style_preset: style === 'tech' ? 'enhance' : - style === 'gaming' ? 'neon-punk' : - style === 'tutorial' ? 'digital-art' : 'photographic', - }), - }); + const response = await fetch( + `${STABILITY_API_URL}/${selectedModel.id}/text-to-image`, + { + method: "POST", + headers: { + Authorization: `Bearer ${process.env.STABILITY_API_KEY}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + text_prompts: [ + { + text: finalPrompt, + weight: 1.0, + }, + { + text: negativePrompt, + weight: -1.0, + }, + ], + cfg_scale: parameters.guidance_scale, + height: parameters.height, + width: parameters.width, + samples: 1, + steps: parameters.steps, + style_preset: + style === "tech" + ? "enhance" + : style === "gaming" + ? "neon-punk" + : style === "tutorial" + ? "digital-art" + : "photographic", + }), + } + ); if (!response.ok) { const errorText = await response.text(); console.error("Stability API error:", errorText); - + if (response.status === 401) { throw new Error("Invalid Stability AI API key"); } if (response.status === 429) { - throw new Error("Stability AI rate limit exceeded. Please try again later."); + throw new Error( + "Stability AI rate limit exceeded. Please try again later." + ); } if (response.status === 400) { - throw new Error("Invalid request to Stability AI. Please check your prompt."); + throw new Error( + "Invalid request to Stability AI. Please check your prompt." + ); } - - throw new Error(`Stability AI API error: ${response.status} ${response.statusText}`); + + throw new Error( + `Stability AI API error: ${response.status} ${response.statusText}` + ); } const data = await response.json(); - + if (!data.artifacts || data.artifacts.length === 0) { throw new Error("No image generated by Stability AI"); } // Convert base64 to blob const base64Image = data.artifacts[0].base64; - const imageBuffer = Buffer.from(base64Image, 'base64'); - const imageBlob = new Blob([imageBuffer], { type: 'image/png' }); + const imageBuffer = Buffer.from(base64Image, "base64"); + const imageBlob = new Blob([imageBuffer], { type: "image/png" }); console.log("โœ… Stability AI generation successful"); @@ -171,16 +194,16 @@ export async function generateThumbnail( prompt: finalPrompt, style, model: selectedModel.name, - provider: 'stability', + provider: "stability", parameters, }; } catch (error) { console.error("โŒ Stability AI error:", error); - + if (error instanceof Error) { throw error; } - + throw new Error(`Failed to generate thumbnail with Stability AI: ${error}`); } } @@ -192,13 +215,13 @@ export async function testConnection(): Promise { } console.log("๐Ÿ” Testing Stability AI connection..."); - - const response = await fetch('https://api.stability.ai/v1/user/account', { + + const response = await fetch("https://api.stability.ai/v1/user/account", { headers: { - 'Authorization': `Bearer ${process.env.STABILITY_API_KEY}`, + Authorization: `Bearer ${process.env.STABILITY_API_KEY}`, }, }); - + if (response.ok) { console.log("โœ… Stability AI connection test successful"); return true; @@ -214,7 +237,7 @@ export async function testConnection(): Promise { export async function testThumbnailGeneration(): Promise { console.log("๐Ÿงช Testing Stability AI thumbnail generation..."); - + try { const result = await generateThumbnail({ prompt: "iPhone review", @@ -222,7 +245,7 @@ export async function testThumbnailGeneration(): Promise { model: "sdxl", quality: "fast", }); - + console.log("โœ… Stability AI test generation successful"); console.log("Prompt used:", result.prompt); console.log("Image blob size:", result.imageBlob.size, "bytes"); @@ -230,4 +253,4 @@ export async function testThumbnailGeneration(): Promise { console.error("โŒ Stability AI test generation failed:", error); throw error; } -} \ No newline at end of file +}