實驗二:AI插圖生成平台

核心代碼

1. 圖像生成服務 (services/image_service.py)

import os
import base64
from datetime import datetime
from dotenv import load_dotenv
from utils.aliyun_client import AliyunClient
import requests
import json

# 加載環境變量
load_dotenv()

def generate_image(prompt, style=None, size=None):
    """
    使用阿里雲百鍊平台的圖像生成服務創建故事插圖
    
    Args:
        prompt: 圖像描述提示詞
        style: 圖像風格
        size: 圖像尺寸
    
    Returns:
        dict: 包含圖像信息的字典
    """
    # 確保uploads目錄存在
    uploads_dir = os.path.abspath(os.getenv('UPLOAD_FOLDER', './uploads'))
    os.makedirs(uploads_dir, exist_ok=True)
    
    # 構建優化後的提示詞
    optimized_prompt = _optimize_prompt_for_children_illustration(prompt, style)
    
    print(f"🎨 原始提示詞: {prompt}")
    print(f"🎨 優化後提示詞: {optimized_prompt}")
    
    # 創建阿里雲客户端
    client = AliyunClient()
    
    try:
        # 設置圖像參數
        image_params = {
            "model": "wan2.5-t2i-preview",
            "n": 1,
            "size": size or "1024x1024"
        }
        
        # 調用圖像生成API
        print("🔄 調用阿里雲圖像生成API...")
        result = client.generate_image(optimized_prompt, **image_params)
        
        print(f"✅ 圖像生成API返回結果: {result}")
        
        # 處理API響應,嘗試多種可能的數據結構
        image_data = None
        
        # 檢查不同的響應結構
        if 'results' in result:
            if isinstance(result['results'], list) and len(result['results']) > 0:
                # 檢查 results.data 或 results.url 或 results.output
                for item in result['results']:
                    if 'data' in item and item['data']:
                        # 處理base64編碼的圖像數據
                        image_data = item['data']
                        break
                    elif 'url' in item and item['url']:
                        # 處理URL形式的圖像數據
                        try:
                            response = requests.get(item['url'], timeout=10)
                            if response.status_code == 200:
                                image_data = base64.b64encode(response.content).decode('utf-8')
                                break
                        except Exception as e:
                            print(f"⚠️ 下載圖像失敗: {e}")
                    elif 'output' in item and 'images' in item['output'] and item['output']['images']:
                        # 處理output.images形式的圖像數據
                        image_data = item['output']['images'][0]
                        break
        elif 'output' in result and 'images' in result['output']:
            # 直接從output.images獲取
            if result['output']['images']:
                image_data = result['output']['images'][0]
        elif 'data' in result and result['data']:
            # 直接從data字段獲取
            image_data = result['data']
        elif 'url' in result and result['url']:
            # 直接從url字段獲取
            try:
                response = requests.get(result['url'], timeout=10)
                if response.status_code == 200:
                    image_data = base64.b64encode(response.content).decode('utf-8')
            except Exception as e:
                print(f"⚠️ 下載圖像失敗: {e}")
        
        # 如果沒有找到有效的圖像數據,使用備用方案
        if not image_data:
            print("❌ 未找到有效的圖像數據,使用備用方案")
            image_info = _generate_fallback_image(prompt, uploads_dir)
            return image_info
        
        # 確保image_data是base64編碼的
        if not image_data.startswith('data:image') and not image_data.startswith('iVBORw0KGgo'):
            # 如果不是標準的data URL,可能需要添加前綴
            if 'http' in image_data:
                # 如果是URL,嘗試下載
                try:
                    response = requests.get(image_data, timeout=10)
                    if response.status_code == 200:
                        image_data = base64.b64encode(response.content).decode('utf-8')
                except Exception as e:
                    print(f"⚠️ 下載圖像失敗: {e}")
                    image_info = _generate_fallback_image(prompt, uploads_dir)
                    return image_info
            else:
                # 如果不是URL,嘗試直接使用base64數據
                pass
        
        # 清理base64數據
        if image_data.startswith('data:image'):
            # 移除data:image前綴
            image_data = image_data.split(',')[1]
        
        try:
            # 解碼base64數據
            image_bytes = base64.b64decode(image_data)
            
            # 生成文件名
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            image_filename = f"story_illustration_{timestamp}.png"
            image_path = os.path.join(uploads_dir, image_filename)
            
            # 保存圖像文件
            with open(image_path, 'wb') as f:
                f.write(image_bytes)
            
            print(f"✅ 圖像保存成功: {image_path}")
            
            # 返回圖像信息
            return {
                'filename': image_filename,
                'path': image_path,
                'url': f"/uploads/{image_filename}",
                'prompt': prompt,
                'style': style,
                'size': size or "1024x1024"
            }
            
        except Exception as e:
            print(f"❌ 圖像處理失敗: {e}")
            # 使用備用方案
            image_info = _generate_fallback_image(prompt, uploads_dir)
            return image_info
            
    except Exception as e:
        error_msg = str(e)
        print(f"❌ 圖像生成異常: {error_msg}")
        
        # 特殊處理API頻率限制錯誤
        if "429" in error_msg or "Too Many Requests" in error_msg:
            raise Exception("圖像生成服務暫時繁忙,請稍後再試。建議等待1-2分鐘後再進行圖像生成。")
        elif "400" in error_msg and "InvalidParameter" in error_msg:
            raise Exception("圖像生成參數錯誤,請檢查提示詞內容。")
        else:
            # 使用備用方案
            image_info = _generate_fallback_image(prompt, uploads_dir)
            return image_info

def _optimize_prompt_for_children_illustration(prompt, style=None):
    """
    優化提示詞以生成適合兒童的插圖
    
    Args:
        prompt: 原始提示詞
        style: 圖像風格
    
    Returns:
        str: 優化後的提示詞
    """
    # 基礎提示詞模板
    base_prompt = "兒童插畫,風格可愛,明亮温暖的色彩,清晰的線條,卡通風格,適合兒童繪本,高清細節,"
    
    # 風格映射
    style_map = {
        'cartoon': "迪士尼卡通風格,色彩鮮豔,角色圓潤可愛",
        'watercolor': "水彩畫風格,柔和的色彩過渡,輕盈透明",
        'flat': "扁平化設計,簡約乾淨,明亮色調",
        'anime': "日式動漫風格,大眼睛角色,明亮色彩",
        'sketch': "素描風格,線條清晰,黑白為主"
    }
    
    # 添加風格描述
    if style and style in style_map:
        base_prompt += style_map[style] + ", "
    
    # 添加用户提示詞
    base_prompt += prompt
    
    # 添加額外要求
    base_prompt += ", 適合3-12歲兒童,無恐怖元素,積極向上,温馨友好"
    
    return base_prompt

def _generate_fallback_image(prompt, uploads_dir):
    """
    生成備用圖像(當AI圖像生成失敗時使用)
    
    Args:
        prompt: 原始提示詞
        uploads_dir: 上傳目錄
    
    Returns:
        dict: 包含圖像信息的字典
    """
    print("🔄 生成備用圖像...")
    
    # 生成文件名
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    image_filename = f"fallback_image_{timestamp}.svg"
    image_path = os.path.join(uploads_dir, image_filename)
    
    # 創建簡單的SVG圖像
    # 提取主題關鍵詞用於圖像描述
    theme_summary = prompt[:30] + "..." if len(prompt) > 30 else prompt
    
    svg_content = f'''
    <svg width="500" height="500" xmlns="http://www.w3.org/2000/svg">
      <!-- 背景 -->
      <rect width="500" height="500" fill="#e6f7ff"/>
      
      <!-- 中心圓 -->
      <circle cx="250" cy="250" r="200" fill="#f0f9ff" stroke="#91d5ff" stroke-width="4"/>
      
      <!-- 圖標 -->
      <text x="250" y="200" font-family="Arial, sans-serif" font-size="80" text-anchor="middle" fill="#69c0ff">📚</text>
      
      <!-- 文本 -->
      <text x="250" y="300" font-family="Arial, sans-serif" font-size="20" text-anchor="middle" fill="#1890ff">故事插圖</text>
      <text x="250" y="340" font-family="Arial, sans-serif" font-size="14" text-anchor="middle" fill="#40a9ff">{theme_summary}</text>
      <text x="250" y="380" font-family="Arial, sans-serif" font-size="12" text-anchor="middle" fill="#91d5ff">AI圖像生成暫不可用</text>
      
      <!-- 裝飾元素 -->
      <circle cx="100" cy="100" r="20" fill="#fff2e8" stroke="#ffbb96" stroke-width="2"/>
      <circle cx="400" cy="100" r="15" fill="#f6ffed" stroke="#b7eb8f" stroke-width="2"/>
      <circle cx="100" cy="400" r="18" fill="#fff1f0" stroke="#ffadd2" stroke-width="2"/>
      <circle cx="400" cy="400" r="12" fill="#f0f5ff" stroke="#adc6ff" stroke-width="2"/>
    </svg>
    '''
    
    # 保存SVG文件
    with open(image_path, 'w', encoding='utf-8') as f:
        f.write(svg_content.strip())
    
    print(f"✅ 備用圖像生成成功: {image_path}")
    
    # 返回圖像信息
    return {
        'filename': image_filename,
        'path': image_path,
        'url': f"/uploads/{image_filename}",
        'prompt': prompt,
        'style': 'fallback',
        'size': '500x500'
    }

2. 圖像生成API路由 (routes/image_routes.py)

from flask import Blueprint, request, jsonify

# 創建圖像相關的藍圖
bp = Blueprint('image', __name__)

# 導入圖像生成服務
from services.image_service import generate_image

# 圖像生成路由
@bp.route('/generate', methods=['POST'])
def generate_image_route():
    try:
        # 獲取請求數據
        data = request.json
        
        # 驗證必要的參數
        if not data or 'prompt' not in data:
            return jsonify({'error': '缺少必要的提示詞參數'}), 400
        
        # 獲取參數
        prompt = data['prompt']
        style = data.get('style', 'cartoon')  # 默認卡通風格
        size = data.get('size', '1024x1024')  # 默認尺寸
        
        # 調用圖像生成服務
        image_data = generate_image(prompt, style, size)
        
        # 返回成功響應
        return jsonify(image_data), 200
        
    except Exception as e:
        # 記錄錯誤並返回錯誤響應
        print(f"❌ 圖像生成API錯誤: {e}")
        return jsonify({'error': f'生成圖像時發生錯誤: {str(e)}'}), 500

功能説明

圖像生成服務

  • 核心功能:使用阿里雲百鍊平台的圖像生成API創建故事插圖
  • 提示詞優化:針對兒童插畫特點優化提示詞,支持多種藝術風格
  • 多來源數據提取:靈活處理不同格式的API響應(base64、URL、嵌套結構)
  • 錯誤處理:完善的異常捕獲和備用圖像生成機制
  • 圖像保存:將生成的圖像保存到本地文件系統

備用圖像生成

  • 應急方案:當API調用失敗時,自動生成SVG格式的備用圖像
  • 內容關聯:備用圖像包含故事主題摘要,保持與原始請求的關聯性
  • 友好提示:清晰指示這是備用圖像,避免用户困惑

API路由

  • 生成圖像:POST /image/generate,接收提示詞、風格和尺寸參數
  • 參數驗證:確保必要參數存在,設置合理默認值
  • 錯誤響應:統一的錯誤處理和狀態碼返回

技術棧

  • Python 3.12
  • Flask Web框架
  • 阿里雲百鍊平台圖像生成API
  • SVG生成(備用方案)
  • Base64編碼處理
  • 異常處理與容錯機制

使用方法

  1. 確保配置了阿里雲API密鑰(在.env文件中)
  2. 調用 /image/generate 端點,傳入故事相關的提示詞
  3. 可選指定圖像風格和尺寸
  4. API返回生成的圖像信息,包括文件名、路徑和URL

與Story模型的集成

  • 生成的圖像URL可以保存到Story模型的image_url字段
  • 支持為每個故事關聯配套插圖
  • 與實驗一的故事生成服務協同工作

特色功能

  • 多樣化風格支持:卡通、水彩、扁平化、動漫等多種藝術風格
  • 兒童友好優化:專為兒童設計的插圖風格,確保內容健康正面
  • 多級容錯機制:API失敗時的備用方案,保證服務可用性
  • 靈活的圖像格式:支持PNG格式的AI生成圖像和SVG格式的備用圖像