├── assets
└── banner.jpeg
├── src
└── app
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ ├── api
│ └── generate
│ │ └── route.ts
│ ├── page.tsx
│ └── components
│ ├── AnimatedBackground.tsx
│ └── ImageGenerator.tsx
├── postcss.config.mjs
├── public
├── vercel.svg
├── window.svg
├── file.svg
├── globe.svg
└── next.svg
├── next.config.ts
├── eslint.config.mjs
├── .gitignore
├── tsconfig.json
├── package.json
├── LICENSE
└── README.md
/assets/banner.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skytells-ai/image-generator-saas-nextjs/HEAD/assets/banner.jpeg
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skytells-ai/image-generator-saas-nextjs/HEAD/src/app/favicon.ico
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | const config = {
2 | plugins: ["@tailwindcss/postcss"],
3 | };
4 |
5 | export default config;
6 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | :root {
4 | --background: #ffffff;
5 | --foreground: #171717;
6 | }
7 |
8 | @theme inline {
9 | --color-background: var(--background);
10 | --color-foreground: var(--foreground);
11 | --font-sans: var(--font-geist-sans);
12 | --font-mono: var(--font-geist-mono);
13 | }
14 |
15 | @media (prefers-color-scheme: dark) {
16 | :root {
17 | --background: #0a0a0a;
18 | --foreground: #ededed;
19 | }
20 | }
21 |
22 | body {
23 | background: var(--background);
24 | color: var(--foreground);
25 | font-family: Arial, Helvetica, sans-serif;
26 | }
27 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Geist, Geist_Mono } from "next/font/google";
3 | import "./globals.css";
4 |
5 | const geistSans = Geist({
6 | variable: "--font-geist-sans",
7 | subsets: ["latin"],
8 | });
9 |
10 | const geistMono = Geist_Mono({
11 | variable: "--font-geist-mono",
12 | subsets: ["latin"],
13 | });
14 |
15 | export const metadata: Metadata = {
16 | title: "Create Next App",
17 | description: "Generated by create next app",
18 | };
19 |
20 | export default function RootLayout({
21 | children,
22 | }: Readonly<{
23 | children: React.ReactNode;
24 | }>) {
25 | return (
26 |
27 |
30 | {children}
31 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/public/next.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/page.tsx:
--------------------------------------------------------------------------------
1 | import ImageGenerator from "./components/ImageGenerator";
2 | import AnimatedBackground from "./components/AnimatedBackground";
3 |
4 | export default function Home() {
5 | return (
6 |
7 | {/* Animated background */}
8 |
9 |
10 | {/* Header */}
11 |
12 |
13 |
14 |
15 | S
16 |
17 |
Skytells AI Image Generator
18 |
19 |
20 |
42 |
43 |
44 |
45 |
46 | {/* Main content */}
47 |
48 |
49 |
50 |
51 | {/* Footer */}
52 |
66 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | 
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 |
--------------------------------------------------------------------------------
/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 |
124 |
125 | {/* Example prompts as capsules - only show when not generating and no image exists */}
126 | {!isGenerating && !image && (
127 |
128 |
Try these examples:
129 |
130 | {EXAMPLE_PROMPTS.map((example, index) => (
131 |
useExamplePrompt(example.text)}
134 | className="inline-flex items-center px-3 py-1 text-xs rounded-full transition-all duration-300 hover:scale-105 relative"
135 | >
136 | {/* Always visible neon border */}
137 |
138 |
139 | {example.text}
140 |
141 |
142 | ))}
143 |
144 |
145 | )}
146 |
147 | {/* Error message */}
148 | {error && (
149 |
150 | {error}
151 |
152 | )}
153 |
154 | {/* Image result area with animation */}
155 |
158 | {isGenerating ? (
159 |
160 |
161 | {/* Animated particles */}
162 |
163 | {[...Array(5)].map((_, i) => (
164 |
174 | ))}
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | Creating your masterpiece...
183 |
184 |
185 | ) : image ? (
186 |
187 |
194 | {/* Response data toggle button */}
195 | {rawResponse && (
196 |
setSidebarOpen(!sidebarOpen)}
198 | className="absolute bottom-4 right-4 bg-black/70 hover:bg-black/90 text-white p-2 rounded-full transition-all duration-300"
199 | title={sidebarOpen ? "Hide response data" : "Show response data"}
200 | >
201 |
202 |
203 |
204 |
205 | )}
206 |
207 | ) : null}
208 |
209 |
210 | {/* Response sidebar */}
211 |
214 |
215 |
Response Data
216 |
setSidebarOpen(false)}
218 | className="p-1 rounded-full hover:bg-gray-800"
219 | >
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 | {rawResponse ? JSON.stringify(rawResponse, null, 2) : 'No data available'}
228 |
229 |
230 |
231 |
232 | {/* CTA for API key */}
233 |
250 |
251 | {/* Custom animation styles */}
252 |
328 |
329 | );
330 | }
--------------------------------------------------------------------------------