后端: - free_video.py: 提交/轮询/收藏/软删全链路,复用 AITask(新增 is_deleted/is_favorited, migration 0022) - video_pricing.py: 按时长 token 计费(×1.10 buffer+clamp); video_errors.py 错误归一; media_probe.py 时长探测 - catalog/volcano: Seedance free-video 模型接入+seed(migration 0023) - 素材库: FreeAssetGroup/FreeAsset(火山 Assets API 引用登记, migration 0009)+ 上传/轮询/删除接口 - settings: FREE_VIDEO_MAX_CONCURRENT 团队并发闸(默认3); CELERY_TASK_ALWAYS_EAGER 本地联调开关(生产恒关) - 测试: test_free_video.py 新增; billing/products/projects tests 配套调整 前端: - /free-create 页面+components/free-create/ 全套(输入栏/@mention 素材引用/生成卡/视频详情弹窗/素材库弹窗) - api.ts/types.ts 扩展 free-video 与 free-assets 接口; 路由/侧边栏入口接入 bug/: 测试清单 (11)(12) 与截图归档 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
80 lines
3.3 KiB
Python
80 lines
3.3 KiB
Python
from airshelf.celery import app
|
|
|
|
|
|
@app.task(bind=True, max_retries=3)
|
|
def submit_ai_task(self, task_id: str) -> str:
|
|
return task_id
|
|
|
|
|
|
@app.task(bind=True, max_retries=5)
|
|
def poll_ai_task(self, task_id: str) -> str:
|
|
return task_id
|
|
|
|
|
|
@app.task(bind=True, max_retries=0)
|
|
def generate_standalone_image_task(self, task_id: str) -> str:
|
|
"""单张独立图的慢活(ARK 出图 ~30s)在 worker 内跑,Web 层不被占住。
|
|
幂等且失败自退费(见 run_standalone_image_task),故 max_retries=0,不向上抛重试。"""
|
|
from apps.ai.services import run_standalone_image_task
|
|
|
|
run_standalone_image_task(task_id=task_id)
|
|
return task_id
|
|
|
|
|
|
@app.task(bind=True, max_retries=0)
|
|
def extract_entities_task(self, task_id: str) -> str:
|
|
"""实体提取的慢活(豆包思考模型流式,可达数十秒)在 worker 内跑,Web 层秒回不被占住、不再 502。
|
|
幂等且失败自退费(见 run_extract_entities_task),故 max_retries=0,不向上抛重试。"""
|
|
from apps.ai.services import run_extract_entities_task
|
|
|
|
run_extract_entities_task(task_id=task_id)
|
|
return task_id
|
|
|
|
|
|
@app.task(bind=True, max_retries=0)
|
|
def generate_base_asset_task(self, task_id: str) -> str:
|
|
"""基础资产(商品/人物/场景立绘)的慢出图在 worker 内跑,Web 层不被占住。
|
|
幂等且失败自退费(见 run_base_asset_task),故 max_retries=0,不向上抛重试。"""
|
|
from apps.ai.services import run_base_asset_task
|
|
|
|
run_base_asset_task(task_id=task_id)
|
|
return task_id
|
|
|
|
|
|
@app.task(bind=True, max_retries=0)
|
|
def generate_triview_task(self, task_id: str) -> str:
|
|
"""三视图(image_edit 以立绘为参考,慢)在 worker 内跑,Web 层不被占住。
|
|
幂等且失败自退费(见 run_triview_task),故 max_retries=0,不向上抛重试。"""
|
|
from apps.ai.services import run_triview_task
|
|
|
|
run_triview_task(task_id=task_id)
|
|
return task_id
|
|
|
|
|
|
@app.task(bind=True, max_retries=0)
|
|
def poll_free_video_task(self, task_id: str, attempt: int = 0) -> str:
|
|
"""自由创作视频·worker 兜底轮询:每 30s 一次自重排(不依赖 celery beat),
|
|
上限 60 次(≈30 分钟,足够 Seedance 5-10 分钟出片)。finalize 幂等(POSTPROCESSING 认领),
|
|
与前端主动 poll 并存不双扣。轮询本身出错不重试(max_retries=0),下一次自重排继续。"""
|
|
from apps.ai.free_video import finalize_free_video
|
|
from apps.ai.models import AITask
|
|
|
|
task = AITask.objects.select_related("model_config", "model_config__provider", "team").filter(id=task_id).first()
|
|
if task is None:
|
|
return task_id
|
|
try:
|
|
task = finalize_free_video(task=task)
|
|
except Exception: # noqa: BLE001 — 单次轮询失败(网络抖动等)不终结任务,等下一轮
|
|
import logging
|
|
|
|
logging.getLogger(__name__).warning("poll_free_video_task %s attempt %s failed", task_id, attempt, exc_info=True)
|
|
# eager(本地联调/单测)下 apply_async 会内联立即执行,自重排=同步死循环 → 跳过,收尾交给前端主动 poll
|
|
from django.conf import settings as dj_settings
|
|
|
|
if getattr(dj_settings, "CELERY_TASK_ALWAYS_EAGER", False):
|
|
return task_id
|
|
if task.status in (AITask.Status.SUBMITTED, AITask.Status.POLLING) and attempt < 60:
|
|
poll_free_video_task.apply_async(args=[task_id, attempt + 1], countdown=30)
|
|
return task_id
|
|
|