diff --git a/app/api/generate-thumbnail/route.ts b/app/api/generate-thumbnail/route.ts
new file mode 100644
index 0000000..df24406
--- /dev/null
+++ b/app/api/generate-thumbnail/route.ts
@@ -0,0 +1,272 @@
+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)
+
+// Enhanced error response helper
+function createErrorResponse(message: string, status: number = 500, type?: string) {
+ return NextResponse.json(
+ {
+ error: message,
+ type: type || "api",
+ success: false,
+ timestamp: new Date().toISOString(),
+ },
+ { status }
+ );
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const body = await request.json();
+ const {
+ prompt,
+ style = "tech",
+ model = "sdxl",
+ quality = "balanced",
+<<<<<<< HEAD
+ userId,
+=======
+ provider = "huggingface",
+ userId,
+ refinementPrompt,
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+ } = body;
+
+ // Enhanced input validation
+ if (!prompt || typeof prompt !== "string" || prompt.trim().length === 0) {
+ return createErrorResponse(
+ "Please enter a video description",
+ 400,
+ "validation"
+ );
+ }
+
+ if (prompt.length > 500) {
+ return createErrorResponse(
+ "Description must be less than 500 characters",
+ 400,
+ "validation"
+ );
+ }
+
+ if (prompt.trim().length < 5) {
+ return createErrorResponse(
+ "Description must be at least 5 characters long",
+ 400,
+ "validation"
+ );
+ }
+
+<<<<<<< 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"];
+ const validStyles = ["tech", "gaming", "tutorial", "lifestyle"];
+
+ 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"
+ );
+ }
+
+ if (!validQualities.includes(quality)) {
+ return createErrorResponse(
+ "Invalid quality setting selected. Please choose a valid quality level.",
+ 400,
+ "validation"
+ );
+ }
+
+ if (!validStyles.includes(style)) {
+ return createErrorResponse(
+ "Invalid style selected. Please choose a valid style.",
+ 400,
+ "validation"
+ );
+ }
+
+<<<<<<< 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':
+ apiKeyMissing = !process.env.HUGGINGFACE_API_KEY;
+ apiKeyName = "HUGGINGFACE_API_KEY";
+ break;
+ case 'stability':
+ apiKeyMissing = !process.env.STABILITY_API_KEY;
+ apiKeyName = "STABILITY_API_KEY";
+ break;
+ 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"
+ );
+ }
+
+ // Generate thumbnail with enhanced error handling
+ const options: ThumbnailGenerationOptions = {
+ prompt: prompt.trim(),
+ style,
+ model,
+ quality,
+<<<<<<< HEAD
+ userId,
+=======
+ provider,
+ userId,
+ refinementPrompt: refinementPrompt?.trim(),
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+ };
+
+ console.log("Generating thumbnail with options:", {
+ prompt: prompt.substring(0, 50) + "...",
+ 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);
+
+ // Convert blob to base64 for response
+ const arrayBuffer = await result.imageBlob.arrayBuffer();
+ const base64 = Buffer.from(arrayBuffer).toString("base64");
+ const dataUrl = `data:image/png;base64,${base64}`;
+
+ return NextResponse.json({
+ success: true,
+ imageUrl: dataUrl,
+ 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(),
+ });
+ } catch (error) {
+ console.error("API Error:", error);
+
+ // Enhanced error categorization
+ if (error instanceof Error) {
+ const errorMessage = error.message.toLowerCase();
+
+ // Network/connection errors
+ if (errorMessage.includes("fetch") || errorMessage.includes("network")) {
+ return createErrorResponse(
+ "Unable to connect to AI service. Please check your internet connection.",
+ 503,
+ "network"
+ );
+ }
+
+ // Rate limiting errors
+ if (errorMessage.includes("rate") || errorMessage.includes("limit") || errorMessage.includes("quota")) {
+ return createErrorResponse(
+ "Too many requests. Please wait a moment before trying again.",
+ 429,
+ "quota"
+ );
+ }
+
+ // Model-specific errors
+ if (errorMessage.includes("model") || errorMessage.includes("loading")) {
+ return createErrorResponse(
+ "AI model is currently unavailable. Try switching to a different model or wait a moment.",
+ 503,
+ "model"
+ );
+ }
+
+ // Authentication errors
+ if (errorMessage.includes("unauthorized") || errorMessage.includes("forbidden")) {
+ return createErrorResponse(
+ "AI service authentication failed. Please contact support.",
+ 401,
+ "api"
+ );
+ }
+
+ // Timeout errors
+ if (errorMessage.includes("timeout") || errorMessage.includes("aborted")) {
+ return createErrorResponse(
+ "Request timed out. Please try again with a shorter description.",
+ 408,
+ "network"
+ );
+ }
+
+ // Return the actual error message for debugging
+ return createErrorResponse(
+ `Generation failed: ${error.message}`,
+ 500,
+ "api"
+ );
+ }
+
+ // Fallback for unknown errors
+ return createErrorResponse(
+ "An unexpected error occurred. Please try again.",
+ 500,
+ "unknown"
+ );
+ }
+}
+
+export async function GET() {
+ return NextResponse.json({
+ message: "PixelAI Thumbnail Generation API",
+ version: "1.0.0",
+ status: "online",
+ supportedStyles: ["tech", "gaming", "tutorial", "lifestyle"],
+ supportedModels: ["sdxl", "flux", "realistic"],
+ supportedQualities: ["fast", "balanced", "high"],
+ limits: {
+ maxPromptLength: 500,
+ minPromptLength: 5,
+ },
+ timestamp: new Date().toISOString(),
+ });
+}
diff --git a/app/api/test-ai/route.ts b/app/api/test-ai/route.ts
new file mode 100644
index 0000000..5f299f4
--- /dev/null
+++ b/app/api/test-ai/route.ts
@@ -0,0 +1,56 @@
+import { NextRequest, NextResponse } from "next/server";
+import { testConnection, testThumbnailGeneration } from "@/lib/ai/huggingface";
+
+export async function POST(request: NextRequest) {
+ try {
+ console.log("๐ Starting AI service test...");
+
+ // 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();
+
+ 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 });
+ }
+}
+
+export async function GET() {
+ return NextResponse.json({
+ 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
new file mode 100644
index 0000000..555742b
--- /dev/null
+++ b/app/dashboard/page.tsx
@@ -0,0 +1,1385 @@
+"use client";
+import { useState, useEffect, Suspense } from "react";
+import {
+ Button,
+ Input,
+ Card,
+ CardBody,
+ CardHeader,
+ Image,
+ Chip,
+ Divider,
+ Progress,
+ Badge,
+ Spinner,
+} from "@nextui-org/react";
+import { PageLayout } from "@/components/layouts/pageLayout";
+import { useUser } from "@/contexts/userContext";
+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";
+<<<<<<< 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)
+
+type ThumbnailStyle = "tech" | "gaming" | "tutorial" | "lifestyle";
+
+interface GenerationResult {
+ imageUrl: string;
+ prompt: string;
+ style: string;
+ model: string;
+<<<<<<< HEAD
+=======
+ provider: string;
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+ parameters: {
+ steps: number;
+ guidance_scale: number;
+ negative_prompt: string;
+ };
+ timestamp: string;
+}
+
+// Error types for better error handling
+interface ErrorInfo {
+ type: "validation" | "network" | "api" | "quota" | "model" | "unknown";
+ 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 styleOptions = [
+ {
+ key: "tech",
+ label: "Tech Review",
+ description: "Modern, clean, professional",
+ gradient: "from-blue-500 to-cyan-500",
+ icon: "๐ป",
+ },
+ {
+ key: "gaming",
+ label: "Gaming",
+ description: "Intense, dramatic, neon colors",
+ gradient: "from-purple-500 to-pink-500",
+ icon: "๐ฎ",
+ },
+ {
+ key: "tutorial",
+ label: "Tutorial",
+ description: "Educational, clear, step-by-step",
+ gradient: "from-green-500 to-teal-500",
+ icon: "๐",
+ },
+ {
+ key: "lifestyle",
+ label: "Lifestyle",
+ description: "Warm, personal, authentic",
+ gradient: "from-orange-500 to-red-500",
+ icon: "โจ",
+ },
+];
+
+const modelOptions = [
+ {
+ value: "sdxl",
+ label: "Stable Diffusion XL",
+ description: "Best balance of quality and speed",
+ icon: "โก",
+ 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,
+ },
+];
+
+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,
+ },
+];
+
+// 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[] = [];
+
+ // 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 (inputLower.includes("gaming") || inputLower.includes("game")) {
+ suggestions.push("Epic gaming moment with intense action");
+ suggestions.push("Gaming highlight reel compilation");
+ }
+ if (inputLower.includes("lifestyle") || inputLower.includes("vlog")) {
+ suggestions.push("Daily lifestyle vlog with authentic moments");
+ suggestions.push("Personal story with emotional journey");
+ }
+
+ // 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");
+ }
+
+ return suggestions.slice(0, 3);
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+};
+
+function DashboardContent() {
+ const { user, loading: userLoading } = useUser();
+ const { message } = useMessage();
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ 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);
+ const [progress, setProgress] = useState(0);
+ const [result, setResult] = useState(null);
+ 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 [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]);
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+
+ // Handle search parameters
+ useEffect(() => {
+ const searchPrompt = searchParams.get("prompt");
+ if (searchPrompt) {
+ setPrompt(searchPrompt);
+ }
+ }, [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(() => {
+<<<<<<< HEAD
+ if (loading) {
+=======
+ if (loading || isRefining) {
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+ setProgress(0);
+ const interval = setInterval(() => {
+ setProgress((prev) => {
+ if (prev >= 90) return prev;
+ return prev + Math.random() * 15;
+ });
+ }, 500);
+ return () => clearInterval(interval);
+ }
+<<<<<<< HEAD
+ }, [loading]);
+=======
+ }, [loading, isRefining]);
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+
+ // 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",
+ retryable: false
+ };
+ setError(validationError);
+ return;
+ }
+
+ setLoading(true);
+ setError(null);
+ setResult(null);
+ setProgress(0);
+<<<<<<< HEAD
+=======
+ setHasRefined(false);
+ setRefinementPrompt("");
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+
+ // Simulate progress updates
+ const progressInterval = setInterval(() => {
+ setProgress((prev) => Math.min(prev + 10, 90));
+ }, 800);
+
+ try {
+ const userId = authDisabled ? "demo-user" : user?.uid;
+ const response = await fetch("/api/generate-thumbnail", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ prompt,
+ style,
+ model,
+ quality,
+<<<<<<< HEAD
+=======
+ provider,
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+ userId,
+ }),
+ });
+
+ 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);
+ message("Thumbnail generated successfully!", "success");
+ setRetryCount(0);
+ } else {
+ const errorInfo = categorizeError(data.error);
+ setError(errorInfo);
+ }
+ } catch (err) {
+ clearInterval(progressInterval);
+ const errorInfo = categorizeError(err);
+ setError(errorInfo);
+ console.error("Generation error:", err);
+ } finally {
+ setLoading(false);
+ setTimeout(() => setProgress(0), 2000);
+ }
+ };
+
+ const handleRetry = () => {
+ setRetryCount((prev) => prev + 1);
+ setError(null);
+ setTimeout(() => {
+ handleGenerate();
+ }, 1000);
+ };
+
+<<<<<<< HEAD
+=======
+ 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);
+ }
+ };
+
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+ const handleSuggestionClick = (suggestion: string) => {
+ setPrompt(suggestion);
+ setSuggestions([]);
+ setError(null);
+ };
+
+ if (userLoading) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {/* 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
+
+
+ Transform your video ideas into eye-catching thumbnails that boost
+ clicks and engagement
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+
+
+
+
+ {/* Main Generation Section */}
+
+ {/* Input Section */}
+
+
+
+
+
+<<<<<<< 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
+
+
+
+ >>>>>> 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)}
+ maxLength={500}
+ description={`${prompt.length}/500 characters`}
+ startContent={
+
+ }
+ classNames={{
+ inputWrapper: [
+ "shadow-md",
+ "bg-default-200/50",
+ "dark:bg-default/60",
+ "backdrop-blur-xl",
+ "hover:bg-default-200/70",
+ "dark:hover:bg-default/70",
+ "group-data-[focus=true]:bg-default-200/50",
+ "dark:group-data-[focus=true]:bg-default/60",
+ "border-2",
+ "border-transparent",
+ "group-data-[focus=true]:border-primary/50",
+ ],
+ }}
+ />
+
+ {/* Intelligent Suggestions */}
+ {suggestions.length > 0 && (
+
+
+ Or try these suggestions:
+
+
+ {suggestions.map((suggestion, index) => (
+ handleSuggestionClick(suggestion)}
+ title={suggestion}
+ >
+
+ {suggestion}
+
+
+ ))}
+
+
+ Click a suggestion to use it, or keep typing for more ideas
+
+
+ )}
+
+
+
+
+ {/* Style Selection */}
+
+
+
+
+<<<<<<< HEAD
+
Choose Your Style
+=======
+
+ Choose Your Style
+
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+
+ Select the aesthetic that matches your content
+
+
+
+
+ {styleOptions.map((option) => (
+
setStyle(option.key as ThumbnailStyle)}
+ >
+
+
+ {option.icon}
+
+
+ {option.label}
+
+
+ {option.description}
+
+
+
+ ))}
+
+
+
+
+
+<<<<<<< HEAD
+=======
+ {/* AI Provider Selection */}
+
+
+
+
+
+ Choose AI Provider
+
+
+ Select your preferred AI service for generating thumbnails
+
+
+
+
+ {availableProviders.map((providerOption) => (
+
+ setProvider(providerOption.id)}
+ >
+
+
+
+
+
+
+ {providerOption.name}
+
+
+ {providerOption.pricing}
+
+
+
+ {providerOption.description}
+
+
+ {providerOption.models.length} model{providerOption.models.length !== 1 ? 's' : ''}
+
+
+
+
+ ))}
+
+
+
+
+
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+ {/* Model and Quality Settings */}
+
+
+
+
+
+ Advanced Settings
+
+
+ Fine-tune your generation parameters
+
+
+
+
+ {/* Model Selection */}
+
+
+
+
+ AI Model
+
+
+
+<<<<<<< HEAD
+ {modelOptions.map((option) => (
+
+ setModel(option.value)}
+=======
+ {currentModels.map((option) => (
+
+ setModel(option.id)}
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+ >
+
+
+
+
+
+<<<<<<< HEAD
+ {option.label}
+=======
+ {option.name}
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+
+ {option.recommended && (
+
+ Recommended
+
+ )}
+
+
+ {option.description}
+
+
+
{option.icon}
+
+
+
+
+ ))}
+
+
+
+ {/* Quality Selection */}
+
+
+
+
+ Quality
+
+
+
+ {qualityOptions.map((option) => (
+
+ setQuality(option.value)}
+ >
+
+
+
+
+
+ {option.label}
+
+ {option.recommended && (
+
+ Balanced
+
+ )}
+
+
+ {option.description}
+
+
+
{option.icon}
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+ {/* Generate Button */}
+
+
+ )
+=======
+ !loading &&
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+ }
+ >
+ {loading ? "Creating Your Thumbnail..." : "Generate Thumbnail"}
+
+
+
+ {/* 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)}%
+
+
+
+
+<<<<<<< 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} โข{" "}
+ {qualityOptions.find((q) => q.value === quality)?.label}{" "}
+ quality โข {availableProviders.find((p) => p.id === provider)?.name}
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+
+
+
+
+
+ )}
+
+ {/* Enhanced Error Display */}
+ {error && (
+
+
+
+
+
+
+
+ {error.message}
+
+ {error.suggestion && (
+
+ {error.suggestion}
+
+ )}
+ {retryCount > 0 && (
+
+ Retry attempt: {retryCount}
+
+ )}
+
+ {error.retryable && (
+
}
+ isDisabled={loading}
+ >
+ Retry
+
+ )}
+
+
+
+
+ )}
+
+
+ {/* Results Panel */}
+
+
+
+
+ Preview
+
+
+ {result ? (
+
+
+
+
+
+
+
+
+
+ Model:
+
+ {modelOptions.find((m) => m.value === model)?.label}
+
+
+
+ Style:
+
+ {styleOptions.find((s) => s.key === style)?.label}
+
+
+
+<<<<<<< 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)
+
+
+
+
+
{
+ const link = document.createElement("a");
+ link.href = result.imageUrl;
+ link.download = "thumbnail.png";
+ link.click();
+ }}
+ className="w-full"
+ color="primary"
+ variant="flat"
+ >
+ Download Thumbnail
+
+<<<<<<< HEAD
+=======
+
+ {/* Refinement Section */}
+ {!hasRefined && (
+
+
+
+ Refine Your Thumbnail
+
+
+ Make one improvement to your thumbnail
+
+
+
setRefinementPrompt(e.target.value)}
+ maxLength={200}
+ description={`${refinementPrompt.length}/200 characters`}
+ classNames={{
+ inputWrapper: [
+ "bg-default-100",
+ "border-1",
+ "border-default-200",
+ "hover:border-default-300",
+ "focus:border-primary",
+ ],
+ }}
+ />
+
+ {isRefining ? "Refining..." : "Refine Thumbnail"}
+
+
+ )}
+
+ {hasRefined && (
+
+
+ โ
+ Thumbnail refined
+
+
+ )}
+>>>>>>> cde6b69 (feat: add dashboard navigation and search functionality)
+
+ ) : (
+
+
+ ๐จ
+
+
+ Your generated thumbnail will appear here
+
+
+ )}
+
+
+
+
+
+
+
+ );
+}
+
+export default function Dashboard() {
+ return (
+ Loading...}>
+
+
+ );
+}
diff --git a/app/page.tsx b/app/page.tsx
index 32beaee..a33a829 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -21,7 +21,7 @@ export default function Home() {
maintaining a polished online presence.
{}}
+ onClick={() => (window.location.href = "/dashboard")}
className={button({ hideOnMobile: true })}
endContent={ }
>
diff --git a/components/error-boundary.tsx b/components/error-boundary.tsx
new file mode 100644
index 0000000..8a854b6
--- /dev/null
+++ b/components/error-boundary.tsx
@@ -0,0 +1,142 @@
+"use client";
+
+import React from "react";
+import { Card, CardBody, Button } from "@nextui-org/react";
+import { AlertCircle, RefreshCw } from "lucide-react";
+
+interface ErrorBoundaryState {
+ hasError: boolean;
+ error: Error | null;
+ errorInfo: string | null;
+}
+
+interface ErrorBoundaryProps {
+ children: React.ReactNode;
+ fallback?: React.ComponentType<{ error: Error; reset: () => void }>;
+}
+
+export class ErrorBoundary extends React.Component<
+ ErrorBoundaryProps,
+ ErrorBoundaryState
+> {
+ constructor(props: ErrorBoundaryProps) {
+ super(props);
+ this.state = {
+ hasError: false,
+ error: null,
+ errorInfo: null,
+ };
+ }
+
+ static getDerivedStateFromError(error: Error): Partial {
+ return {
+ hasError: true,
+ error,
+ };
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ console.error("Error Boundary caught an error:", error, errorInfo);
+ this.setState({
+ error,
+ errorInfo: errorInfo.componentStack,
+ });
+ }
+
+ handleReset = () => {
+ this.setState({
+ hasError: false,
+ error: null,
+ errorInfo: null,
+ });
+ };
+
+ render() {
+ if (this.state.hasError) {
+ if (this.props.fallback) {
+ const FallbackComponent = this.props.fallback;
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ Something went wrong
+
+
+ An unexpected error occurred while loading this page.
+
+
+
+ {process.env.NODE_ENV === "development" && this.state.error && (
+
+
+ {this.state.error.message}
+
+
+ )}
+
+
+ }
+ >
+ Try Again
+
+ window.location.href = "/"}
+ >
+ Go Home
+
+
+
+
+
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+// Hook version for functional components
+export function useErrorBoundary() {
+ const [error, setError] = React.useState(null);
+
+ const resetError = React.useCallback(() => {
+ setError(null);
+ }, []);
+
+ const captureError = React.useCallback((error: Error) => {
+ setError(error);
+ }, []);
+
+ React.useEffect(() => {
+ if (error) {
+ throw error;
+ }
+ }, [error]);
+
+ return { captureError, resetError };
+}
+
+export default ErrorBoundary;
\ No newline at end of file
diff --git a/components/navbar/index.tsx b/components/navbar/index.tsx
index 47cc39f..f052de0 100644
--- a/components/navbar/index.tsx
+++ b/components/navbar/index.tsx
@@ -49,6 +49,14 @@ export const Navbar = () => {
+
+ Dashboard
+
Profile
diff --git a/components/search/searchComponent.tsx b/components/search/searchComponent.tsx
index 6e0ac2c..e6ddbbb 100644
--- a/components/search/searchComponent.tsx
+++ b/components/search/searchComponent.tsx
@@ -8,14 +8,34 @@ import { AnimatedDiv, AnimatedList } from "@/components/motion";
import { SearchIcon } from "@/components/icons";
import { button } from "@/components/primitives";
import { siteConfig } from "@/config/site";
+import { useRouter } from "next/navigation";
+import { useUser } from "@/contexts/userContext";
export const SearchComponent: React.FC = () => {
const [inputValue, setInputValue] = useState("");
+ const router = useRouter();
+ const { user, toggleLogin } = useUser();
const handleInputChange = (event: React.ChangeEvent) => {
setInputValue(event.target.value);
};
+ const handleGenerate = () => {
+ const authDisabled = process.env.NEXT_PUBLIC_DISABLE_AUTH === "true";
+
+ if (!authDisabled && !user) {
+ toggleLogin();
+ return;
+ }
+
+ // Navigate to dashboard with the search query
+ const searchParams = new URLSearchParams();
+ if (inputValue.trim()) {
+ searchParams.set("prompt", inputValue.trim());
+ }
+ router.push(`/dashboard?${searchParams.toString()}`);
+ };
+
return (
{
}
endContent={
-
+
Generate
diff --git a/config/site.ts b/config/site.ts
index b75c348..cd4a88b 100644
--- a/config/site.ts
+++ b/config/site.ts
@@ -5,14 +5,14 @@ export const siteConfig = {
description:
"Using AI to create engaging thumbnails for online content using advanced language models and image processing. Streamlines the thumbnail creation workflow and enhances visual presentation to boost engagement.",
navItems: [
- // {
- // label: "Home",
- // href: "/",
- // },
- // {
- // label: "Pricing",
- // href: "/pricing",
- // },
+ {
+ label: "Home",
+ href: "/",
+ },
+ {
+ label: "Dashboard",
+ href: "/dashboard",
+ },
],
navMenuItems: [
{
@@ -20,6 +20,11 @@ export const siteConfig = {
href: "/",
protected: false,
},
+ {
+ label: "Dashboard",
+ href: "/dashboard",
+ protected: true,
+ },
{
label: "About Us",
href: "/about",
diff --git a/lib/ai/fal.ts b/lib/ai/fal.ts
new file mode 100644
index 0000000..199cfb0
--- /dev/null
+++ b/lib/ai/fal.ts
@@ -0,0 +1,264 @@
+// fal.ai Provider Implementation
+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';
+
+// 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",
+ 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",
+ negative: "boring, dull, poor lighting, low quality",
+ basePrompt: "gaming content thumbnail",
+ },
+ tutorial: {
+ 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",
+ negative: "artificial, fake, poor lighting, low quality",
+ basePrompt: "lifestyle content thumbnail",
+ },
+};
+
+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',
+ 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',
+ 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',
+ steps: { fast: 8, balanced: 16, high: 25 },
+ guidance: { fast: 3.0, balanced: 5.0, high: 7.0 },
+ },
+};
+
+export async function generateThumbnail(
+ options: ThumbnailGenerationOptions
+): Promise {
+ const {
+ prompt,
+ style = "tech",
+ model = "flux-schnell",
+ quality = "balanced",
+ refinementPrompt,
+ } = options;
+
+ // Validate API key
+ if (!process.env.FAL_KEY) {
+ throw new Error("FAL_KEY environment variable is not set");
+ }
+
+ // Get model configuration
+ const selectedModel = models[model as keyof typeof models];
+ if (!selectedModel) {
+ throw new Error(`Model ${model} not found`);
+ }
+
+ const styleConfig = stylePrompts[style];
+
+ // Build the prompt with optional refinement
+ let finalPrompt = `${styleConfig.basePrompt}, ${prompt}, ${styleConfig.positive}`;
+ if (refinementPrompt) {
+ finalPrompt += `, ${refinementPrompt}`;
+ }
+
+ // Build negative prompt
+ const negativePrompt = `${styleConfig.negative}, ${universalNegativePrompt}`;
+
+ // Generation parameters
+ const qualitySettings = selectedModel.steps[quality];
+ const guidanceSettings = selectedModel.guidance[quality];
+
+ const parameters: GenerationParameters = {
+ model: selectedModel.id,
+ width: 1024,
+ height: 576, // 16:9 aspect ratio
+ steps: qualitySettings,
+ guidance_scale: guidanceSettings,
+ negative_prompt: negativePrompt,
+ };
+
+ try {
+ console.log("๐จ Generating with fal.ai:", {
+ model: selectedModel.name,
+ prompt: finalPrompt.substring(0, 80) + "...",
+ quality,
+ style,
+ steps: parameters.steps,
+ guidance: parameters.guidance_scale,
+ dimensions: `${parameters.width}x${parameters.height}`,
+ });
+
+ // Prepare request body based on model
+ let requestBody: any = {
+ prompt: finalPrompt,
+ negative_prompt: negativePrompt,
+ image_size: {
+ width: parameters.width,
+ height: parameters.height,
+ },
+ num_images: 1,
+ enable_safety_checker: true,
+ output_format: "jpeg",
+ };
+
+ // Add model-specific parameters
+ if (model === 'flux-schnell') {
+ requestBody.num_inference_steps = parameters.steps;
+ } else if (model === 'flux-dev') {
+ requestBody.num_inference_steps = parameters.steps;
+ requestBody.guidance_scale = parameters.guidance_scale;
+ } 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',
+ headers: {
+ 'Authorization': `Key ${process.env.FAL_KEY}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(requestBody),
+ });
+
+ 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");
+ }
+ if (response.status === 429) {
+ throw new Error("fal.ai rate limit exceeded. Please try again later.");
+ }
+ if (response.status === 400) {
+ 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 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");
+ }
+
+ // 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");
+ }
+
+ const imageBlob = await imageResponse.blob();
+
+ console.log("โ
fal.ai generation successful");
+
+ return {
+ imageBlob,
+ prompt: finalPrompt,
+ style,
+ model: selectedModel.name,
+ 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}`);
+ }
+}
+
+export async function testConnection(): Promise {
+ try {
+ if (!process.env.FAL_KEY) {
+ return false;
+ }
+
+ 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',
+ headers: {
+ 'Authorization': `Key ${process.env.FAL_KEY}`,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ prompt: "test image",
+ image_size: {
+ width: 512,
+ height: 512,
+ },
+ num_images: 1,
+ num_inference_steps: 1,
+ }),
+ });
+
+ if (response.ok) {
+ console.log("โ
fal.ai connection test successful");
+ return true;
+ } else if (response.status === 402) {
+ console.log("โ ๏ธ fal.ai connection OK but requires payment");
+ return true; // Connection works, just needs credits
+ } else {
+ console.error("โ fal.ai connection test failed:", response.status);
+ return false;
+ }
+ } catch (error) {
+ console.error("โ fal.ai connection test failed:", error);
+ return false;
+ }
+}
+
+export async function testThumbnailGeneration(): Promise {
+ console.log("๐งช Testing fal.ai thumbnail generation...");
+
+ try {
+ const result = await generateThumbnail({
+ prompt: "iPhone review",
+ style: "tech",
+ 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");
+ } catch (error) {
+ 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
new file mode 100644
index 0000000..9c15f43
--- /dev/null
+++ b/lib/ai/huggingface.ts
@@ -0,0 +1,249 @@
+import { HfInference } from "@huggingface/inference";
+
+const hf = new HfInference(process.env.HUGGINGFACE_API_KEY);
+
+export interface ThumbnailGenerationOptions {
+ prompt: string;
+ style?: "tech" | "gaming" | "tutorial" | "lifestyle";
+ userId?: string;
+ model?: "sdxl" | "sd15" | "sd21";
+ quality?: "fast" | "balanced" | "high";
+}
+
+export interface ThumbnailResult {
+ imageBlob: Blob;
+ prompt: string;
+ style: string;
+ model: string;
+ provider: string;
+ parameters: GenerationParameters;
+}
+
+interface GenerationParameters {
+ model: string;
+ steps: number;
+ guidance_scale: number;
+ negative_prompt: string;
+ width: number;
+ height: number;
+}
+
+// FREE TIER WORKING MODELS - No gated access required
+const models = {
+ sdxl: {
+ id: "stabilityai/stable-diffusion-xl-base-1.0",
+ name: "Stable Diffusion XL",
+ description: "Best balance of quality and speed",
+ recommended: true,
+ },
+ sd15: {
+ id: "runwayml/stable-diffusion-v1-5",
+ name: "Stable Diffusion 1.5",
+ description: "Fast and reliable generation",
+ recommended: false,
+ },
+ sd21: {
+ id: "stabilityai/stable-diffusion-2-1",
+ name: "Stable Diffusion 2.1",
+ description: "Good quality output",
+ recommended: false,
+ },
+};
+
+// Optimized quality presets for free tier
+const qualityPresets = {
+ fast: {
+ steps: 15,
+ guidance_scale: 7.0,
+ description: "Quick generation (~10s)",
+ },
+ balanced: {
+ steps: 25,
+ guidance_scale: 7.5,
+ description: "Good quality (~20s)",
+ },
+ high: {
+ steps: 35,
+ guidance_scale: 8.0,
+ description: "Best quality (~30s)",
+ },
+};
+
+// Simple, effective prompts that work well with free models
+const stylePrompts = {
+ tech: {
+ 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",
+ negative: "boring, dull, poor lighting",
+ basePrompt: "gaming thumbnail",
+ },
+ tutorial: {
+ positive: "educational content, clear presentation, professional layout",
+ negative: "confusing, cluttered, messy",
+ basePrompt: "tutorial thumbnail",
+ },
+ lifestyle: {
+ positive: "lifestyle photo, natural lighting, authentic, warm",
+ negative: "artificial, fake, poor lighting",
+ basePrompt: "lifestyle thumbnail",
+ },
+};
+
+// Minimal negative prompt for better results
+const universalNegativePrompt = "low quality, blurry, amateur, watermark";
+
+export async function generateThumbnail(
+ options: ThumbnailGenerationOptions
+): Promise {
+ const {
+ prompt,
+ style = "tech",
+ model = "sdxl",
+ quality = "balanced",
+ } = options;
+
+ // Validate API key
+ if (!process.env.HUGGINGFACE_API_KEY) {
+ 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];
+
+ // Build simple, effective prompt
+ const optimizedPrompt = `${styleConfig.basePrompt}, ${prompt}, ${styleConfig.positive}`;
+
+ // Simple negative prompt
+ const negativePrompt = `${styleConfig.negative}, ${universalNegativePrompt}`;
+
+ // Generation parameters optimized for free tier
+ const parameters: GenerationParameters = {
+ model: selectedModel.id,
+ width: 768, // Smaller size for free tier
+ height: 432, // 16:9 ratio
+ steps: qualitySettings.steps,
+ guidance_scale: qualitySettings.guidance_scale,
+ negative_prompt: negativePrompt,
+ };
+
+ try {
+ console.log("๐จ Generating with FREE TIER optimized parameters:", {
+ model: selectedModel.name,
+ prompt: optimizedPrompt.substring(0, 80) + "...",
+ quality,
+ style,
+ steps: parameters.steps,
+ guidance: parameters.guidance_scale,
+ dimensions: `${parameters.width}x${parameters.height}`,
+ });
+
+ const response = await hf.textToImage({
+ model: selectedModel.id,
+ inputs: optimizedPrompt,
+ parameters: {
+ width: parameters.width,
+ height: parameters.height,
+ num_inference_steps: parameters.steps,
+ guidance_scale: parameters.guidance_scale,
+ negative_prompt: parameters.negative_prompt,
+ },
+ });
+
+ console.log("โ
Thumbnail generation successful");
+
+ return {
+ imageBlob: response as unknown as Blob,
+ prompt: optimizedPrompt,
+ style,
+ model: selectedModel.name,
+ 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("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("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"}`);
+ }
+}
+
+// Get available models for UI
+export function getAvailableModels() {
+ return Object.entries(models).map(([key, model]) => ({
+ key,
+ ...model,
+ }));
+}
+
+// Get quality presets for UI
+export function getQualityPresets() {
+ return Object.entries(qualityPresets).map(([key, preset]) => ({
+ key,
+ ...preset,
+ }));
+}
+
+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",
+ parameters: {
+ width: 512,
+ height: 512,
+ num_inference_steps: 10,
+ guidance_scale: 7.5,
+ },
+ });
+
+ console.log("โ
HuggingFace connection test successful");
+ return response !== null;
+ } catch (error) {
+ console.error("โ HuggingFace connection test failed:", error);
+ return false;
+ }
+}
+
+// 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",
+ style: "tech",
+ 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");
+ } catch (error) {
+ console.error("โ Test thumbnail generation failed:", error);
+ throw error;
+ }
+}
diff --git a/lib/ai/index.ts b/lib/ai/index.ts
new file mode 100644
index 0000000..06455f6
--- /dev/null
+++ b/lib/ai/index.ts
@@ -0,0 +1,142 @@
+// Central AI Provider Manager
+import {
+ getProvider,
+ getAvailableProviders,
+ getModelsForProvider,
+ providerSupportsRefinement,
+ ThumbnailGenerationOptions,
+ ThumbnailResult,
+ AIProvider,
+ AIModel
+} from './providers';
+
+export {
+ getAvailableProviders,
+ getModelsForProvider,
+ providerSupportsRefinement,
+ type AIProvider,
+ type AIModel,
+ type ThumbnailGenerationOptions,
+ type ThumbnailResult
+};
+
+/**
+ * Generate a thumbnail using the specified AI provider
+ */
+export async function generateThumbnail(
+ options: ThumbnailGenerationOptions
+): Promise {
+ 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) {
+ console.error(`โ Error with provider ${selectedProvider.name}:`, error);
+ throw error;
+ }
+}
+
+/**
+ * Test connection to a specific AI provider
+ */
+export async function testConnection(provider: string): Promise {
+ const selectedProvider = getProvider(provider);
+ if (!selectedProvider) {
+ throw new Error(`Provider ${provider} not found`);
+ }
+
+ console.log(`๐ Testing connection to ${selectedProvider.name}...`);
+
+ try {
+ return await selectedProvider.testConnection();
+ } catch (error) {
+ console.error(`โ Connection test failed for ${selectedProvider.name}:`, error);
+ return false;
+ }
+}
+
+/**
+ * Test all available providers and return their status
+ */
+export async function testAllProviders(): Promise> {
+ const providers = getAvailableProviders();
+ const results: Record = {};
+
+ console.log('๐งช Testing all AI providers...');
+
+ for (const provider of providers) {
+ try {
+ results[provider.id] = await testConnection(provider.id);
+ } catch (error) {
+ console.error(`โ Test failed for ${provider.name}:`, error);
+ results[provider.id] = false;
+ }
+ }
+
+ return results;
+}
+
+/**
+ * Get the best available provider based on API key availability
+ */
+export async function getBestAvailableProvider(): Promise {
+ const providers = getAvailableProviders();
+
+ // Test providers in order of preference
+ const preferenceOrder = ['stability', 'fal', 'huggingface'];
+
+ for (const providerId of preferenceOrder) {
+ const provider = providers.find(p => p.id === providerId);
+ if (provider) {
+ const isAvailable = await testConnection(providerId);
+ if (isAvailable) {
+ console.log(`โ
Best available provider: ${provider.name}`);
+ return providerId;
+ }
+ }
+ }
+
+ // Fallback to first provider
+ console.log('โ ๏ธ No providers available, falling back to HuggingFace');
+ return 'huggingface';
+}
+
+/**
+ * Get provider availability status
+ */
+export async function getProviderStatus(): Promise> {
+ const providers = getAvailableProviders();
+ const status: Record = {};
+
+ for (const provider of providers) {
+ const isAvailable = await testConnection(provider.id);
+ status[provider.id] = {
+ available: isAvailable,
+ name: provider.name,
+ description: provider.description,
+ pricing: provider.pricing,
+ models: provider.models,
+ };
+ }
+
+ return status;
+}
\ No newline at end of file
diff --git a/lib/ai/providers.ts b/lib/ai/providers.ts
new file mode 100644
index 0000000..5d2d11d
--- /dev/null
+++ b/lib/ai/providers.ts
@@ -0,0 +1,207 @@
+// Provider abstraction for AI image generation services
+export interface AIProvider {
+ id: string;
+ name: string;
+ description: string;
+ pricing: string;
+ models: AIModel[];
+ supportsRefinement: boolean;
+ generateThumbnail: (options: ThumbnailGenerationOptions) => Promise;
+ testConnection: () => Promise;
+}
+
+export interface AIModel {
+ id: string;
+ name: string;
+ description: string;
+ icon: string;
+ recommended: boolean;
+ speed: 'fast' | 'medium' | 'slow';
+ quality: 'good' | 'high' | 'excellent';
+}
+
+export interface ThumbnailGenerationOptions {
+ prompt: string;
+ style?: "tech" | "gaming" | "tutorial" | "lifestyle";
+ userId?: string;
+ model?: string;
+ quality?: "fast" | "balanced" | "high";
+ provider?: string;
+ refinementPrompt?: string;
+}
+
+export interface ThumbnailResult {
+ imageBlob: Blob;
+ prompt: string;
+ style: string;
+ model: string;
+ provider: string;
+ parameters: GenerationParameters;
+}
+
+export interface GenerationParameters {
+ model: string;
+ steps: number;
+ guidance_scale: number;
+ negative_prompt: string;
+ width: number;
+ height: number;
+}
+
+// Provider configurations
+export const AI_PROVIDERS: Record = {
+ stability: {
+ 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: '๐ฏ',
+ recommended: true,
+ speed: 'medium',
+ quality: 'excellent'
+ },
+ {
+ id: 'sd-3.5-turbo',
+ name: 'Stable Diffusion 3.5 Turbo',
+ description: '4-step generation, ultra-fast',
+ icon: 'โก',
+ recommended: false,
+ speed: 'fast',
+ quality: 'high'
+ },
+ {
+ id: 'sdxl',
+ name: 'Stable Diffusion XL',
+ description: 'Proven quality, reliable',
+ icon: '๐ผ๏ธ',
+ recommended: false,
+ speed: 'medium',
+ quality: 'high'
+ }
+ ],
+ generateThumbnail: async (options: ThumbnailGenerationOptions) => {
+ const { generateThumbnail: stabilityGenerate } = await import('./stability');
+ return stabilityGenerate(options);
+ },
+ testConnection: async () => {
+ const { testConnection: stabilityTest } = await import('./stability');
+ return stabilityTest();
+ }
+ },
+ fal: {
+ 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: '๐',
+ recommended: true,
+ speed: 'fast',
+ quality: 'high'
+ },
+ {
+ id: 'flux-dev',
+ name: 'FLUX.1 Dev',
+ description: 'Premium quality, 40 images per $1',
+ icon: '๐',
+ recommended: false,
+ speed: 'medium',
+ quality: 'excellent'
+ },
+ {
+ id: 'hidream-fast',
+ name: 'HiDream I1 Fast',
+ description: 'New model, 16 steps',
+ icon: 'โจ',
+ recommended: false,
+ speed: 'fast',
+ quality: 'high'
+ }
+ ],
+ generateThumbnail: async (options: ThumbnailGenerationOptions) => {
+ const { generateThumbnail: falGenerate } = await import('./fal');
+ return falGenerate(options);
+ },
+ testConnection: async () => {
+ const { testConnection: falTest } = await import('./fal');
+ return falTest();
+ }
+ },
+ huggingface: {
+ 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: 'โก',
+ recommended: true,
+ speed: 'medium',
+ quality: 'high'
+ },
+ {
+ id: 'sd15',
+ name: 'Stable Diffusion 1.5',
+ description: 'Fast and reliable generation',
+ icon: '๐',
+ recommended: false,
+ speed: 'fast',
+ quality: 'good'
+ },
+ {
+ id: 'sd21',
+ name: 'Stable Diffusion 2.1',
+ description: 'Good quality output',
+ icon: '๐ธ',
+ recommended: false,
+ speed: 'medium',
+ quality: 'good'
+ }
+ ],
+ generateThumbnail: async (options: ThumbnailGenerationOptions) => {
+ const { generateThumbnail: hfGenerate } = await import('./huggingface');
+ return hfGenerate(options);
+ },
+ testConnection: async () => {
+ const { testConnection: hfTest } = await import('./huggingface');
+ return hfTest();
+ }
+ }
+};
+
+// Get available providers
+export function getAvailableProviders(): AIProvider[] {
+ return Object.values(AI_PROVIDERS);
+}
+
+// Get provider by ID
+export function getProvider(providerId: string): AIProvider | null {
+ return AI_PROVIDERS[providerId] || null;
+}
+
+// Get models for a specific provider
+export function getModelsForProvider(providerId: string): AIModel[] {
+ const provider = getProvider(providerId);
+ return provider?.models || [];
+}
+
+// Check if provider supports refinement
+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
new file mode 100644
index 0000000..d80dbd9
--- /dev/null
+++ b/lib/ai/stability.ts
@@ -0,0 +1,233 @@
+// Stability AI Provider Implementation
+import { ThumbnailGenerationOptions, ThumbnailResult, GenerationParameters } from './providers';
+
+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",
+ 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",
+ negative: "boring, dull, poor lighting, low quality",
+ basePrompt: "gaming content thumbnail",
+ },
+ tutorial: {
+ 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",
+ negative: "artificial, fake, poor lighting, low quality",
+ basePrompt: "lifestyle content thumbnail",
+ },
+};
+
+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',
+ 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',
+ 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',
+ steps: { fast: 15, balanced: 25, high: 35 },
+ guidance: { fast: 6.0, balanced: 7.5, high: 8.5 },
+ },
+};
+
+export async function generateThumbnail(
+ options: ThumbnailGenerationOptions
+): Promise {
+ const {
+ prompt,
+ style = "tech",
+ model = "sd-3.5-large",
+ quality = "balanced",
+ refinementPrompt,
+ } = options;
+
+ // Validate API key
+ if (!process.env.STABILITY_API_KEY) {
+ throw new Error("STABILITY_API_KEY environment variable is not set");
+ }
+
+ // Get model configuration
+ const selectedModel = models[model as keyof typeof models];
+ if (!selectedModel) {
+ throw new Error(`Model ${model} not found`);
+ }
+
+ const styleConfig = stylePrompts[style];
+
+ // Build the prompt with optional refinement
+ let finalPrompt = `${styleConfig.basePrompt}, ${prompt}, ${styleConfig.positive}`;
+ if (refinementPrompt) {
+ finalPrompt += `, ${refinementPrompt}`;
+ }
+
+ // Build negative prompt
+ const negativePrompt = `${styleConfig.negative}, ${universalNegativePrompt}`;
+
+ // Generation parameters
+ const qualitySettings = selectedModel.steps[quality];
+ const guidanceSettings = selectedModel.guidance[quality];
+
+ const parameters: GenerationParameters = {
+ model: selectedModel.id,
+ width: 1024,
+ height: 576, // 16:9 aspect ratio
+ steps: qualitySettings,
+ guidance_scale: guidanceSettings,
+ negative_prompt: negativePrompt,
+ };
+
+ try {
+ console.log("๐จ Generating with Stability AI:", {
+ model: selectedModel.name,
+ prompt: finalPrompt.substring(0, 80) + "...",
+ quality,
+ style,
+ steps: parameters.steps,
+ guidance: parameters.guidance_scale,
+ 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',
+ }),
+ });
+
+ 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.");
+ }
+ if (response.status === 400) {
+ throw new Error("Invalid request to Stability AI. Please check your prompt.");
+ }
+
+ 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' });
+
+ console.log("โ
Stability AI generation successful");
+
+ return {
+ imageBlob,
+ prompt: finalPrompt,
+ style,
+ model: selectedModel.name,
+ 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}`);
+ }
+}
+
+export async function testConnection(): Promise {
+ try {
+ if (!process.env.STABILITY_API_KEY) {
+ return false;
+ }
+
+ console.log("๐ Testing Stability AI connection...");
+
+ const response = await fetch('https://api.stability.ai/v1/user/account', {
+ headers: {
+ 'Authorization': `Bearer ${process.env.STABILITY_API_KEY}`,
+ },
+ });
+
+ if (response.ok) {
+ console.log("โ
Stability AI connection test successful");
+ return true;
+ } else {
+ console.error("โ Stability AI connection test failed:", response.status);
+ return false;
+ }
+ } catch (error) {
+ console.error("โ Stability AI connection test failed:", error);
+ return false;
+ }
+}
+
+export async function testThumbnailGeneration(): Promise {
+ console.log("๐งช Testing Stability AI thumbnail generation...");
+
+ try {
+ const result = await generateThumbnail({
+ prompt: "iPhone review",
+ style: "tech",
+ 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");
+ } catch (error) {
+ console.error("โ Stability AI test generation failed:", error);
+ throw error;
+ }
+}
\ No newline at end of file
diff --git a/lib/firebase/firebaseConfig.ts b/lib/firebase/firebaseConfig.ts
index 2601784..90d3e62 100644
--- a/lib/firebase/firebaseConfig.ts
+++ b/lib/firebase/firebaseConfig.ts
@@ -4,16 +4,17 @@ import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
- apiKey: process.env.NEXT_FIREBASE_API_KEY,
- authDomain: process.env.NEXT_FIREBASE_AUTH_DOMAIN,
- projectId: process.env.NEXT_FIREBASE_PROJECT_ID,
- storageBucket: process.env.NEXT_FIREBASE_STORAGE_BUCKET,
- messagingSenderId: process.env.NEXT_FIREBASE_SENDER_ID,
- appId: process.env.NEXT_FIREBASE_APP_ID,
- measurementId: process.env.NEXT_FIREBASE_MEASUREMENT_ID,
+ apiKey: process.env.NEXT_FIREBASE_API_KEY || "demo-key",
+ authDomain:
+ process.env.NEXT_FIREBASE_AUTH_DOMAIN || "demo-project.firebaseapp.com",
+ projectId: process.env.NEXT_FIREBASE_PROJECT_ID || "demo-project",
+ storageBucket:
+ process.env.NEXT_FIREBASE_STORAGE_BUCKET || "demo-project.appspot.com",
+ messagingSenderId: process.env.NEXT_FIREBASE_SENDER_ID || "123456789",
+ appId: process.env.NEXT_FIREBASE_APP_ID || "1:123456789:web:abcdef",
+ measurementId: process.env.NEXT_FIREBASE_MEASUREMENT_ID || "G-ABCDEF",
};
-console.log(process.env.NEXT_FIREBASE_API_KEY);
// Initialize Firebase
const app = initializeApp(firebaseConfig);
diff --git a/package.json b/package.json
index eeed28f..fcbc829 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"dependencies": {
"@firebase/firestore": "^4.6.3",
"@hookform/resolvers": "^3.6.0",
+ "@huggingface/inference": "^4.3.2",
"@nextui-org/button": "^2.0.32",
"@nextui-org/code": "^2.0.28",
"@nextui-org/input": "^2.2.0",
@@ -33,6 +34,7 @@
"@types/react-slick": "^0.23.13",
"@typescript-eslint/eslint-plugin": "^7.10.0",
"@typescript-eslint/parser": "^7.10.0",
+ "ai": "^4.3.16",
"autoprefixer": "10.4.19",
"clsx": "^2.0.0",
"cogo-toast": "^4.2.3",
@@ -52,6 +54,7 @@
"firebase-admin": "^12.1.1",
"framer-motion": "^11.2.10",
"intl-messageformat": "^10.5.0",
+ "lucide-react": "^0.525.0",
"next": "14.2.3",
"next-themes": "^0.2.1",
"postcss": "8.4.38",
@@ -61,6 +64,7 @@
"react-hook-form": "^7.52.0",
"react-slick": "^0.30.2",
"sharp": "^0.33.4",
+ "stability-ai": "^0.7.0",
"tailwind-variants": "^0.1.20",
"tailwindcss": "3.4.3",
"ts-node": "^10.9.2",
diff --git a/yarn.lock b/yarn.lock
index 45b3f5c..d7eeffe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7,6 +7,41 @@
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.0.tgz#728c484f4e10df03d5a3acd0d8adcbbebff8ad63"
integrity sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==
+"@ai-sdk/provider-utils@2.2.8":
+ version "2.2.8"
+ resolved "https://registry.yarnpkg.com/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz#ad11b92d5a1763ab34ba7b5fc42494bfe08b76d1"
+ integrity sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==
+ dependencies:
+ "@ai-sdk/provider" "1.1.3"
+ nanoid "^3.3.8"
+ secure-json-parse "^2.7.0"
+
+"@ai-sdk/provider@1.1.3":
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/@ai-sdk/provider/-/provider-1.1.3.tgz#ebdda8077b8d2b3f290dcba32c45ad19b2704681"
+ integrity sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==
+ dependencies:
+ json-schema "^0.4.0"
+
+"@ai-sdk/react@1.2.12":
+ version "1.2.12"
+ resolved "https://registry.yarnpkg.com/@ai-sdk/react/-/react-1.2.12.tgz#f4250b6df566b170af98a71d5708b52108dd0ce1"
+ integrity sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==
+ dependencies:
+ "@ai-sdk/provider-utils" "2.2.8"
+ "@ai-sdk/ui-utils" "1.2.11"
+ swr "^2.2.5"
+ throttleit "2.1.0"
+
+"@ai-sdk/ui-utils@1.2.11":
+ version "1.2.11"
+ resolved "https://registry.yarnpkg.com/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz#4f815589d08d8fef7292ade54ee5db5d09652603"
+ integrity sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==
+ dependencies:
+ "@ai-sdk/provider" "1.1.3"
+ "@ai-sdk/provider-utils" "2.2.8"
+ zod-to-json-schema "^3.24.1"
+
"@alloc/quick-lru@^5.2.0":
version "5.2.0"
resolved "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz"
@@ -879,6 +914,24 @@
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.6.0.tgz#71ae08acf7f7624fb24ea0505de00b9001a63687"
integrity sha512-UBcpyOX3+RR+dNnqBd0lchXpoL8p4xC21XP8H6Meb8uve5Br1GCnmg0PcBoKKqPKgGu9GHQ/oygcmPrQhetwqw==
+"@huggingface/inference@^4.3.2":
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/@huggingface/inference/-/inference-4.3.2.tgz#412a9098d228c33fb3382e107ac772aba3799b23"
+ integrity sha512-c7MJJPDbhb0Xy3JHvO3LaRhCDnfAthdmV3UiLCYH440UkIkECGwaLHAsWg9G2gdUrmcfzybZvZ0lAQhwsiTKnA==
+ dependencies:
+ "@huggingface/jinja" "^0.5.0"
+ "@huggingface/tasks" "^0.19.22"
+
+"@huggingface/jinja@^0.5.0":
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/@huggingface/jinja/-/jinja-0.5.0.tgz#0da65deb98798cd24ea42ad13f6df224ce23f443"
+ integrity sha512-Ptc03/jGRiYRoi0bUYKZ14MkDslsBRT24oxmsvUlfYrvQMldrxCevhPnT+hfX8awKTT8/f/0ZBBWldoeAcMHdQ==
+
+"@huggingface/tasks@^0.19.22":
+ version "0.19.22"
+ resolved "https://registry.yarnpkg.com/@huggingface/tasks/-/tasks-0.19.22.tgz#0759707ed1e74fd2c3d2c03a7fd20e35ea2d43af"
+ integrity sha512-jtRXsJZTES01X4gJ5VOUnEm3ONyyfXUcWKObbWkr/SQmjaH/kxtWqc2zVWKaxL4QLoXqXJ+T+Pi5xupMStSudQ==
+
"@humanwhocodes/config-array@^0.11.14":
version "0.11.14"
resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz"
@@ -2302,6 +2355,11 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
+"@opentelemetry/api@1.9.0":
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe"
+ integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==
+
"@pkgjs/parseargs@^0.11.0":
version "0.11.0"
resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
@@ -3865,6 +3923,11 @@
dependencies:
"@types/node" "*"
+"@types/diff-match-patch@^1.0.36":
+ version "1.0.36"
+ resolved "https://registry.yarnpkg.com/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz#dcef10a69d357fe9d43ac4ff2eca6b85dbf466af"
+ integrity sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==
+
"@types/express-serve-static-core@^4.17.33":
version "4.19.3"
resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.3.tgz"
@@ -4253,6 +4316,18 @@ agent-base@^7.0.2:
dependencies:
debug "^4.3.4"
+ai@^4.3.16:
+ version "4.3.16"
+ resolved "https://registry.yarnpkg.com/ai/-/ai-4.3.16.tgz#c9446da1024cdc1dfe2913d151b70c91d40f2378"
+ integrity sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g==
+ dependencies:
+ "@ai-sdk/provider" "1.1.3"
+ "@ai-sdk/provider-utils" "2.2.8"
+ "@ai-sdk/react" "1.2.12"
+ "@ai-sdk/ui-utils" "1.2.11"
+ "@opentelemetry/api" "1.9.0"
+ jsondiffpatch "0.6.0"
+
ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
@@ -4496,6 +4571,15 @@ axe-core@=4.7.0:
resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz"
integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==
+axios@^1.6.8:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-1.10.0.tgz#af320aee8632eaf2a400b6a1979fa75856f38d54"
+ integrity sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==
+ dependencies:
+ follow-redirects "^1.15.6"
+ form-data "^4.0.0"
+ proxy-from-env "^1.1.0"
+
axobject-query@^3.2.1:
version "3.2.1"
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz"
@@ -4732,6 +4816,11 @@ chalk@^4.0.0, chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+chalk@^5.3.0:
+ version "5.4.1"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8"
+ integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==
+
char-regex@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
@@ -5084,6 +5173,11 @@ didyoumean@^1.2.2:
resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz"
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
+diff-match-patch@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.5.tgz#abb584d5f10cd1196dfc55aa03701592ae3f7b37"
+ integrity sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==
+
diff-sequences@^29.6.3:
version "29.6.3"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
@@ -5852,6 +5946,11 @@ flatted@^3.2.9:
resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz"
integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
+follow-redirects@^1.15.6:
+ version "1.15.9"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
+ integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
+
for-each@^0.3.3:
version "0.3.3"
resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz"
@@ -5902,6 +6001,15 @@ fs-constants@^1.0.0:
resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
+fs-extra@^11.2.0:
+ version "11.3.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
+ integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
+ dependencies:
+ graceful-fs "^4.2.0"
+ jsonfile "^6.0.1"
+ universalify "^2.0.0"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
@@ -6130,7 +6238,7 @@ gopd@^1.0.1:
dependencies:
get-intrinsic "^1.1.3"
-graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
+graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
version "4.2.11"
resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
@@ -7098,6 +7206,11 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+json-schema@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
+ integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
+
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
@@ -7122,6 +7235,24 @@ json5@^2.2.3:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+jsondiffpatch@0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz#daa6a25bedf0830974c81545568d5f671c82551f"
+ integrity sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==
+ dependencies:
+ "@types/diff-match-patch" "^1.0.36"
+ chalk "^5.3.0"
+ diff-match-patch "^1.0.5"
+
+jsonfile@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
+ integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
+ dependencies:
+ universalify "^2.0.0"
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
jsonwebtoken@^9.0.0:
version "9.0.2"
resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz"
@@ -7389,6 +7520,11 @@ lru-memoizer@^2.2.0:
lodash.clonedeep "^4.5.0"
lru-cache "6.0.0"
+lucide-react@^0.525.0:
+ version "0.525.0"
+ resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.525.0.tgz#5f7bcecd65e4f9b2b5b6b5d295e3376df032d5e3"
+ integrity sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==
+
lz-string@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
@@ -7523,6 +7659,11 @@ nanoid@^3.3.6, nanoid@^3.3.7:
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz"
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
+nanoid@^3.3.8:
+ version "3.3.11"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
+ integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
+
napi-build-utils@^1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz"
@@ -8003,6 +8144,11 @@ protobufjs@7.3.0, protobufjs@^7.2.5, protobufjs@^7.2.6:
"@types/node" ">=13.7.0"
long "^5.0.0"
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
psl@^1.1.33:
version "1.9.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
@@ -8336,6 +8482,11 @@ scroll-into-view-if-needed@3.0.10:
dependencies:
compute-scroll-into-view "^3.0.2"
+secure-json-parse@^2.7.0:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862"
+ integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==
+
semver@^6.1.0, semver@^6.3.0, semver@^6.3.1:
version "6.3.1"
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
@@ -8483,6 +8634,16 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
+stability-ai@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/stability-ai/-/stability-ai-0.7.0.tgz#425feb9eea3d523a3b26756656c5acef9b82a3f7"
+ integrity sha512-uXKhaCgSF0J379zSuLgj8hBEheCV26fF2B8eTFyU3qWO2TzDUlGmLdyQmF6ZGDkADSVyxuxKAPcLeKcQZx9dPQ==
+ dependencies:
+ axios "^1.6.8"
+ dotenv "^16.4.5"
+ fs-extra "^11.2.0"
+ uuid "^9.0.1"
+
stack-utils@^2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"
@@ -8709,6 +8870,14 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+swr@^2.2.5:
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/swr/-/swr-2.3.4.tgz#60bcb5b97cae157a6ef69eff0ed2beb9010eba69"
+ integrity sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg==
+ dependencies:
+ dequal "^2.0.3"
+ use-sync-external-store "^1.4.0"
+
symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
@@ -8827,6 +8996,11 @@ thenify-all@^1.0.0:
dependencies:
any-promise "^1.0.0"
+throttleit@2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-2.1.0.tgz#a7e4aa0bf4845a5bd10daa39ea0c783f631a07b4"
+ integrity sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==
+
tiny-case@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03"
@@ -9030,6 +9204,11 @@ universalify@^0.2.0:
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
+universalify@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
+ integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
+
update-browserslist-db@^1.0.13:
version "1.0.16"
resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz"
@@ -9093,6 +9272,11 @@ use-sidecar@^1.1.2:
detect-node-es "^1.1.0"
tslib "^2.0.0"
+use-sync-external-store@^1.4.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0"
+ integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==
+
util-deprecate@^1.0.1, util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
@@ -9362,3 +9546,8 @@ yup@^1.4.0:
tiny-case "^1.0.3"
toposort "^2.0.2"
type-fest "^2.19.0"
+
+zod-to-json-schema@^3.24.1:
+ version "3.24.6"
+ resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz#5920f020c4d2647edfbb954fa036082b92c9e12d"
+ integrity sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==