Implemented complete text-to-image functionality across all AI providers: Backend additions: - Added text_to_image() method to AIProvider abstract class - Implemented for all providers: * OpenAI: DALL-E generations API * Stability AI: SDXL text-to-image with negative prompts * Replicate: SDXL with full parameter control * Mock: Placeholder image generation for testing New API endpoints (/generate): - POST /generate/text-to-image * Generate image from prompt * Optional: create new project automatically * Configurable width/height (256-2048px) * Negative prompt support * Provider and model selection - POST /generate/layer/text-to-image * Generate image as layer in existing project * Smaller dimensions for layer composition * Position control (x, y coordinates) * Saves to project layers directory Features: - Full provider support (OpenAI, Stability, Replicate, Mock) - Negative prompts for better control - Auto-project creation option - Layer-based generation for compositing - Dimension validation (256-2048px range) - Model selection per request Use cases: - Create new images from scratch - Generate elements to add as layers - Quick ideation and iteration - Base image creation for further editing Next: Advanced canvas UI with layers and real-time preview
177 lines
5.4 KiB
Python
177 lines
5.4 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Form
|
|
from sqlalchemy.orm import Session
|
|
from typing import Optional
|
|
from PIL import Image
|
|
from io import BytesIO
|
|
import os
|
|
|
|
from app.database import get_db
|
|
from app.models.project import Project
|
|
from app.schemas import TextToImageRequest, TextToImageResponse
|
|
from app.services.ai_provider import get_ai_provider
|
|
from app.services.edit_service import EditService
|
|
from app.config import settings
|
|
|
|
router = APIRouter(prefix="/generate", tags=["generate"])
|
|
|
|
|
|
@router.post("/text-to-image", response_model=TextToImageResponse)
|
|
async def text_to_image(
|
|
prompt: str = Form(...),
|
|
width: int = Form(1024),
|
|
height: int = Form(1024),
|
|
negative_prompt: Optional[str] = Form(None),
|
|
ai_provider: Optional[str] = Form(None),
|
|
ai_model: Optional[str] = Form(None),
|
|
create_project: bool = Form(True),
|
|
project_name: Optional[str] = Form(None),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Generate an image from text prompt
|
|
|
|
Args:
|
|
prompt: Text description of desired image
|
|
width: Image width (default 1024)
|
|
height: Image height (default 1024)
|
|
negative_prompt: What to avoid in generation
|
|
ai_provider: Override default AI provider
|
|
ai_model: Specific model to use
|
|
create_project: Whether to create a new project with the result
|
|
project_name: Name for the new project (if create_project=True)
|
|
|
|
Returns:
|
|
Generated image info and optionally project details
|
|
"""
|
|
|
|
# Validate dimensions
|
|
if width < 256 or width > 2048 or height < 256 or height > 2048:
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail="Width and height must be between 256 and 2048"
|
|
)
|
|
|
|
# Get AI provider
|
|
provider = get_ai_provider(ai_provider, ai_model)
|
|
|
|
try:
|
|
# Generate image
|
|
image_bytes = await provider.text_to_image(
|
|
prompt=prompt,
|
|
width=width,
|
|
height=height,
|
|
model=ai_model,
|
|
negative_prompt=negative_prompt
|
|
)
|
|
|
|
project_id = None
|
|
image_url = None
|
|
|
|
if create_project:
|
|
# Create a new project
|
|
project = Project(
|
|
name=project_name or f"Generated: {prompt[:50]}",
|
|
user_id=None # TODO: Add authentication
|
|
)
|
|
db.add(project)
|
|
db.commit()
|
|
db.refresh(project)
|
|
|
|
project_id = project.id
|
|
|
|
# Save image as both original and current
|
|
edit_service = EditService()
|
|
edit_service.ensure_project_dir(project_id)
|
|
|
|
original_path = edit_service.get_original_image_path(project_id)
|
|
current_path = edit_service.get_current_image_path(project_id)
|
|
|
|
# Save image
|
|
img = Image.open(BytesIO(image_bytes))
|
|
img.save(original_path, 'PNG')
|
|
img.save(current_path, 'PNG')
|
|
|
|
image_url = f"/projects/{project_id}/current"
|
|
|
|
return TextToImageResponse(
|
|
status="success",
|
|
prompt=prompt,
|
|
width=width,
|
|
height=height,
|
|
project_id=project_id,
|
|
image_url=image_url,
|
|
ai_provider=ai_provider or settings.ai_provider,
|
|
ai_model=ai_model
|
|
)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.post("/layer/text-to-image", response_model=TextToImageResponse)
|
|
async def text_to_image_layer(
|
|
project_id: int = Form(...),
|
|
prompt: str = Form(...),
|
|
width: int = Form(512),
|
|
height: int = Form(512),
|
|
x: int = Form(0),
|
|
y: int = Form(0),
|
|
negative_prompt: Optional[str] = Form(None),
|
|
ai_provider: Optional[str] = Form(None),
|
|
ai_model: Optional[str] = Form(None),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Generate an image as a new layer in an existing project
|
|
|
|
This generates a smaller image that can be placed as a layer
|
|
on top of the current project canvas.
|
|
"""
|
|
|
|
# Verify project exists
|
|
project = db.query(Project).filter(Project.id == project_id).first()
|
|
if not project:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
|
|
# Get AI provider
|
|
provider = get_ai_provider(ai_provider, ai_model)
|
|
|
|
try:
|
|
# Generate image
|
|
image_bytes = await provider.text_to_image(
|
|
prompt=prompt,
|
|
width=width,
|
|
height=height,
|
|
model=ai_model,
|
|
negative_prompt=negative_prompt
|
|
)
|
|
|
|
# Save as temporary layer file
|
|
edit_service = EditService()
|
|
layers_dir = edit_service.get_project_dir(project_id) / "layers"
|
|
layers_dir.mkdir(exist_ok=True)
|
|
|
|
# Generate unique layer filename
|
|
import time
|
|
layer_filename = f"generated_{int(time.time())}.png"
|
|
layer_path = layers_dir / layer_filename
|
|
|
|
# Save layer image
|
|
with open(layer_path, 'wb') as f:
|
|
f.write(image_bytes)
|
|
|
|
return TextToImageResponse(
|
|
status="success",
|
|
prompt=prompt,
|
|
width=width,
|
|
height=height,
|
|
project_id=project_id,
|
|
image_url=f"/projects/{project_id}/layers/{layer_filename}",
|
|
layer_position={"x": x, "y": y},
|
|
ai_provider=ai_provider or settings.ai_provider,
|
|
ai_model=ai_model
|
|
)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|