Files
AI-comic-generator/backend/app/services/ai_service.py
T
2026-01-28 21:08:37 +08:00

115 lines
4.9 KiB
Python

import os
import time
import logging
from typing import List, Optional
from sqlmodel import Session
from app.models.models import ModelConfig
from google import genai
from google.genai import types
from PIL import Image
import io
logger = logging.getLogger(__name__)
class AIService:
def __init__(self, session: Session):
self.session = session
def _get_client(self, model_type: str):
from app.cruds.crud_config import get_active_config
config = get_active_config(self.session, model_type)
if not config:
raise ValueError(f"No active configuration found for {model_type} model.")
if config.provider.lower() == "google":
# Initialize Google Client
return genai.Client(api_key=config.api_key), config.model_name
raise NotImplementedError(f"Provider {config.provider} not supported yet.")
def generate_storyboard(self, system_prompt: str, user_input: str) -> str:
client, model_name = self._get_client("text")
full_prompt = f"{system_prompt}\n\nUser Input: {user_input}\n\nPlease generate the full storyboard in JSON format as requested."
max_retries = 3
for attempt in range(max_retries):
try:
response = client.models.generate_content(
model=model_name,
contents=full_prompt
)
return response.text
except Exception as e:
logger.error(f"Error generating storyboard (Attempt {attempt + 1}/{max_retries}): {e}")
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
continue
raise e
def generate_image(self, prompt: str, context_images: List[str] = None, aspect_ratio: str = "16:9", resolution: str = "2K") -> bytes:
client, model_name = self._get_client("image")
contents = [prompt]
if context_images:
for img_path in context_images:
if os.path.exists(img_path):
try:
prev_img = Image.open(img_path)
contents.append(prev_img)
except Exception as e:
logger.warning(f"Failed to load context image {img_path}: {e}")
else:
# Log missing context image but don't fail, just skip it
logger.warning(f"Warning: Context image not found at {img_path}, skipping.")
# Retry loop
max_retries = 3
for attempt in range(max_retries):
try:
logger.info(f"DEBUG: Starting image generation attempt {attempt + 1}/{max_retries} with model {model_name}...")
logger.info(f"DEBUG: Prompt length: {len(prompt)}")
if context_images:
logger.info(f"DEBUG: Context images count: {len(context_images)}")
response = client.models.generate_content(
model=model_name,
contents=contents,
config=types.GenerateContentConfig(
image_config=types.ImageConfig(
aspect_ratio=aspect_ratio,
image_size=resolution
),
)
)
logger.info(f"DEBUG: Generation API call completed for attempt {attempt + 1}")
if response.parts:
for part in response.parts:
if part.inline_data is not None:
image_data = part.inline_data.data
if len(image_data) > 0:
logger.info(f"DEBUG: Successfully received image data ({len(image_data)} bytes)")
return image_data
else:
logger.warning(f"Warning: Received empty image data on attempt {attempt + 1}")
# Check for text refusal/error
if response.text:
logger.warning(f"Model response text (no image): {response.text}")
logger.warning(f"Attempt {attempt + 1} failed: No valid image data found in response.")
if attempt == max_retries - 1:
raise ValueError(f"No image found in response after {max_retries} retries. Last response: {response.text if response.text else 'Empty'}")
time.sleep(2 ** attempt)
except Exception as e:
logger.error(f"Error generating image (Attempt {attempt + 1}/{max_retries}): {e}")
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
continue
raise e
return b""