Files
freepik-api/app/routers/image_generation.py
Sebastian Krüger 1a5c686dfc
Some checks failed
Build and Push Docker Image / build (push) Failing after 9s
fix: align Freepik API paths with OpenAPI spec
The original implementation used guessed endpoint paths that don't match
the actual Freepik API. Key fixes based on their OpenAPI spec:

- Task polling is per-endpoint (e.g. GET /v1/ai/text-to-image/flux-dev/{task-id})
  not a generic /v1/ai/tasks/{id}. freepik_client now returns TaskResult
  with status_path, and task_tracker polls using that path.
- Fixed endpoint paths: flux-pro -> flux-pro-v1-1, upscale -> image-upscaler,
  relight -> image-relight, style-transfer -> image-style-transfer,
  expand -> image-expand/flux-pro, inpaint -> ideogram-image-edit,
  remove-background -> beta/remove-background, classifier -> classifier/image,
  audio-isolate -> audio-isolation, icon -> text-to-icon
- Fixed video paths: kling -> kling-o1-pro with kling-o1 status path,
  minimax -> minimax-hailuo-02-1080p, seedance -> seedance-pro-1080p
- Fixed request schemas to match actual API params (e.g. scale_factor
  not scale, reference_image not style_reference, image_url for bg removal)
- Fixed response parsing: status is uppercase (COMPLETED not completed),
  results in data.generated[] array, classifier returns [{class_name, probability}]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:26:42 +01:00

114 lines
4.0 KiB
Python

import asyncio
import logging
from datetime import datetime, timezone
from fastapi import APIRouter, HTTPException, Query
from app.schemas.common import TaskDetail, TaskResponse, TaskStatus
from app.schemas.image_generation import (
FluxDevRequest,
FluxProRequest,
MysticRequest,
SeedreamRequest,
)
from app.services import task_tracker
from app.services.freepik_client import TaskResult, _extract_task_id
logger = logging.getLogger(__name__)
router = APIRouter(prefix='/api/v1/generate/image', tags=['image-generation'])
async def _submit_and_respond(
result: TaskResult,
sync: bool,
metadata: dict | None = None,
) -> TaskResponse | TaskDetail:
"""Extract task_id from Freepik response, track it, optionally wait."""
freepik_task_id = _extract_task_id(result.data)
if not freepik_task_id:
raise HTTPException(status_code=502, detail='No task_id in Freepik response')
internal_id = task_tracker.submit(freepik_task_id, result.status_path, metadata)
if not sync:
return TaskResponse(
task_id=internal_id,
status=TaskStatus.pending,
created_at=datetime.now(timezone.utc),
)
# Sync mode: wait for completion
from app.config import settings
elapsed = 0
while elapsed < settings.task_poll_timeout_seconds:
await asyncio.sleep(2)
elapsed += 2
task = task_tracker.get_task(internal_id)
if not task:
raise HTTPException(status_code=500, detail='Task disappeared')
if task['status'] == TaskStatus.completed:
return TaskDetail(
task_id=internal_id,
status=TaskStatus.completed,
created_at=task['created_at'],
updated_at=task['updated_at'],
progress=1.0,
result_url=f'/api/v1/tasks/{internal_id}/result',
)
if task['status'] == TaskStatus.failed:
raise HTTPException(status_code=502, detail=task.get('error', 'Task failed'))
raise HTTPException(status_code=504, detail='Task did not complete in time')
@router.post('/mystic', response_model=TaskResponse)
async def generate_mystic(request: MysticRequest, sync: bool = Query(False)):
from app.services import freepik_client
result = await freepik_client.generate_mystic(
prompt=request.prompt,
resolution=request.resolution,
aspect_ratio=request.aspect_ratio,
model=request.model,
seed=request.seed,
styling=request.styling,
structure_reference=request.structure_reference,
style_reference=request.style_reference,
)
return await _submit_and_respond(result, sync, {'model': 'mystic'})
@router.post('/flux-dev', response_model=TaskResponse)
async def generate_flux_dev(request: FluxDevRequest, sync: bool = Query(False)):
from app.services import freepik_client
result = await freepik_client.generate_flux_dev(
prompt=request.prompt,
aspect_ratio=request.aspect_ratio,
styling=request.styling,
seed=request.seed,
)
return await _submit_and_respond(result, sync, {'model': 'flux-dev'})
@router.post('/flux-pro', response_model=TaskResponse)
async def generate_flux_pro(request: FluxProRequest, sync: bool = Query(False)):
from app.services import freepik_client
result = await freepik_client.generate_flux_pro(
prompt=request.prompt,
aspect_ratio=request.aspect_ratio,
styling=request.styling,
seed=request.seed,
)
return await _submit_and_respond(result, sync, {'model': 'flux-pro'})
@router.post('/seedream', response_model=TaskResponse)
async def generate_seedream(request: SeedreamRequest, sync: bool = Query(False)):
from app.services import freepik_client
result = await freepik_client.generate_seedream(
prompt=request.prompt,
aspect_ratio=request.aspect_ratio,
seed=request.seed,
)
return await _submit_and_respond(result, sync, {'model': 'seedream'})