├── .gitignore ├── LICENSE ├── README.md ├── assets └── banner.jpeg ├── eslint.config.mjs ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public ├── file.svg ├── globe.svg ├── next.svg ├── vercel.svg └── window.svg ├── src └── app │ ├── api │ └── generate │ │ └── route.ts │ ├── components │ ├── AnimatedBackground.tsx │ └── ImageGenerator.tsx │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Skytells AI Showcase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Skytells Image Generator 2 | 3 | A beautiful and responsive image generator powered by the Skytells AI API. This application showcases how to integrate the Skytells SDK into a Next.js application to create AI-generated images based on text prompts. 4 | 5 | ![Skytells Image Generator Demo](assets/banner.jpeg) 6 | 7 | Skytells offers a range of AI models for image generation, This project demonstrates how to integrate the Skytells SDK into a Next.js application to create AI-generated images based on text prompts. 8 | 9 | 10 | ## Features 11 | 12 | - ✨ Modern UI with animations and visual effects 13 | - 🖼️ Generate images from text prompts using Skytells AI 14 | - 🔄 Loading states and error handling 15 | - 🎨 Responsive design for all devices 16 | - 🌓 Dark and light mode support 17 | - 📱 Mobile-friendly interface 18 | - 🔍 Example prompts for inspiration 19 | 20 | ## Getting Started 21 | 22 | First, Create a Skytells account and get your API key from [https://www.skytells.ai/dashboard/api-keys](https://www.skytells.ai/dashboard/api-keys). 23 | 24 | ### Prerequisites 25 | 26 | - Node.js 18.0.0 or newer 27 | - npm, yarn, or pnpm 28 | 29 | ### Installation 30 | 31 | 1. Clone this repository: 32 | ```bash 33 | git clone https://github.com/skytells-ai/image-generator-saas-nextjs.git 34 | cd image-generator-saas-nextjs 35 | ``` 36 | 37 | 2. Install dependencies: 38 | ```bash 39 | npm install 40 | # or 41 | yarn install 42 | # or 43 | pnpm install 44 | ``` 45 | 46 | 3. Create a `.env.local` file from the example: 47 | ```bash 48 | cp .env.local.example .env.local 49 | ``` 50 | 51 | 4. Add your Skytells API key to the `.env.local` file. You can get your API key from [https://www.skytells.ai/dashboard/api-keys](https://www.skytells.ai/dashboard/api-keys). 52 | 53 | 5. Start the development server: 54 | ```bash 55 | npm run dev 56 | # or 57 | yarn dev 58 | # or 59 | pnpm dev 60 | ``` 61 | 62 | 6. Open [http://localhost:3000](http://localhost:3000) in your browser to see the application. 63 | 64 | ## Project Structure 65 | 66 | ``` 67 | ├── public/ # Static assets 68 | ├── src/ 69 | │ ├── app/ # Next.js App Router 70 | │ │ ├── api/ # API Routes 71 | │ │ │ └── generate/ # Image generation API endpoint 72 | │ │ ├── components/ # React components 73 | │ │ │ ├── AnimatedBackground.tsx # Background animation 74 | │ │ │ └── ImageGenerator.tsx # Main image generator component 75 | │ │ ├── page.tsx # Home page 76 | │ │ ├── layout.tsx # Root layout 77 | │ │ └── globals.css # Global styles 78 | ├── .env.local.example # Example environment variables 79 | ├── next.config.ts # Next.js configuration 80 | ├── package.json # Dependencies and scripts 81 | └── tsconfig.json # TypeScript configuration 82 | ``` 83 | 84 | ## How It Works 85 | 86 | The application uses the Skytells TypeScript SDK to communicate with the Skytells AI API for image generation: 87 | 88 | ### Frontend (React/Next.js) 89 | 90 | 1. The main UI is in `src/app/components/ImageGenerator.tsx`, which provides: 91 | - A text input field for the user's prompt 92 | - Example prompts for inspiration 93 | - A "Generate" button to trigger the image generation 94 | - Loading states and error handling 95 | - Display of the generated image 96 | 97 | 2. When a user submits a prompt, the application sends a POST request to the local API endpoint (`/api/generate`). 98 | 99 | ### Backend (Next.js API Route) 100 | 101 | 1. The API route in `src/app/api/generate/route.ts` handles the request: 102 | - Initializes the Skytells client with your API key 103 | - Sends the prompt to the Skytells API using the SDK 104 | - Returns the generated image to the frontend 105 | 106 | 2. The Skytells SDK makes it simple to interact with their AI models: 107 | ```typescript 108 | const skytells = createClient(process.env.SKYTELLS_API_KEY || ''); 109 | 110 | const prediction = await skytells.predict({ 111 | model: 'truefusion-pro', 112 | input: { 113 | prompt: prompt 114 | } 115 | }); 116 | ``` 117 | 118 | ## Skytells AI Documentation 119 | 120 | For comprehensive documentation on the Skytells AI platform, visit: 121 | 122 | - **General Documentation:** [https://docs.skytells.ai](https://docs.skytells.ai) 123 | - **TypeScript SDK Documentation:** [https://docs.skytells.ai/sdks/ts](https://docs.skytells.ai/sdks/ts) 124 | - **Available Models:** [https://docs.skytells.ai/models](https://docs.skytells.ai/models) 125 | - **API Docs:** [https://docs.skytells.ai](https://docs.skytells.ai) 126 | 127 | ## Environment Variables 128 | 129 | | Variable | Description | 130 | |----------|-------------| 131 | | `SKYTELLS_API_KEY` | Your Skytells API key from the dashboard | 132 | 133 | ## Learn More 134 | 135 | - [Skytells Documentation](https://docs.skytells.ai) 136 | - [Next.js Documentation](https://nextjs.org/docs) 137 | - [React Documentation](https://react.dev) 138 | 139 | ## Contributing 140 | 141 | If you'd like to contribute to this project, please fork the repository and create a pull request. 142 | 143 | ## Repository 144 | 145 | [https://github.com/skytells-ai/image-generator-saas-nextjs](https://github.com/skytells-ai/image-generator-saas-nextjs) 146 | 147 | Made with ❤️ by [Skytells](https://www.skytells.ai) 148 | 149 | ## License 150 | 151 | This project is licensed under the MIT License - see the LICENSE file for details. 152 | -------------------------------------------------------------------------------- /assets/banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skytells-ai/image-generator-saas-nextjs/c40334130214bc0b5329a5e9c49087b6c532e3d0/assets/banner.jpeg -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | images: { 5 | domains: ['cdn.skytells.ai', 'skytells-ai.s3.us-east-1.amazonaws.com', 'storage.skytells.ai', 'api.skytells.ai', 'skytells.ai', 'localhost'], 6 | }, 7 | reactStrictMode: true, 8 | }; 9 | 10 | export default nextConfig; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-image-generator", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "15.2.3", 13 | "react": "^19.0.0", 14 | "react-dom": "^19.0.0", 15 | "skytells": "^1.0.0" 16 | }, 17 | "devDependencies": { 18 | "@eslint/eslintrc": "^3", 19 | "@tailwindcss/postcss": "^4", 20 | "@types/node": "^20", 21 | "@types/react": "^19", 22 | "@types/react-dom": "^19", 23 | "eslint": "^9", 24 | "eslint-config-next": "15.2.3", 25 | "tailwindcss": "^4", 26 | "typescript": "^5" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/generate/route.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from 'skytells'; 2 | import { NextRequest, NextResponse } from 'next/server'; 3 | 4 | export async function POST(request: NextRequest) { 5 | try { 6 | const { prompt } = await request.json(); 7 | 8 | if (!prompt) { 9 | return NextResponse.json( 10 | { error: 'Prompt is required' }, 11 | { status: 400 } 12 | ); 13 | } 14 | 15 | /** 16 | * Initialize Skytells client 17 | * In production, you'd want to use an environment variable for the API key 18 | * 19 | * @param process.env.SKYTELLS_API_KEY - The API key for the Skytells API 20 | * You can get your API key from the Skytells dashboard 21 | * https://www.skytells.ai/settings/api-keys 22 | */ 23 | const skytells = createClient(process.env.SKYTELLS_API_KEY || ''); 24 | 25 | // Make prediction request to Skytells API 26 | const prediction = await skytells.predict({ 27 | // The model to use for image generation 28 | // truefusion-pro is a powerful model for image generation 29 | // to learn more about the model, visit https://docs.skytells.ai/models 30 | model: 'truefusion-pro', 31 | input: { 32 | prompt: prompt 33 | } 34 | }); 35 | 36 | return NextResponse.json(prediction); 37 | } catch (error: any) { 38 | console.error('Error generating image:', error); 39 | return NextResponse.json( 40 | { error: error.message || 'Failed to generate image' }, 41 | { status: 500 } 42 | ); 43 | } 44 | } -------------------------------------------------------------------------------- /src/app/components/AnimatedBackground.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect, useRef } from 'react'; 4 | 5 | export default function AnimatedBackground() { 6 | const canvasRef = useRef(null); 7 | 8 | useEffect(() => { 9 | const canvas = canvasRef.current; 10 | if (!canvas) return; 11 | 12 | const ctx = canvas.getContext('2d'); 13 | if (!ctx) return; 14 | 15 | // Set canvas dimensions 16 | const setCanvasDimensions = () => { 17 | if (!canvas) return; 18 | canvas.width = window.innerWidth; 19 | canvas.height = window.innerHeight; 20 | }; 21 | 22 | setCanvasDimensions(); 23 | window.addEventListener('resize', setCanvasDimensions); 24 | 25 | // Particle class 26 | class Particle { 27 | x: number; 28 | y: number; 29 | size: number; 30 | speedX: number; 31 | speedY: number; 32 | color: string; 33 | 34 | constructor() { 35 | this.x = Math.random() * (canvas?.width || window.innerWidth); 36 | this.y = Math.random() * (canvas?.height || window.innerHeight); 37 | this.size = Math.random() * 5 + 1; 38 | this.speedX = Math.random() * 3 - 1.5; 39 | this.speedY = Math.random() * 3 - 1.5; 40 | this.color = `rgba(${Math.floor(Math.random() * 100 + 155)}, ${Math.floor(Math.random() * 100 + 155)}, ${Math.floor(Math.random() * 255)}, ${Math.random() * 0.3 + 0.2})`; 41 | } 42 | 43 | update() { 44 | this.x += this.speedX; 45 | this.y += this.speedY; 46 | 47 | if (this.x > (canvas?.width || window.innerWidth) || this.x < 0) { 48 | this.speedX = -this.speedX; 49 | } 50 | 51 | if (this.y > (canvas?.height || window.innerHeight) || this.y < 0) { 52 | this.speedY = -this.speedY; 53 | } 54 | } 55 | 56 | draw() { 57 | if (!ctx) return; 58 | ctx.fillStyle = this.color; 59 | ctx.beginPath(); 60 | ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); 61 | ctx.fill(); 62 | } 63 | } 64 | 65 | // Create particles 66 | const particleCount = Math.min(50, Math.floor(window.innerWidth * window.innerHeight / 20000)); 67 | const particles: Particle[] = []; 68 | 69 | for (let i = 0; i < particleCount; i++) { 70 | particles.push(new Particle()); 71 | } 72 | 73 | // Animation loop 74 | const animate = () => { 75 | if (!ctx || !canvas) return; 76 | ctx.clearRect(0, 0, canvas.width, canvas.height); 77 | 78 | particles.forEach(particle => { 79 | particle.update(); 80 | particle.draw(); 81 | }); 82 | 83 | // Connect particles with lines if they're close enough 84 | for (let i = 0; i < particles.length; i++) { 85 | for (let j = i + 1; j < particles.length; j++) { 86 | const dx = particles[i].x - particles[j].x; 87 | const dy = particles[i].y - particles[j].y; 88 | const distance = Math.sqrt(dx * dx + dy * dy); 89 | 90 | if (distance < 150) { 91 | ctx.beginPath(); 92 | ctx.strokeStyle = `rgba(150, 150, 150, ${(150 - distance) / 150 * 0.2})`; 93 | ctx.lineWidth = 1; 94 | ctx.moveTo(particles[i].x, particles[i].y); 95 | ctx.lineTo(particles[j].x, particles[j].y); 96 | ctx.stroke(); 97 | } 98 | } 99 | } 100 | 101 | requestAnimationFrame(animate); 102 | }; 103 | 104 | animate(); 105 | 106 | return () => { 107 | window.removeEventListener('resize', setCanvasDimensions); 108 | }; 109 | }, []); 110 | 111 | return ( 112 | 116 | ); 117 | } -------------------------------------------------------------------------------- /src/app/components/ImageGenerator.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useRef, useEffect } from 'react'; 4 | import Image from 'next/image'; 5 | 6 | // Example prompts with descriptions 7 | const EXAMPLE_PROMPTS = [ 8 | { text: "A futuristic cityscape at sunset with flying cars", category: "Sci-fi" }, 9 | { text: "A serene mountain lake surrounded by autumn trees", category: "Nature" }, 10 | { text: "A cute robot playing with a cat in a living room", category: "Whimsical" }, 11 | { text: "An astronaut riding a horse on Mars", category: "Surreal" }, 12 | ]; 13 | 14 | export default function ImageGenerator() { 15 | const [prompt, setPrompt] = useState(''); 16 | const [image, setImage] = useState(null); 17 | const [isGenerating, setIsGenerating] = useState(false); 18 | const [error, setError] = useState(null); 19 | const [rawResponse, setRawResponse] = useState(null); 20 | const [sidebarOpen, setSidebarOpen] = useState(false); 21 | const textareaRef = useRef(null); 22 | 23 | // Auto-resize textarea based on content 24 | useEffect(() => { 25 | if (textareaRef.current) { 26 | textareaRef.current.style.height = 'auto'; 27 | textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; 28 | } 29 | }, [prompt]); 30 | 31 | const generateImage = async () => { 32 | if (!prompt.trim()) { 33 | setError('Please enter a prompt'); 34 | return; 35 | } 36 | 37 | setIsGenerating(true); 38 | setError(null); 39 | 40 | try { 41 | const response = await fetch('/api/generate', { 42 | method: 'POST', 43 | headers: { 44 | 'Content-Type': 'application/json', 45 | }, 46 | body: JSON.stringify({ prompt }), 47 | }); 48 | 49 | const data = await response.json(); 50 | 51 | // Store the raw response 52 | setRawResponse(data); 53 | 54 | if (!response.ok) { 55 | throw new Error(data.error || 'Failed to generate image'); 56 | } 57 | 58 | if (data.metadata?.storage?.files?.length > 0) { 59 | setImage(data.metadata.storage.files[0].url); 60 | } else if (data.output && data.output.length > 0) { 61 | setImage(data.output[0]); 62 | } else { 63 | throw new Error('No image generated'); 64 | } 65 | } catch (error: any) { 66 | setError(error.message || 'Something went wrong'); 67 | console.error('Error generating image:', error); 68 | } finally { 69 | setIsGenerating(false); 70 | } 71 | }; 72 | 73 | const useExamplePrompt = (promptText: string) => { 74 | setPrompt(promptText); 75 | if (textareaRef.current) { 76 | textareaRef.current.focus(); 77 | } 78 | }; 79 | 80 | return ( 81 |
82 | {/* Heading with animation */} 83 |
84 |

85 | Skytells Image Generator 86 |

87 |

88 | Transform your ideas into stunning visuals with AI-powered image generation 89 |

90 |
91 | 92 | {/* Input area */} 93 |
94 |