'use client'; import { useState, useEffect } from 'react'; import { Header } from '@/components/header'; import { MainLayout } from '@/components/main-layout'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { PromptTextarea } from '@/components/prompt-textarea'; import { Label } from '@/components/ui/label'; import { Card, CardContent } from '@/components/ui/card'; import { apiClient, type GenerationRequest, type JobInfo, type ModelInfo } from '@/lib/api'; import { Loader2, Download, X } from 'lucide-react'; import { downloadImage } from '@/lib/utils'; export default function Text2ImgPage() { const [formData, setFormData] = useState({ prompt: '', negative_prompt: '', width: 512, height: 512, steps: 20, cfg_scale: 7.5, seed: '', sampling_method: 'euler_a', scheduler: 'default', batch_count: 1, }); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [jobInfo, setJobInfo] = useState(null); const [generatedImages, setGeneratedImages] = useState([]); const [samplers, setSamplers] = useState>([]); const [schedulers, setSchedulers] = useState>([]); const [vaeModels, setVaeModels] = useState([]); const [selectedVae, setSelectedVae] = useState(''); const [loraModels, setLoraModels] = useState([]); const [embeddings, setEmbeddings] = useState([]); useEffect(() => { const loadOptions = async () => { try { const [samplersData, schedulersData, models, loras, embeds] = await Promise.all([ apiClient.getSamplers(), apiClient.getSchedulers(), apiClient.getModels('vae'), apiClient.getModels('lora'), apiClient.getModels('embedding'), ]); setSamplers(samplersData); setSchedulers(schedulersData); setVaeModels(models); setLoraModels(loras.map(m => m.name)); setEmbeddings(embeds.map(m => m.name)); } catch (err) { console.error('Failed to load options:', err); } }; loadOptions(); }, []); const handleInputChange = ( e: React.ChangeEvent ) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: name === 'prompt' || name === 'negative_prompt' || name === 'seed' || name === 'sampling_method' ? value : Number(value), })); }; const pollJobStatus = async (jobId: string) => { const maxAttempts = 300; // 5 minutes with 1 second interval let attempts = 0; const poll = async () => { try { const status = await apiClient.getJobStatus(jobId); setJobInfo(status); if (status.status === 'completed' && status.result?.images) { setGeneratedImages(status.result.images); setLoading(false); } else if (status.status === 'failed') { setError(status.error || 'Generation failed'); setLoading(false); } else if (status.status === 'cancelled') { setError('Generation was cancelled'); setLoading(false); } else if (attempts < maxAttempts) { attempts++; setTimeout(poll, 1000); } else { setError('Job polling timeout'); setLoading(false); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to check job status'); setLoading(false); } }; poll(); }; const handleGenerate = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setError(null); setGeneratedImages([]); setJobInfo(null); try { const job = await apiClient.text2img(formData); setJobInfo(job); const jobId = job.request_id || job.id; if (jobId) { await pollJobStatus(jobId); } else { setError('No job ID returned from server'); setLoading(false); } } catch (err) { setError(err instanceof Error ? err.message : 'Failed to generate image'); setLoading(false); } }; const handleCancel = async () => { const jobId = jobInfo?.request_id || jobInfo?.id; if (jobId) { try { await apiClient.cancelJob(jobId); setLoading(false); setError('Generation cancelled'); } catch (err) { console.error('Failed to cancel job:', err); } } }; return (
{/* Left Panel - Form */}
setFormData({ ...formData, prompt: value })} placeholder="a beautiful landscape with mountains and a lake, sunset, highly detailed..." rows={4} loras={loraModels} embeddings={embeddings} />

Tip: Use <lora:name:weight> for LoRAs (e.g., <lora:myLora:0.8>) and embedding names directly

setFormData({ ...formData, negative_prompt: value })} placeholder="blurry, low quality, distorted..." rows={2} />
{loading && ( )}
{error && (
{error}
)} {jobInfo && (

Job ID: {jobInfo.id}

Status: {jobInfo.status}

{jobInfo.progress !== undefined && (

Progress: {Math.round(jobInfo.progress * 100)}%

)}
)}
{/* Right Panel - Generated Images */}

Generated Images

{generatedImages.length === 0 ? (

{loading ? 'Generating...' : 'Generated images will appear here'}

) : (
{generatedImages.map((image, index) => (
{`Generated
))}
)}
); }