后端生成闸+多项修复;前端全站更新;QA 审计与报告

后端:
- 新增 celery_health 生成前置闸——无 worker 在线时图片/视频生成入口
  一律 503,防"提交到 ARK 后无人轮询、结果悬空+额度冻结"的数据丢失
- 拼接导出:帧率跟随源众数、字幕逐句重映射、原声/BGM 混音修复
- 资金核算审计脚本 + genesis 账目回填 + 滞留预留清理命令
- 接入 yunqi provider 与豆包 TTS 模型(catalog/migrations/bootstrap 命令)

前端:全站页面更新(pipeline/library/products/projects/team/account 等),
新增共享 pager 分页组件

QA:刷新 function-audit 全量输出,新增 full-qa 报告
文档:BP 产品介绍资料、design/CLAUDE.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-15 10:06:15 +08:00
co-authored by Claude Fable 5
parent 890cb9ab67
commit 216a711291
92 changed files with 5114 additions and 2027 deletions
@@ -0,0 +1,63 @@
"""释放僵尸预留额度。
生成流程中断(进程被杀/浏览器关闭/轮询停止)会留下 status=ACTIVE 的 CreditReservation,
对应 AITask 永远停在 reserved/submitted/polling——这些预留会一直占用 reserved_balance,
吃掉团队可用额度。本命令把「超过 --hours 小时仍未终态」的预留按正规 release 流程释放
(走 release_credit,有 RELEASE 流水可审计),并把对应任务标记为 FAILED。
用法:
python manage.py sweep_stale_reservations # 默认释放超过 12 小时的
python manage.py sweep_stale_reservations --hours 1 # 收紧窗口
python manage.py sweep_stale_reservations --dry-run # 只看不动
"""
from datetime import timedelta
from django.core.management.base import BaseCommand
from django.utils import timezone
from apps.ai.models import AITask
from apps.billing.models import CreditReservation
from apps.billing.services.ledger import release_credit
# 这些任务状态意味着流程没走到终态(成功会 charge、失败会 release),超时即视为僵尸
STUCK_TASK_STATUSES = (
AITask.Status.CREATED,
AITask.Status.RESERVED,
AITask.Status.SUBMITTED,
AITask.Status.POLLING,
)
class Command(BaseCommand):
help = "释放超时未终态的 ACTIVE 预留额度(僵尸预留),并把对应任务标记为失败"
def add_arguments(self, parser):
parser.add_argument("--hours", type=int, default=12, help="超过多少小时视为僵尸(默认 12)")
parser.add_argument("--dry-run", action="store_true", help="只列出将要释放的,不实际执行")
def handle(self, *args, **options):
cutoff = timezone.now() - timedelta(hours=options["hours"])
stale = (
CreditReservation.objects.filter(status=CreditReservation.Status.ACTIVE, created_at__lt=cutoff)
.select_related("task")
.order_by("created_at")
)
released = 0
for reservation in stale:
task = reservation.task
if task is not None and task.status not in STUCK_TASK_STATUSES:
# 任务已终态却仍 ACTIVE:同样是泄漏,照常释放
pass
label = f"resv {str(reservation.id)[:8]} ¥{reservation.amount} task={str(task.id)[:8] if task else '-'}({task.status if task else '-'}) created={reservation.created_at:%m-%d %H:%M}"
if options["dry_run"]:
self.stdout.write(f"[dry-run] would release {label}")
continue
release_credit(reservation=reservation, reason=f"僵尸预留清扫(>{options['hours']}h 未终态)")
if task is not None and task.status in STUCK_TASK_STATUSES:
task.status = AITask.Status.FAILED
task.error_message = "stale task swept: 流程中断超时,预留已释放"
task.completed_at = timezone.now()
task.save(update_fields=["status", "error_message", "completed_at", "updated_at"])
released += 1
self.stdout.write(f"released {label}")
self.stdout.write(self.style.SUCCESS(f"done: released {released} stale reservation(s)"))