Initial commit: 国土空间规划课程智能体 v1.0
单容器 Docker 架构的国土空间规划课程智能问答系统,集成 FastAPI 后端与 Next.js 前端。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
"""
|
||||
图像生成和分析API
|
||||
支持文生图和图生图功能
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..core.database import get_db
|
||||
from ..core.security import get_current_user
|
||||
from ..models.user import User
|
||||
from ..models.generated_image import GeneratedImageRecord
|
||||
from ..models.image_models import (
|
||||
TextToImageRequest, TextToImageResponse, GeneratedImage,
|
||||
ImageEditRequest, ImageEditResponse, ImageEditResult,
|
||||
ImageVariationRequest, ImageVariationResponse, ImageVariationResult,
|
||||
ModelInfo, TemplateInfo, StyleInfo, SizeInfo, EditModeInfo
|
||||
)
|
||||
from ..services.text_to_image_service import TextToImageService
|
||||
from ..services.image_to_image_service import ImageToImageService
|
||||
|
||||
router = APIRouter(prefix="/image", tags=["图像生成"])
|
||||
|
||||
# 服务实例
|
||||
text_to_image_service = TextToImageService()
|
||||
image_to_image_service = ImageToImageService()
|
||||
|
||||
|
||||
def _save_record(db: Session, user_id: int, image_id: str, image_type: str,
|
||||
image_url: str, prompt: str = None, model: str = None,
|
||||
style: str = None, size: str = None, template: str = None,
|
||||
original_filename: str = None, mode: str = None,
|
||||
strength: float = None, metadata: dict = None):
|
||||
"""保存图像生成记录到数据库"""
|
||||
record = GeneratedImageRecord(
|
||||
user_id=user_id,
|
||||
image_id=image_id,
|
||||
image_type=image_type,
|
||||
prompt=prompt,
|
||||
model=model,
|
||||
style=style,
|
||||
size=size,
|
||||
template=template,
|
||||
image_url=image_url,
|
||||
original_filename=original_filename,
|
||||
mode=mode,
|
||||
strength=str(strength) if strength else None,
|
||||
generation_meta=json.dumps(metadata or {}, ensure_ascii=False),
|
||||
)
|
||||
db.add(record)
|
||||
db.commit()
|
||||
|
||||
|
||||
@router.post("/text-to-image", response_model=TextToImageResponse)
|
||||
async def text_to_image(
|
||||
request: TextToImageRequest,
|
||||
current_user: str = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""文生图 - 从文字描述生成图像"""
|
||||
try:
|
||||
user = db.query(User).filter(User.username == current_user).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
|
||||
images_data = await text_to_image_service.generate(
|
||||
prompt=request.prompt, model=request.model, template=request.template,
|
||||
style=request.style, size=request.size, num_images=request.num_images
|
||||
)
|
||||
|
||||
images = [
|
||||
GeneratedImage(id=img["id"], url=img["url"], prompt=img["prompt"],
|
||||
model=img["model"], metadata=img["metadata"])
|
||||
for img in images_data
|
||||
]
|
||||
|
||||
for img in images_data:
|
||||
_save_record(db, user.id, img["id"], "text_to_image", img["url"],
|
||||
prompt=request.prompt, model=request.model,
|
||||
style=request.style, size=request.size,
|
||||
template=request.template, metadata=img.get("metadata"))
|
||||
|
||||
return TextToImageResponse(images=images, total=len(images))
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"文生图失败: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/image-to-image/edit", response_model=ImageEditResponse)
|
||||
async def image_edit(
|
||||
image: UploadFile = File(...),
|
||||
prompt: str = Form(...),
|
||||
mode: str = Form("optimize"),
|
||||
mask: Optional[UploadFile] = File(None),
|
||||
strength: float = Form(0.8),
|
||||
current_user: str = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""图生图 - 图像编辑"""
|
||||
try:
|
||||
user = db.query(User).filter(User.username == current_user).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
|
||||
result_data = await image_to_image_service.edit_image(
|
||||
image_file=image, prompt=prompt, mode=mode, mask_file=mask, strength=strength
|
||||
)
|
||||
|
||||
result = ImageEditResult(
|
||||
id=result_data["id"], url=result_data["url"],
|
||||
original_filename=result_data["original_filename"],
|
||||
edit_prompt=result_data["edit_prompt"], mode=result_data["mode"],
|
||||
metadata=result_data["metadata"]
|
||||
)
|
||||
|
||||
_save_record(db, user.id, result_data["id"], "image_edit", result_data["url"],
|
||||
prompt=prompt, model="Qwen-Image-Edit",
|
||||
original_filename=result_data.get("original_filename"),
|
||||
mode=mode, strength=strength, metadata=result_data.get("metadata"))
|
||||
|
||||
return ImageEditResponse(result=result)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"图像编辑失败: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/image-to-image/variations", response_model=ImageVariationResponse)
|
||||
async def image_variations(
|
||||
image: UploadFile = File(...),
|
||||
num_variations: int = Form(3),
|
||||
current_user: str = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""图生图 - 生成图像变体"""
|
||||
try:
|
||||
user = db.query(User).filter(User.username == current_user).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
|
||||
variations_data = await image_to_image_service.create_variation(
|
||||
image_file=image, num_variations=num_variations
|
||||
)
|
||||
|
||||
variations = [
|
||||
ImageVariationResult(id=var["id"], url=var["url"], type=var["type"], metadata=var["metadata"])
|
||||
for var in variations_data
|
||||
]
|
||||
|
||||
for var in variations_data:
|
||||
_save_record(db, user.id, var["id"], "variation", var["url"],
|
||||
model="Qwen-Image-Edit", metadata=var.get("metadata"))
|
||||
|
||||
return ImageVariationResponse(variations=variations, total=len(variations))
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"生成图像变体失败: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/image-to-image/style-transfer", response_model=ImageEditResponse)
|
||||
async def style_transfer(
|
||||
image: UploadFile = File(...),
|
||||
style_prompt: str = Form(...),
|
||||
strength: float = Form(0.8),
|
||||
current_user: str = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""图生图 - 风格转换"""
|
||||
try:
|
||||
user = db.query(User).filter(User.username == current_user).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
|
||||
result_data = await image_to_image_service.style_transfer(
|
||||
image_file=image, style_prompt=style_prompt, strength=strength
|
||||
)
|
||||
|
||||
result = ImageEditResult(
|
||||
id=result_data["id"], url=result_data["url"],
|
||||
original_filename=result_data["original_filename"],
|
||||
edit_prompt=result_data["edit_prompt"], mode=result_data["mode"],
|
||||
metadata=result_data["metadata"]
|
||||
)
|
||||
|
||||
_save_record(db, user.id, result_data["id"], "style_transfer", result_data["url"],
|
||||
prompt=style_prompt, model="Qwen-Image-Edit",
|
||||
original_filename=result_data.get("original_filename"),
|
||||
mode="style_transfer", strength=strength, metadata=result_data.get("metadata"))
|
||||
|
||||
return ImageEditResponse(result=result)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"风格转换失败: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/image-to-image/optimize", response_model=ImageEditResponse)
|
||||
async def optimize_image(
|
||||
image: UploadFile = File(...),
|
||||
optimization_prompt: str = Form("优化图像质量,增强细节,提高清晰度"),
|
||||
strength: float = Form(0.6),
|
||||
current_user: str = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""图生图 - 图像优化"""
|
||||
try:
|
||||
user = db.query(User).filter(User.username == current_user).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
|
||||
result_data = await image_to_image_service.optimize_image(
|
||||
image_file=image, optimization_prompt=optimization_prompt, strength=strength
|
||||
)
|
||||
|
||||
result = ImageEditResult(
|
||||
id=result_data["id"], url=result_data["url"],
|
||||
original_filename=result_data["original_filename"],
|
||||
edit_prompt=result_data["edit_prompt"], mode=result_data["mode"],
|
||||
metadata=result_data["metadata"]
|
||||
)
|
||||
|
||||
_save_record(db, user.id, result_data["id"], "optimize", result_data["url"],
|
||||
prompt=optimization_prompt, model="Qwen-Image-Edit",
|
||||
original_filename=result_data.get("original_filename"),
|
||||
mode="optimize", strength=strength, metadata=result_data.get("metadata"))
|
||||
|
||||
return ImageEditResponse(result=result)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"图像优化失败: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/image-to-image/outpaint", response_model=ImageEditResponse)
|
||||
async def outpaint_image(
|
||||
image: UploadFile = File(...),
|
||||
expansion_prompt: str = Form(...),
|
||||
strength: float = Form(0.7),
|
||||
current_user: str = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""图生图 - 图像智能扩展"""
|
||||
try:
|
||||
user = db.query(User).filter(User.username == current_user).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
|
||||
result_data = await image_to_image_service.outpaint_image(
|
||||
image_file=image, expansion_prompt=expansion_prompt, strength=strength
|
||||
)
|
||||
|
||||
result = ImageEditResult(
|
||||
id=result_data["id"], url=result_data["url"],
|
||||
original_filename=result_data["original_filename"],
|
||||
edit_prompt=result_data["edit_prompt"], mode=result_data["mode"],
|
||||
metadata=result_data["metadata"]
|
||||
)
|
||||
|
||||
_save_record(db, user.id, result_data["id"], "outpaint", result_data["url"],
|
||||
prompt=expansion_prompt, model="Qwen-Image-Edit",
|
||||
original_filename=result_data.get("original_filename"),
|
||||
mode="outpaint", strength=strength, metadata=result_data.get("metadata"))
|
||||
|
||||
return ImageEditResponse(result=result)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"图像扩展失败: {str(e)}")
|
||||
|
||||
|
||||
# 配置信息端点
|
||||
@router.get("/models")
|
||||
async def get_available_models():
|
||||
"""获取可用的模型列表"""
|
||||
models_data = text_to_image_service.get_available_models()
|
||||
return {
|
||||
"models": [
|
||||
ModelInfo(id=model["id"], name=model["name"], description=model["description"])
|
||||
for model in models_data
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/templates")
|
||||
async def get_available_templates():
|
||||
"""获取可用的提示词模板"""
|
||||
templates_data = text_to_image_service.get_available_templates()
|
||||
return {
|
||||
"templates": [
|
||||
TemplateInfo(id=template["id"], name=template["name"], description=template["description"])
|
||||
for template in templates_data
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/styles")
|
||||
async def get_available_styles():
|
||||
"""获取可用的风格选项"""
|
||||
styles_data = text_to_image_service.get_available_styles()
|
||||
return {
|
||||
"styles": [
|
||||
StyleInfo(id=style["id"], name=style["name"], description=style["description"])
|
||||
for style in styles_data
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/sizes")
|
||||
async def get_available_sizes():
|
||||
"""获取可用的尺寸选项"""
|
||||
sizes_data = text_to_image_service.get_available_sizes()
|
||||
return {
|
||||
"sizes": [
|
||||
SizeInfo(id=size["id"], name=size["name"], description=size["description"])
|
||||
for size in sizes_data
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/edit-modes")
|
||||
async def get_edit_modes():
|
||||
"""获取可用的编辑模式"""
|
||||
modes_data = image_to_image_service.get_available_modes()
|
||||
return {
|
||||
"modes": [
|
||||
EditModeInfo(id=mode["id"], name=mode["name"], description=mode["description"])
|
||||
for mode in modes_data
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/style-presets")
|
||||
async def get_style_presets():
|
||||
"""获取预设风格选项"""
|
||||
presets_data = image_to_image_service.get_style_presets()
|
||||
return {
|
||||
"presets": [
|
||||
StyleInfo(id=preset["id"], name=preset["name"], description=preset["description"])
|
||||
for preset in presets_data
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/history")
|
||||
async def get_image_history(
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
current_user: str = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取用户的图像生成历史"""
|
||||
try:
|
||||
user = db.query(User).filter(User.username == current_user).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
|
||||
|
||||
total = db.query(GeneratedImageRecord).filter(GeneratedImageRecord.user_id == user.id).count()
|
||||
records = (
|
||||
db.query(GeneratedImageRecord)
|
||||
.filter(GeneratedImageRecord.user_id == user.id)
|
||||
.order_by(GeneratedImageRecord.created_at.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
|
||||
images = []
|
||||
for r in records:
|
||||
img = {
|
||||
"id": r.image_id,
|
||||
"url": r.image_url,
|
||||
"image_type": r.image_type,
|
||||
"prompt": r.prompt,
|
||||
"model": r.model,
|
||||
"created_at": r.created_at.isoformat() if r.created_at else None,
|
||||
}
|
||||
if r.style:
|
||||
img["style"] = r.style
|
||||
if r.size:
|
||||
img["size"] = r.size
|
||||
if r.template:
|
||||
img["template"] = r.template
|
||||
if r.mode:
|
||||
img["mode"] = r.mode
|
||||
if r.original_filename:
|
||||
img["original_filename"] = r.original_filename
|
||||
images.append(img)
|
||||
|
||||
return {
|
||||
"images": images,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"has_next": page * page_size < total,
|
||||
"has_prev": page > 1
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"获取图像历史失败: {str(e)}")
|
||||
Reference in New Issue
Block a user