Add File
This commit is contained in:
360
src/landppt/api/global_master_template_api.py
Normal file
360
src/landppt/api/global_master_template_api.py
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
"""
|
||||||
|
Global Master Template API endpoints
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import List, Optional
|
||||||
|
from fastapi import APIRouter, HTTPException, Depends, Query
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
GlobalMasterTemplateCreate, GlobalMasterTemplateUpdate, GlobalMasterTemplateResponse,
|
||||||
|
GlobalMasterTemplateDetailResponse, GlobalMasterTemplateGenerateRequest,
|
||||||
|
TemplateSelectionRequest, TemplateSelectionResponse
|
||||||
|
)
|
||||||
|
from ..services.global_master_template_service import GlobalMasterTemplateService
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Create router
|
||||||
|
router = APIRouter(prefix="/api/global-master-templates", tags=["Global Master Templates"])
|
||||||
|
|
||||||
|
# Service instance
|
||||||
|
template_service = GlobalMasterTemplateService()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response_model=GlobalMasterTemplateResponse)
|
||||||
|
async def create_template(template_data: GlobalMasterTemplateCreate):
|
||||||
|
"""Create a new global master template"""
|
||||||
|
try:
|
||||||
|
result = await template_service.create_template(template_data.model_dump())
|
||||||
|
return GlobalMasterTemplateResponse(**result)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to create template: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to create template")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=dict)
|
||||||
|
async def get_all_templates(
|
||||||
|
active_only: bool = Query(True, description="Only return active templates"),
|
||||||
|
tags: Optional[str] = Query(None, description="Filter by tags (comma-separated)"),
|
||||||
|
page: int = Query(1, ge=1, description="Page number (1-based)"),
|
||||||
|
page_size: int = Query(6, ge=1, le=100, description="Number of items per page"),
|
||||||
|
search: Optional[str] = Query(None, description="Search in template name and description")
|
||||||
|
):
|
||||||
|
"""Get all global master templates with pagination"""
|
||||||
|
try:
|
||||||
|
if tags:
|
||||||
|
tag_list = [tag.strip() for tag in tags.split(",")]
|
||||||
|
result = await template_service.get_templates_by_tags_paginated(
|
||||||
|
tag_list, active_only, page, page_size, search
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result = await template_service.get_all_templates_paginated(
|
||||||
|
active_only, page, page_size, search
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"templates": [GlobalMasterTemplateResponse(**template) for template in result["templates"]],
|
||||||
|
"pagination": result["pagination"]
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to get templates: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to get templates")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{template_id}", response_model=GlobalMasterTemplateDetailResponse)
|
||||||
|
async def get_template_by_id(template_id: int):
|
||||||
|
"""Get a global master template by ID"""
|
||||||
|
try:
|
||||||
|
template = await template_service.get_template_by_id(template_id)
|
||||||
|
if not template:
|
||||||
|
raise HTTPException(status_code=404, detail="Template not found")
|
||||||
|
|
||||||
|
return GlobalMasterTemplateDetailResponse(**template)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to get template {template_id}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to get template")
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{template_id}", response_model=dict)
|
||||||
|
async def update_template(template_id: int, update_data: GlobalMasterTemplateUpdate):
|
||||||
|
"""Update a global master template"""
|
||||||
|
try:
|
||||||
|
# Filter out None values
|
||||||
|
update_dict = {k: v for k, v in update_data.model_dump().items() if v is not None}
|
||||||
|
|
||||||
|
if not update_dict:
|
||||||
|
raise HTTPException(status_code=400, detail="No update data provided")
|
||||||
|
|
||||||
|
success = await template_service.update_template(template_id, update_dict)
|
||||||
|
if not success:
|
||||||
|
raise HTTPException(status_code=404, detail="Template not found")
|
||||||
|
|
||||||
|
return {"success": True, "message": "Template updated successfully"}
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to update template {template_id}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to update template")
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{template_id}", response_model=dict)
|
||||||
|
async def delete_template(template_id: int):
|
||||||
|
"""Delete a global master template"""
|
||||||
|
try:
|
||||||
|
success = await template_service.delete_template(template_id)
|
||||||
|
if not success:
|
||||||
|
raise HTTPException(status_code=404, detail="Template not found")
|
||||||
|
|
||||||
|
return {"success": True, "message": "Template deleted successfully"}
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to delete template {template_id}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to delete template")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{template_id}/set-default", response_model=dict)
|
||||||
|
async def set_default_template(template_id: int):
|
||||||
|
"""Set a template as the default template"""
|
||||||
|
try:
|
||||||
|
success = await template_service.set_default_template(template_id)
|
||||||
|
if not success:
|
||||||
|
raise HTTPException(status_code=404, detail="Template not found")
|
||||||
|
|
||||||
|
return {"success": True, "message": "Default template set successfully"}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to set default template {template_id}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to set default template")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/default/template", response_model=GlobalMasterTemplateDetailResponse)
|
||||||
|
async def get_default_template():
|
||||||
|
"""Get the default global master template"""
|
||||||
|
try:
|
||||||
|
template = await template_service.get_default_template()
|
||||||
|
if not template:
|
||||||
|
raise HTTPException(status_code=404, detail="No default template found")
|
||||||
|
|
||||||
|
return GlobalMasterTemplateDetailResponse(**template)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to get default template: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to get default template")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/generate")
|
||||||
|
async def generate_template_with_ai(request: GlobalMasterTemplateGenerateRequest):
|
||||||
|
"""Generate a new template using AI (does not save to database)"""
|
||||||
|
try:
|
||||||
|
# 准备参考图片数据
|
||||||
|
reference_image_data = None
|
||||||
|
if request.reference_image:
|
||||||
|
reference_image_data = {
|
||||||
|
"filename": request.reference_image.filename,
|
||||||
|
"data": request.reference_image.data,
|
||||||
|
"size": request.reference_image.size,
|
||||||
|
"type": request.reference_image.type
|
||||||
|
}
|
||||||
|
|
||||||
|
# 使用AI生成服务(不保存到数据库)
|
||||||
|
result = await template_service.generate_template_with_ai(
|
||||||
|
prompt=request.prompt,
|
||||||
|
template_name=request.template_name,
|
||||||
|
description=request.description,
|
||||||
|
tags=request.tags,
|
||||||
|
generation_mode=request.generation_mode,
|
||||||
|
reference_image=reference_image_data
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"message": "模板生成完成!",
|
||||||
|
"data": result
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to generate template with AI: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/save-generated", response_model=GlobalMasterTemplateResponse)
|
||||||
|
async def save_generated_template(request: dict):
|
||||||
|
"""Save a generated template after user confirmation"""
|
||||||
|
try:
|
||||||
|
# Extract template data from request
|
||||||
|
template_data = {
|
||||||
|
'template_name': request.get('template_name'),
|
||||||
|
'description': request.get('description', ''),
|
||||||
|
'html_template': request.get('html_template'),
|
||||||
|
'tags': request.get('tags', []),
|
||||||
|
'created_by': 'AI'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add timestamp to avoid name conflicts
|
||||||
|
import time
|
||||||
|
timestamp = int(time.time())
|
||||||
|
template_data['template_name'] = f"{template_data['template_name']}_{timestamp}"
|
||||||
|
|
||||||
|
result = await template_service.create_template(template_data)
|
||||||
|
return GlobalMasterTemplateResponse(**result)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to save generated template: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to save template")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/adjust-template")
|
||||||
|
async def adjust_template(request: dict):
|
||||||
|
"""Adjust a generated template based on user feedback"""
|
||||||
|
from fastapi.responses import StreamingResponse
|
||||||
|
import json
|
||||||
|
|
||||||
|
async def adjust_stream():
|
||||||
|
try:
|
||||||
|
# Get the current template and adjustment request
|
||||||
|
current_html = request.get('html_template')
|
||||||
|
adjustment_request = request.get('adjustment_request')
|
||||||
|
template_name = request.get('template_name', '模板')
|
||||||
|
|
||||||
|
# Send initial status
|
||||||
|
yield f"data: {json.dumps({'type': 'status', 'message': '正在分析调整需求...'})}\n\n"
|
||||||
|
|
||||||
|
# Use the template service to adjust the template
|
||||||
|
async for chunk in template_service.adjust_template_with_ai_stream(
|
||||||
|
current_html=current_html,
|
||||||
|
adjustment_request=adjustment_request,
|
||||||
|
template_name=template_name
|
||||||
|
):
|
||||||
|
yield f"data: {json.dumps(chunk)}\n\n"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to adjust template: {e}")
|
||||||
|
yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n"
|
||||||
|
|
||||||
|
return StreamingResponse(
|
||||||
|
adjust_stream(),
|
||||||
|
media_type="text/plain",
|
||||||
|
headers={
|
||||||
|
"Cache-Control": "no-cache",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Content-Type": "text/event-stream"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/select", response_model=TemplateSelectionResponse)
|
||||||
|
async def select_template_for_project(request: TemplateSelectionRequest):
|
||||||
|
"""Select a template for PPT generation"""
|
||||||
|
try:
|
||||||
|
if request.selected_template_id:
|
||||||
|
# Get the selected template
|
||||||
|
template = await template_service.get_template_by_id(request.selected_template_id)
|
||||||
|
if not template:
|
||||||
|
raise HTTPException(status_code=404, detail="Selected template not found")
|
||||||
|
|
||||||
|
# Increment usage count
|
||||||
|
await template_service.increment_template_usage(request.selected_template_id)
|
||||||
|
|
||||||
|
return TemplateSelectionResponse(
|
||||||
|
success=True,
|
||||||
|
message="Template selected successfully",
|
||||||
|
selected_template=GlobalMasterTemplateResponse(**template)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Use default template
|
||||||
|
template = await template_service.get_default_template()
|
||||||
|
if not template:
|
||||||
|
raise HTTPException(status_code=404, detail="No default template found")
|
||||||
|
|
||||||
|
# Increment usage count
|
||||||
|
await template_service.increment_template_usage(template['id'])
|
||||||
|
|
||||||
|
return TemplateSelectionResponse(
|
||||||
|
success=True,
|
||||||
|
message="Default template selected",
|
||||||
|
selected_template=GlobalMasterTemplateResponse(**template)
|
||||||
|
)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to select template: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to select template")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{template_id}/duplicate", response_model=GlobalMasterTemplateResponse)
|
||||||
|
async def duplicate_template(template_id: int, new_name: str = Query(..., description="New template name")):
|
||||||
|
"""Duplicate an existing template"""
|
||||||
|
try:
|
||||||
|
# Get the original template
|
||||||
|
original = await template_service.get_template_by_id(template_id)
|
||||||
|
if not original:
|
||||||
|
raise HTTPException(status_code=404, detail="Template not found")
|
||||||
|
|
||||||
|
# Create duplicate data
|
||||||
|
duplicate_data = {
|
||||||
|
'template_name': new_name,
|
||||||
|
'description': f"复制自: {original['template_name']}",
|
||||||
|
'html_template': original['html_template'],
|
||||||
|
'tags': original['tags'] + ['复制'],
|
||||||
|
'created_by': 'duplicate'
|
||||||
|
}
|
||||||
|
|
||||||
|
result = await template_service.create_template(duplicate_data)
|
||||||
|
return GlobalMasterTemplateResponse(**result)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to duplicate template {template_id}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to duplicate template")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{template_id}/preview", response_model=dict)
|
||||||
|
async def get_template_preview(template_id: int):
|
||||||
|
"""Get template preview data"""
|
||||||
|
try:
|
||||||
|
template = await template_service.get_template_by_id(template_id)
|
||||||
|
if not template:
|
||||||
|
raise HTTPException(status_code=404, detail="Template not found")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"id": template['id'],
|
||||||
|
"template_name": template['template_name'],
|
||||||
|
"preview_image": template['preview_image'],
|
||||||
|
"html_template": template['html_template']
|
||||||
|
}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to get template preview {template_id}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to get template preview")
|
||||||
|
|
||||||
|
|
||||||
|
# Add increment usage endpoint for internal use
|
||||||
|
@router.post("/{template_id}/increment-usage", response_model=dict)
|
||||||
|
async def increment_template_usage(template_id: int):
|
||||||
|
"""Increment template usage count (internal use)"""
|
||||||
|
try:
|
||||||
|
success = await template_service.increment_template_usage(template_id)
|
||||||
|
if not success:
|
||||||
|
raise HTTPException(status_code=404, detail="Template not found")
|
||||||
|
|
||||||
|
return {"success": True, "message": "Usage count incremented"}
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to increment usage for template {template_id}: {e}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to increment usage count")
|
||||||
Reference in New Issue
Block a user