How to generate images using ASI:One and ReactJS

English

From ASI Alliance LATAM Community

In this tutorial, we will use the ASI:One Image Generation API combined with ReactJS to generate images from text prompts.

API Reference

This API, powered by ASI:One, allows us to obtain high-quality images based on text descriptions. To use it in this project, we first need to initialize a ReactJS project using ViteJS.

We will also use TailwindCSS and Shadcn.


Create the project with Vite

Run the following command:

pnpm create vite

Answer the prompts as follows:

  1. Project Name: Your project name

  2. Select a framework: React

  3. Select a variant: TypeScript

  4. Use rolldown-vite (Experimental)?: No

  5. Install with pnpm and start now?: Yes

Then go into your project folder:

cd <your-project-name>


Install TailwindCSS

We will use Tailwind CSS v4. Run:

pnpm add tailwindcss @tailwindcss/vite

In the vite.config.ts file, add the Tailwind plugin for ViteJS:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
})

Delete the ./src/App.css file.

In ./src/index.css, remove all content and keep only:

@import "tailwindcss";

Install Shadcn

To install Shadcn, first edit the tsconfig.json file and add:

"compilerOptions": {
  "baseUrl": ".",
  "paths": {
    "@/*": ["./src/*"]
  }
}

The full file should look like this:

{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Then edit tsconfig.app.json and add:

"baseUrl": ".",
"paths": {
  "@/*": [
    "./src/*"
  ]
}

Your file should look similar to:

{
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "target": "ES2022",
    "useDefineForClassFields": true,
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "types": ["vite/client"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "moduleDetection": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "erasableSyntaxOnly": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,

    "baseUrl": ".",
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
  },
  "include": ["src"]
}

Finally, edit vite.config.ts again:

import path from "path"
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
})

Initialize Shadcn

Run the following command in the terminal:

pnpm dlx shadcn@latest init

  1. Which color would you like to use as the base color?: Neutral

Then add the button component:

pnpm dlx shadcn@latest add button


Create App.tsx

Replace the content of ./src/App.tsx with the following code:

import { useState } from 'react'
import { Button } from './components/ui/button'
import { Loader2, Download, ImageIcon } from 'lucide-react'

interface ImageGenerationResponse {
  status: number
  message: string
  created: number
  images: Array<{
    url: string
  }>
}

function App() {
  const [prompt, setPrompt] = useState('')
  const [size, setSize] = useState('1024x1024')
  const [model, setModel] = useState('asi1-mini')
  const [generatedImage, setGeneratedImage] = useState<string | null>(null)
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [apiKey, setApiKey] = useState('')

  const generateImage = async () => {
    if (!prompt.trim()) {
      setError('Please enter a prompt')
      return
    }

    if (!apiKey.trim()) {
      setError('Please enter your API key')
      return
    }

    setIsLoading(true)
    setError(null)
    setGeneratedImage(null)

    try {
      const response = await fetch('https://api.asi1.ai/v1/image/generate', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${apiKey}`,
        },
        body: JSON.stringify({
          prompt: prompt,
          size: size,
          model: model,
        }),
      })

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }

      const result: ImageGenerationResponse = await response.json()

      if (result.images && result.images.length > 0) {
        const imageUrl = result.images[0].url
        if (imageUrl.startsWith('data:image/')) {
          setGeneratedImage(imageUrl)
        } else {
          throw new Error('Unexpected image URL format')
        }
      } else {
        throw new Error('No images found in response')
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : 'An error occurred')
    } finally {
      setIsLoading(false)
    }
  }

  const downloadImage = () => {
    if (!generatedImage) return

    const base64Data = generatedImage.split(',')[1]
    const imageData = atob(base64Data)
    const arrayBuffer = new ArrayBuffer(imageData.length)
    const uint8Array = new Uint8Array(arrayBuffer)
    
    for (let i = 0; i < imageData.length; i++) {
      uint8Array[i] = imageData.charCodeAt(i)
    }

    const blob = new Blob([uint8Array], { type: 'image/png' })
    const url = URL.createObjectURL(blob)
    
    const link = document.createElement('a')
    link.href = url
    link.download = `generated-image-${Date.now()}.png`
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    URL.revokeObjectURL(url)
  }

  const sizeOptions = [
    { value: '1024x1024', label: '1024×1024 (Large Square)' },
    { value: '768x768', label: '768×768 (Medium Square)' },
    { value: '512x512', label: '512×512 (Small Square)' },
  ]

  const modelOptions = [
    { value: 'asi1-mini', label: 'ASI1 Mini' },
  ]

  return (
    <div className="min-h-screen bg-background p-4">
      <div className="max-w-4xl mx-auto">
        <div className="text-center mb-8">
          <h1 className="text-4xl font-bold text-foreground mb-2">
            ReactJS + ASI:One Image Generation
          </h1>
          <p className="text-muted-foreground">
            Generate high-quality images from text descriptions using AI
          </p>
        </div>

        <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
          <div className="space-y-6">
            <div className="bg-card p-6 rounded-lg border">
              <h2 className="text-xl font-semibold mb-4 text-card-foreground">
                Generate Image
              </h2>

              <div className="space-y-2 mb-4">
                <label htmlFor="apiKey" className="text-sm font-medium text-card-foreground">
                  API Key
                </label>
                <input
                  id="apiKey"
                  type="password"
                  value={apiKey}
                  onChange={(e) => setApiKey(e.target.value)}
                  placeholder="Enter your ASI:One API key"
                  className="w-full px-3 py-2 bg-background border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent"
                />
              </div>

              <div className="space-y-2 mb-4">
                <label htmlFor="prompt" className="text-sm font-medium text-card-foreground">
                  Prompt
                </label>
                <textarea
                  id="prompt"
                  value={prompt}
                  onChange={(e) => setPrompt(e.target.value)}
                  placeholder="A futuristic city skyline at sunset with flying cars"
                  rows={3}
                  className="w-full px-3 py-2 bg-background border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent resize-none"
                />
              </div>

              <div className="space-y-2 mb-4">
                <label htmlFor="size" className="text-sm font-medium text-card-foreground">
                  Image Size
                </label>
                <select
                  id="size"
                  value={size}
                  onChange={(e) => setSize(e.target.value)}
                  className="w-full px-3 py-2 bg-background border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent"
                >
                  {sizeOptions.map((option) => (
                    <option key={option.value} value={option.value}>
                      {option.label}
                    </option>
                  ))}
                </select>
              </div>

              <div className="space-y-2 mb-6">
                <label htmlFor="model" className="text-sm font-medium text-card-foreground">
                  Model
                </label>
                <select
                  id="model"
                  value={model}
                  onChange={(e) => setModel(e.target.value)}
                  className="w-full px-3 py-2 bg-background border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent"
                >
                  {modelOptions.map((option) => (
                    <option key={option.value} value={option.value}>
                      {option.label}
                    </option>
                  ))}
                </select>
              </div>

              <Button
                onClick={generateImage}
                disabled={isLoading}
                className="w-full"
                size="lg"
              >
                {isLoading ? (
                  <>
                    <Loader2 className="w-4 h-4 animate-spin" />
                    Generating...
                  </>
                ) : (
                  <>
                    <ImageIcon className="w-4 h-4" />
                    Generate Image
                  </>
                )}
              </Button>

              {error && (
                <div className="mt-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md">
                  <p className="text-destructive text-sm">{error}</p>
                </div>
              )}
            </div>

            <div className="bg-card p-6 rounded-lg border">
              <h3 className="text-lg font-semibold mb-3 text-card-foreground">
                💡 Tips for Better Results
              </h3>
              <ul className="space-y-2 text-sm text-muted-foreground">
                <li>• Be specific with details about style, mood, and lighting</li>
                <li>• Include descriptive language about colors and textures</li>
                <li>• Specify camera angles and viewpoints</li>
                <li>• Mention artistic styles or reference famous artists</li>
              </ul>
            </div>
          </div>

          <div className="space-y-6">
            <div className="bg-card p-6 rounded-lg border min-h-[400px]">
              <div className="flex justify-between items-center mb-4">
                <h2 className="text-xl font-semibold text-card-foreground">
                  Generated Image
                </h2>
                {generatedImage && (
                  <Button
                    onClick={downloadImage}
                    variant="outline"
                    size="sm"
                  >
                    <Download className="w-4 h-4" />
                    Download
                  </Button>
                )}
              </div>

              <div className="flex items-center justify-center min-h-[350px] bg-muted rounded-lg">
                {isLoading ? (
                  <div className="text-center">
                    <Loader2 className="w-8 h-8 animate-spin mx-auto mb-2 text-muted-foreground" />
                    <p className="text-muted-foreground">Generating your image...</p>
                  </div>
                ) : generatedImage ? (
                  <img
                    src={generatedImage}
                    alt="Generated image"
                    className="max-w-full max-h-full object-contain rounded-lg shadow-lg"
                  />
                ) : (
                  <div className="text-center">
                    <ImageIcon className="w-12 h-12 mx-auto mb-2 text-muted-foreground" />
                    <p className="text-muted-foreground">
                      Your generated image will appear here
                    </p>
                  </div>
                )}
              </div>
            </div>

            <div className="bg-card p-6 rounded-lg border">
              <h3 className="text-lg font-semibold mb-3 text-card-foreground">
                🎨 Example Prompts
              </h3>
              <div className="space-y-2">
                {[
                  "A serene mountain landscape with snow-capped peaks and a crystal clear lake",
                  "A cyberpunk city street at night with neon lights reflecting on wet pavement",
                  "A cozy coffee shop interior with warm lighting and vintage furniture",
                  "A majestic dragon flying over a medieval castle at sunset",
                ].map((examplePrompt, index) => (
                  <button
                    key={index}
                    onClick={() => setPrompt(examplePrompt)}
                    className="text-left w-full p-2 text-sm bg-muted hover:bg-muted/80 rounded border transition-colors"
                  >
                    {examplePrompt}
                  </button>
                ))}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

export default App

What are we doing in this file?

We are creating a form.

On the left side, we have several inputs:

  • API Key: Allows the user to enter their ASI:One API key to use the app. We do not include our own key in the project, because even if we use a .env file, it could still be visible in the browser’s Network tab.

  • Prompt: Where the user types what they want to generate.

  • Image Size: The API allows three sizes: 1024x1024, 768x768, 512x512.

  • Model: Currently, only ASI1 Mini is available.

On the right side, we display:

  • A preview area for the generated image.

  • Buttons with example prompts.

  • A download button once the image is generated.

If you followed the tutorial step by step, you should see something like this:

Conclusion

Done! You’ve learned how to integrate the ASI:One Image Generation API into a modern ReactJS project.

Thank you for following this tutorial :rocket:


Español

Desde ASI Alliance LATAM Community

En este tutorial usaremos la API de generación de imágenes de ASI:One, combinada con ReactJS, para generar imágenes mediante prompts.

API

Esta API, impulsada por ASI:One, nos permite obtener imágenes de alta calidad a partir de descripciones en texto. Para utilizarla en este proyecto, primero debemos inicializar un proyecto de ReactJS usando ViteJS.

También utilizaremos TailwindCSS y Shadcn.


Crear el proyecto con Vite

Ejecuta el siguiente comando:

pnpm create vite

Responde lo siguiente:

  1. Project Name: Nombre de tu proyecto

  2. Select a framework: React

  3. Select a variant: TypeScript

  4. Use rolldown-vite (Experimental)?: No

  5. Install with pnpm and start now?: Yes

Luego ingresa a tu proyecto:

cd <your-project-name>


Instalar TailwindCSS

Usaremos Tailwind CSS v4. Ejecuta:

pnpm add tailwindcss @tailwindcss/vite

En el archivo vite.config.ts, agrega el plugin de Tailwind para ViteJS:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
})

Elimina el archivo ./src/App.css.

En ./src/index.css, elimina todo su contenido y deja únicamente:

@import "tailwindcss";

Instalar Shadcn

Para instalar Shadcn, primero edita el archivo tsconfig.json y agrega:

"compilerOptions": {
  "baseUrl": ".",
  "paths": {
    "@/*": ["./src/*"]
  }
}

El archivo completo debe verse así:

{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Luego edita tsconfig.app.json agregando:

"baseUrl": ".",
"paths": {
  "@/*": [
    "./src/*"
  ]
}

Tu archivo debería verse similar a:

{
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "target": "ES2022",
    "useDefineForClassFields": true,
    "lib": ["ES2022", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "types": ["vite/client"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "moduleDetection": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "erasableSyntaxOnly": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,

    "baseUrl": ".",
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
  },
  "include": ["src"]
}

Por último, vuelve a editar vite.config.ts:

import path from "path"
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
})

Inicializar Shadcn

Ejecuta el siguiente comando en la terminal:

pnpm dlx shadcn@latest init

  1. Which color would you like to use as the base color?: Neutral

Luego agrega el componente button:

pnpm dlx shadcn@latest add button


Crear página App.tsx

Reemplaza el contenido de ./src/App.tsx con el siguiente código:

import { useState } from 'react'
import { Button } from './components/ui/button'
import { Loader2, Download, ImageIcon } from 'lucide-react'

interface ImageGenerationResponse {
  status: number
  message: string
  created: number
  images: Array<{
    url: string
  }>
}

function App() {
  const [prompt, setPrompt] = useState('')
  const [size, setSize] = useState('1024x1024')
  const [model, setModel] = useState('asi1-mini')
  const [generatedImage, setGeneratedImage] = useState<string | null>(null)
  const [isLoading, setIsLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [apiKey, setApiKey] = useState('')

  const generateImage = async () => {
    if (!prompt.trim()) {
      setError('Please enter a prompt')
      return
    }

    if (!apiKey.trim()) {
      setError('Please enter your API key')
      return
    }

    setIsLoading(true)
    setError(null)
    setGeneratedImage(null)

    try {
      const response = await fetch('https://api.asi1.ai/v1/image/generate', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${apiKey}`,
        },
        body: JSON.stringify({
          prompt: prompt,
          size: size,
          model: model,
        }),
      })

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }

      const result: ImageGenerationResponse = await response.json()

      if (result.images && result.images.length > 0) {
        const imageUrl = result.images[0].url
        if (imageUrl.startsWith('data:image/')) {
          setGeneratedImage(imageUrl)
        } else {
          throw new Error('Unexpected image URL format')
        }
      } else {
        throw new Error('No images found in response')
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : 'An error occurred')
    } finally {
      setIsLoading(false)
    }
  }

  const downloadImage = () => {
    if (!generatedImage) return

    const base64Data = generatedImage.split(',')[1]
    const imageData = atob(base64Data)
    const arrayBuffer = new ArrayBuffer(imageData.length)
    const uint8Array = new Uint8Array(arrayBuffer)
    
    for (let i = 0; i < imageData.length; i++) {
      uint8Array[i] = imageData.charCodeAt(i)
    }

    const blob = new Blob([uint8Array], { type: 'image/png' })
    const url = URL.createObjectURL(blob)
    
    const link = document.createElement('a')
    link.href = url
    link.download = `generated-image-${Date.now()}.png`
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    URL.revokeObjectURL(url)
  }

  const sizeOptions = [
    { value: '1024x1024', label: '1024×1024 (Large Square)' },
    { value: '768x768', label: '768×768 (Medium Square)' },
    { value: '512x512', label: '512×512 (Small Square)' },
  ]

  const modelOptions = [
    { value: 'asi1-mini', label: 'ASI1 Mini' },
  ]

  return (
    <div className="min-h-screen bg-background p-4">
      <div className="max-w-4xl mx-auto">
        <div className="text-center mb-8">
          <h1 className="text-4xl font-bold text-foreground mb-2">
            ReactJS + ASI:One Image Generation
          </h1>
          <p className="text-muted-foreground">
            Generate high-quality images from text descriptions using AI
          </p>
        </div>

        <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
          <div className="space-y-6">
            <div className="bg-card p-6 rounded-lg border">
              <h2 className="text-xl font-semibold mb-4 text-card-foreground">
                Generate Image
              </h2>

              <div className="space-y-2 mb-4">
                <label htmlFor="apiKey" className="text-sm font-medium text-card-foreground">
                  API Key
                </label>
                <input
                  id="apiKey"
                  type="password"
                  value={apiKey}
                  onChange={(e) => setApiKey(e.target.value)}
                  placeholder="Enter your ASI:One API key"
                  className="w-full px-3 py-2 bg-background border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent"
                />
              </div>

              <div className="space-y-2 mb-4">
                <label htmlFor="prompt" className="text-sm font-medium text-card-foreground">
                  Prompt
                </label>
                <textarea
                  id="prompt"
                  value={prompt}
                  onChange={(e) => setPrompt(e.target.value)}
                  placeholder="A futuristic city skyline at sunset with flying cars"
                  rows={3}
                  className="w-full px-3 py-2 bg-background border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent resize-none"
                />
              </div>

              <div className="space-y-2 mb-4">
                <label htmlFor="size" className="text-sm font-medium text-card-foreground">
                  Image Size
                </label>
                <select
                  id="size"
                  value={size}
                  onChange={(e) => setSize(e.target.value)}
                  className="w-full px-3 py-2 bg-background border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent"
                >
                  {sizeOptions.map((option) => (
                    <option key={option.value} value={option.value}>
                      {option.label}
                    </option>
                  ))}
                </select>
              </div>

              <div className="space-y-2 mb-6">
                <label htmlFor="model" className="text-sm font-medium text-card-foreground">
                  Model
                </label>
                <select
                  id="model"
                  value={model}
                  onChange={(e) => setModel(e.target.value)}
                  className="w-full px-3 py-2 bg-background border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent"
                >
                  {modelOptions.map((option) => (
                    <option key={option.value} value={option.value}>
                      {option.label}
                    </option>
                  ))}
                </select>
              </div>

              <Button
                onClick={generateImage}
                disabled={isLoading}
                className="w-full"
                size="lg"
              >
                {isLoading ? (
                  <>
                    <Loader2 className="w-4 h-4 animate-spin" />
                    Generating...
                  </>
                ) : (
                  <>
                    <ImageIcon className="w-4 h-4" />
                    Generate Image
                  </>
                )}
              </Button>

              {error && (
                <div className="mt-4 p-3 bg-destructive/10 border border-destructive/20 rounded-md">
                  <p className="text-destructive text-sm">{error}</p>
                </div>
              )}
            </div>

            <div className="bg-card p-6 rounded-lg border">
              <h3 className="text-lg font-semibold mb-3 text-card-foreground">
                💡 Tips for Better Results
              </h3>
              <ul className="space-y-2 text-sm text-muted-foreground">
                <li>• Be specific with details about style, mood, and lighting</li>
                <li>• Include descriptive language about colors and textures</li>
                <li>• Specify camera angles and viewpoints</li>
                <li>• Mention artistic styles or reference famous artists</li>
              </ul>
            </div>
          </div>

          <div className="space-y-6">
            <div className="bg-card p-6 rounded-lg border min-h-[400px]">
              <div className="flex justify-between items-center mb-4">
                <h2 className="text-xl font-semibold text-card-foreground">
                  Generated Image
                </h2>
                {generatedImage && (
                  <Button
                    onClick={downloadImage}
                    variant="outline"
                    size="sm"
                  >
                    <Download className="w-4 h-4" />
                    Download
                  </Button>
                )}
              </div>

              <div className="flex items-center justify-center min-h-[350px] bg-muted rounded-lg">
                {isLoading ? (
                  <div className="text-center">
                    <Loader2 className="w-8 h-8 animate-spin mx-auto mb-2 text-muted-foreground" />
                    <p className="text-muted-foreground">Generating your image...</p>
                  </div>
                ) : generatedImage ? (
                  <img
                    src={generatedImage}
                    alt="Generated image"
                    className="max-w-full max-h-full object-contain rounded-lg shadow-lg"
                  />
                ) : (
                  <div className="text-center">
                    <ImageIcon className="w-12 h-12 mx-auto mb-2 text-muted-foreground" />
                    <p className="text-muted-foreground">
                      Your generated image will appear here
                    </p>
                  </div>
                )}
              </div>
            </div>

            <div className="bg-card p-6 rounded-lg border">
              <h3 className="text-lg font-semibold mb-3 text-card-foreground">
                🎨 Example Prompts
              </h3>
              <div className="space-y-2">
                {[
                  "A serene mountain landscape with snow-capped peaks and a crystal clear lake",
                  "A cyberpunk city street at night with neon lights reflecting on wet pavement",
                  "A cozy coffee shop interior with warm lighting and vintage furniture",
                  "A majestic dragon flying over a medieval castle at sunset",
                ].map((examplePrompt, index) => (
                  <button
                    key={index}
                    onClick={() => setPrompt(examplePrompt)}
                    className="text-left w-full p-2 text-sm bg-muted hover:bg-muted/80 rounded border transition-colors"
                  >
                    {examplePrompt}
                  </button>
                ))}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

export default App

¿Qué estamos haciendo en este archivo?

Estamos creando un formulario.

En el lado izquierdo, tenemos varios inputs:

  • API Key: Para que el usuario introduzca su clave de ASI:One y pueda usar la app. No la incluimos en el proyecto, ya que aunque usemos un archivo .env, podría verse en la pestaña Network del navegador.

  • Prompt: Donde el usuario escribe lo que desea generar.

  • Image Size: La API permite tres tamaños: 1024x1024, 768x768, 512x512.

  • Model: Actualmente solo está disponible ASI1 Mini.

En el lado derecho, mostramos:

  • Un cuadro para previsualizar la imagen generada.

  • Botones con ejemplos de prompts.

  • Un botón para descargar la imagen generada.

Si has seguido el tutorial paso a paso, deberías ver algo como esto:

Conclusión

¡Listo! Has aprendido a integrar la API de ASI:One Image Generation en un proyecto moderno de ReactJS.

Gracias por seguir este tutorial :rocket: