- Add Python FastAPI backend with Pydantic validation - Port WhiskClient and MetaAIClient to Python - Create API routers for all endpoints - Add Swagger/ReDoc documentation at /docs - Update Dockerfile for multi-service container - Add lib/api.ts frontend client - Update README for V3
164 lines
4.4 KiB
Python
164 lines
4.4 KiB
Python
"""
|
|
History Router - Upload history management
|
|
|
|
Note: This is a simplified version. The original Next.js version
|
|
stores history in memory/file. For FastAPI, we'll use a simple
|
|
JSON file approach similar to prompts.
|
|
"""
|
|
from fastapi import APIRouter, HTTPException, UploadFile, File, Form
|
|
from models.responses import HistoryResponse, HistoryItem, ErrorResponse
|
|
from services.whisk_client import WhiskClient
|
|
from pathlib import Path
|
|
import json
|
|
import uuid
|
|
import base64
|
|
from typing import Optional
|
|
from datetime import datetime
|
|
|
|
router = APIRouter(prefix="/history", tags=["History"])
|
|
|
|
# History storage
|
|
HISTORY_FILE = Path(__file__).parent.parent.parent / "data" / "history.json"
|
|
|
|
|
|
def load_history() -> list:
|
|
"""Load history from JSON file"""
|
|
try:
|
|
if HISTORY_FILE.exists():
|
|
return json.loads(HISTORY_FILE.read_text())
|
|
except:
|
|
pass
|
|
return []
|
|
|
|
|
|
def save_history(history: list) -> None:
|
|
"""Save history to JSON file"""
|
|
HISTORY_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
HISTORY_FILE.write_text(json.dumps(history, indent=2))
|
|
|
|
|
|
@router.get(
|
|
"",
|
|
response_model=HistoryResponse,
|
|
responses={500: {"model": ErrorResponse}}
|
|
)
|
|
async def get_history(category: Optional[str] = None):
|
|
"""
|
|
Get upload history.
|
|
|
|
- **category**: Optional filter by category (subject, scene, style)
|
|
"""
|
|
try:
|
|
history = load_history()
|
|
|
|
if category:
|
|
history = [h for h in history if h.get("category") == category]
|
|
|
|
return HistoryResponse(history=[HistoryItem(**h) for h in history])
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.post(
|
|
"",
|
|
response_model=HistoryItem,
|
|
responses={
|
|
400: {"model": ErrorResponse},
|
|
500: {"model": ErrorResponse}
|
|
}
|
|
)
|
|
async def upload_to_history(
|
|
file: UploadFile = File(...),
|
|
category: str = Form(default="subject"),
|
|
cookies: Optional[str] = Form(default=None)
|
|
):
|
|
"""
|
|
Upload an image to history.
|
|
|
|
- **file**: Image file to upload
|
|
- **category**: Category (subject, scene, style)
|
|
- **cookies**: Optional Whisk cookies to also upload to Whisk
|
|
"""
|
|
try:
|
|
# Read file
|
|
content = await file.read()
|
|
base64_data = base64.b64encode(content).decode('utf-8')
|
|
mime_type = file.content_type or 'image/png'
|
|
|
|
# Upload to Whisk if cookies provided
|
|
media_id = None
|
|
if cookies:
|
|
try:
|
|
client = WhiskClient(cookies)
|
|
media_id = await client.upload_reference_image(base64_data, mime_type, category)
|
|
except Exception as e:
|
|
print(f"[History] Whisk upload failed: {e}")
|
|
|
|
# Create history item
|
|
new_item = {
|
|
"id": str(uuid.uuid4()),
|
|
"url": f"data:{mime_type};base64,{base64_data}",
|
|
"originalName": file.filename or "upload.png",
|
|
"category": category,
|
|
"mediaId": media_id,
|
|
"createdAt": int(datetime.now().timestamp() * 1000)
|
|
}
|
|
|
|
# Save to history
|
|
history = load_history()
|
|
history.insert(0, new_item) # Add to beginning
|
|
save_history(history)
|
|
|
|
return HistoryItem(**new_item)
|
|
|
|
except Exception as e:
|
|
print(f"Upload error: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.delete(
|
|
"/{item_id}",
|
|
responses={
|
|
404: {"model": ErrorResponse},
|
|
500: {"model": ErrorResponse}
|
|
}
|
|
)
|
|
async def delete_history_item(item_id: str):
|
|
"""
|
|
Delete an item from history.
|
|
|
|
- **item_id**: ID of the history item to delete
|
|
"""
|
|
try:
|
|
history = load_history()
|
|
original_len = len(history)
|
|
|
|
history = [h for h in history if h.get("id") != item_id]
|
|
|
|
if len(history) == original_len:
|
|
raise HTTPException(status_code=404, detail="Item not found")
|
|
|
|
save_history(history)
|
|
|
|
return {"success": True, "deleted": item_id}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.delete(
|
|
"",
|
|
responses={500: {"model": ErrorResponse}}
|
|
)
|
|
async def clear_history():
|
|
"""
|
|
Clear all history items.
|
|
"""
|
|
try:
|
|
save_history([])
|
|
return {"success": True, "message": "History cleared"}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|