apix/services/crawl4ai/app/main.py
Khoa.vo 0f87b8ef99
Some checks are pending
CI / build (18.x) (push) Waiting to run
CI / build (20.x) (push) Waiting to run
feat: add Meta AI video generation
- Add /video/generate endpoint to crawl4ai Python service
- Add VideoGenerateRequest and VideoGenerateResponse models
- Add generateVideo method to MetaCrawlClient TypeScript client
- Add /api/meta/video Next.js API route
- Add 'Video' button in PromptHero UI (visible only for Meta AI provider)
- Blue/cyan gradient styling for Video button to differentiate from Generate
2026-01-06 13:52:31 +07:00

285 lines
8 KiB
Python

"""
Meta AI FastAPI Service (v2.0)
Uses metaai-api library for Meta AI image generation.
See: https://github.com/mir-ashiq/metaai-api
"""
from contextlib import asynccontextmanager
from fastapi import FastAPI, BackgroundTasks, HTTPException
from fastapi.middleware.cors import CORSMiddleware
import asyncio
import uuid
from .models import (
GenerateRequest,
GenerateResponse,
ImageResult,
TaskStatusResponse,
HealthResponse,
GrokChatRequest,
GrokChatResponse,
VideoGenerateRequest,
VideoGenerateResponse,
VideoResult
)
from .grok_client import GrokChatClient
from .meta_crawler import meta_crawler
# Initialize Grok client
grok_client = GrokChatClient()
# Task storage (in-memory for simplicity)
tasks: dict = {}
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Startup and shutdown events"""
print("[MetaAI] Starting Meta AI service...")
yield
print("[MetaAI] Shutting down...")
app = FastAPI(
title="Meta AI Image Generation Service",
description="FastAPI wrapper for Meta AI image generation using metaai-api",
version="2.0.0",
lifespan=lifespan
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/health", response_model=HealthResponse)
async def health_check():
"""Health check endpoint"""
return HealthResponse(
status="healthy",
version="2.0.0",
browser_ready=True # metaai-api handles this internally
)
@app.get("/rate-limit")
async def get_rate_limit():
"""Get current rate limiting status"""
return meta_crawler.get_rate_limit_status()
@app.post("/generate/sync", response_model=GenerateResponse)
async def generate_sync(request: GenerateRequest):
"""
Synchronous image generation - returns when complete.
Requires:
- prompt: The image generation prompt
- cookies: Facebook/Meta cookies (JSON array or string format)
"""
try:
images = await meta_crawler.generate_images(
prompt=request.prompt,
cookies=request.cookies,
num_images=request.num_images
)
return GenerateResponse(
success=True,
images=images,
error=None
)
except Exception as e:
return GenerateResponse(
success=False,
images=[],
error=str(e)
)
@app.post("/generate", response_model=GenerateResponse)
async def generate_async(request: GenerateRequest, background_tasks: BackgroundTasks):
"""
Async image generation - returns immediately with task_id.
Poll /status/{task_id} for results.
"""
task_id = str(uuid.uuid4())
tasks[task_id] = {
"status": "pending",
"images": [],
"error": None
}
async def run_generation():
try:
images = await meta_crawler.generate_images(
prompt=request.prompt,
cookies=request.cookies,
num_images=request.num_images
)
tasks[task_id] = {
"status": "completed",
"images": images,
"error": None
}
except Exception as e:
tasks[task_id] = {
"status": "failed",
"images": [],
"error": str(e)
}
# Run in background
background_tasks.add_task(asyncio.create_task, run_generation())
return GenerateResponse(
success=True,
images=[],
error=None,
task_id=task_id
)
@app.get("/status/{task_id}", response_model=TaskStatusResponse)
async def get_task_status(task_id: str):
"""Get status of async generation task"""
if task_id not in tasks:
raise HTTPException(status_code=404, detail="Task not found")
task = tasks[task_id]
return TaskStatusResponse(
task_id=task_id,
status=task["status"],
images=task["images"],
error=task["error"]
)
@app.delete("/status/{task_id}")
async def delete_task(task_id: str):
"""Clean up completed task"""
if task_id in tasks:
del tasks[task_id]
return {"deleted": True}
raise HTTPException(status_code=404, detail="Task not found")
@app.post("/grok/chat", response_model=GrokChatResponse)
async def grok_chat(request: GrokChatRequest):
"""
Chat with Grok AI
"""
try:
response = await grok_client.chat(request.message, request.history, request.cookies, request.user_agent)
return GrokChatResponse(response=response)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/video/generate", response_model=VideoGenerateResponse)
async def generate_video(request: VideoGenerateRequest):
"""
Generate a video from a text prompt using Meta AI.
This uses the metaai_api library's video generation feature.
Video generation takes longer than image generation (30-60+ seconds).
Requires:
- prompt: The video generation prompt
- cookies: Facebook/Meta cookies (JSON array or string format)
"""
import json
import asyncio
from concurrent.futures import ThreadPoolExecutor
try:
# Parse cookies to dict format for MetaAI
cookies_str = request.cookies.strip()
cookies_dict = {}
if cookies_str.startswith('['):
# JSON array format from Cookie Editor
parsed = json.loads(cookies_str)
if isinstance(parsed, list):
cookies_dict = {c['name']: c['value'] for c in parsed if 'name' in c and 'value' in c}
else:
# String format: "name1=value1; name2=value2"
for pair in cookies_str.split(';'):
pair = pair.strip()
if '=' in pair:
name, value = pair.split('=', 1)
cookies_dict[name.strip()] = value.strip()
if not cookies_dict:
return VideoGenerateResponse(
success=False,
videos=[],
error="No valid cookies provided"
)
print(f"[VideoGen] Starting video generation for: '{request.prompt[:50]}...'")
# Import MetaAI and run video generation in thread pool (it's synchronous)
from metaai_api import MetaAI
def run_video_gen():
ai = MetaAI(cookies=cookies_dict)
return ai.generate_video(
prompt=request.prompt,
wait_before_poll=10,
max_attempts=60, # Up to 5 minutes of polling
wait_seconds=5,
verbose=True
)
# Run in thread pool since metaai_api is synchronous
loop = asyncio.get_event_loop()
with ThreadPoolExecutor(max_workers=1) as executor:
result = await loop.run_in_executor(executor, run_video_gen)
if not result.get('success', False):
error_msg = result.get('error', 'Video generation failed')
print(f"[VideoGen] Failed: {error_msg}")
return VideoGenerateResponse(
success=False,
videos=[],
error=error_msg
)
video_urls = result.get('video_urls', [])
print(f"[VideoGen] Success! Got {len(video_urls)} video(s)")
videos = [
VideoResult(
url=url,
prompt=request.prompt,
model="meta_video"
)
for url in video_urls
]
return VideoGenerateResponse(
success=True,
videos=videos,
conversation_id=result.get('conversation_id')
)
except Exception as e:
import traceback
traceback.print_exc()
print(f"[VideoGen] Error: {str(e)}")
return VideoGenerateResponse(
success=False,
videos=[],
error=str(e)
)