1
This commit is contained in:
+180
-152
@@ -45,6 +45,10 @@ from app.schemas.industry import IndustryConfigCreate, IndustryConfigOut
|
||||
from app.schemas.video_engine import VideoEngineCreate, VideoEngineOut
|
||||
from app.schemas.image_engine import ImageEngineCreate, ImageEngineOut
|
||||
from app.schemas.credit_ratio import CreditRatioCreate, CreditRatioOut
|
||||
from app.services.generation.pipeline.db_lock_service import (
|
||||
DatabaseRowLockBusy,
|
||||
execute_with_lock_timeout,
|
||||
)
|
||||
from app.services.credits import add_credits, deduct_credits
|
||||
from app.services.credit_record_meta_service import build_admin_adjust_meta
|
||||
from app.services.admin_credit_record_service import list_admin_credit_records
|
||||
@@ -59,7 +63,7 @@ from app.services.team_service import batch_get_team_name_map, set_frontend_user
|
||||
|
||||
from app.services.generation.billing_service import (
|
||||
OWNER_GENERATION_RECORD,
|
||||
charge_generation_media_by_params,
|
||||
charge_generation_media_for_record,
|
||||
get_next_credit_attempt_no,
|
||||
)
|
||||
from app.services.generation.refund_service import mark_generation_record_failed_and_refund_once
|
||||
@@ -545,7 +549,7 @@ async def list_admin_notifications(
|
||||
if user_id:
|
||||
query = query.where(Notification.user_id == user_id)
|
||||
count_query = count_query.where(Notification.user_id == user_id)
|
||||
|
||||
|
||||
if is_read is not None:
|
||||
query = query.where(Notification.is_read == is_read)
|
||||
count_query = count_query.where(Notification.is_read == is_read)
|
||||
@@ -749,11 +753,11 @@ async def get_payment_stats(
|
||||
now_cst = datetime.now(CST)
|
||||
today_start = now_cst.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
today_end = today_start + timedelta(days=1)
|
||||
|
||||
|
||||
# Default to today if no date range provided
|
||||
query_start = today_start
|
||||
query_end = today_end
|
||||
|
||||
|
||||
if start_date:
|
||||
query_start = datetime.fromisoformat(start_date).replace(tzinfo=CST)
|
||||
if end_date:
|
||||
@@ -806,7 +810,7 @@ async def get_payment_stats(
|
||||
# Monthly cumulative stats
|
||||
month_start = now_cst.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
month_end = (month_start + timedelta(days=32)).replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
|
||||
month_result = await db.execute(
|
||||
select(
|
||||
func.count().label("paid_count"),
|
||||
@@ -1117,11 +1121,15 @@ async def delete_industry_config(
|
||||
|
||||
@router.get("/video-engines", response_model=list[VideoEngineOut])
|
||||
async def list_video_engines(
|
||||
include_deleted: bool = Query(False),
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
query = select(VideoEngine)
|
||||
if not include_deleted:
|
||||
query = query.where(VideoEngine.deleted_at.is_(None))
|
||||
result = await db.execute(
|
||||
select(VideoEngine).order_by(VideoEngine.priority.desc())
|
||||
query.order_by(VideoEngine.priority.desc(), VideoEngine.id.desc())
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
@@ -1161,7 +1169,7 @@ async def update_video_engine(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(VideoEngine).where(VideoEngine.id == engine_id).limit(1)
|
||||
select(VideoEngine).where(VideoEngine.id == engine_id, VideoEngine.deleted_at.is_(None)).limit(1)
|
||||
)
|
||||
engine = result.scalar_one_or_none()
|
||||
if not engine:
|
||||
@@ -1194,24 +1202,26 @@ async def delete_video_engine(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(VideoEngine).where(VideoEngine.id == engine_id).limit(1)
|
||||
select(VideoEngine).where(VideoEngine.id == engine_id, VideoEngine.deleted_at.is_(None)).limit(1)
|
||||
)
|
||||
engine = result.scalar_one_or_none()
|
||||
if not engine:
|
||||
raise HTTPException(status_code=404, detail="视频引擎不存在")
|
||||
await db.delete(engine)
|
||||
engine_name = engine.name
|
||||
engine.deleted_at = datetime.now(timezone.utc)
|
||||
engine.is_active = False
|
||||
await db.flush()
|
||||
await log_operation(
|
||||
db,
|
||||
admin.id,
|
||||
admin.username,
|
||||
f"删除视频引擎: {engine.name}",
|
||||
f"软删除视频引擎: {engine_name}",
|
||||
"DELETE",
|
||||
f"/admin/video-engines/{engine_id}",
|
||||
detail=json.dumps(
|
||||
{
|
||||
"engine_id": engine_id,
|
||||
"name": engine.name,
|
||||
"name": engine_name,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
@@ -1223,11 +1233,15 @@ async def delete_video_engine(
|
||||
|
||||
@router.get("/image-engines", response_model=list[ImageEngineOut])
|
||||
async def list_image_engines(
|
||||
include_deleted: bool = Query(False),
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
query = select(ImageEngine)
|
||||
if not include_deleted:
|
||||
query = query.where(ImageEngine.deleted_at.is_(None))
|
||||
result = await db.execute(
|
||||
select(ImageEngine).order_by(ImageEngine.priority.desc())
|
||||
query.order_by(ImageEngine.priority.desc(), ImageEngine.id.desc())
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
@@ -1267,7 +1281,7 @@ async def update_image_engine(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(ImageEngine).where(ImageEngine.id == engine_id).limit(1)
|
||||
select(ImageEngine).where(ImageEngine.id == engine_id, ImageEngine.deleted_at.is_(None)).limit(1)
|
||||
)
|
||||
engine = result.scalar_one_or_none()
|
||||
if not engine:
|
||||
@@ -1300,24 +1314,26 @@ async def delete_image_engine(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(
|
||||
select(ImageEngine).where(ImageEngine.id == engine_id).limit(1)
|
||||
select(ImageEngine).where(ImageEngine.id == engine_id, ImageEngine.deleted_at.is_(None)).limit(1)
|
||||
)
|
||||
engine = result.scalar_one_or_none()
|
||||
if not engine:
|
||||
raise HTTPException(status_code=404, detail="图片引擎不存在")
|
||||
await db.delete(engine)
|
||||
engine_name = engine.name
|
||||
engine.deleted_at = datetime.now(timezone.utc)
|
||||
engine.is_active = False
|
||||
await db.flush()
|
||||
await log_operation(
|
||||
db,
|
||||
admin.id,
|
||||
admin.username,
|
||||
f"删除图片引擎: {engine.name}",
|
||||
f"软删除图片引擎: {engine_name}",
|
||||
"DELETE",
|
||||
f"/admin/image-engines/{engine_id}",
|
||||
detail=json.dumps(
|
||||
{
|
||||
"engine_id": engine_id,
|
||||
"name": engine.name,
|
||||
"name": engine_name,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
@@ -1342,7 +1358,7 @@ async def _validate_credit_ratio_engine(db: AsyncSession, req: CreditRatioCreate
|
||||
raise HTTPException(status_code=400, detail="model_config_id 不能为空,当前字段用于保存图片/视频引擎ID")
|
||||
|
||||
model = ImageEngine if gen_type == "image" else VideoEngine
|
||||
result = await db.execute(select(model).where(model.id == engine_id).limit(1))
|
||||
result = await db.execute(select(model).where(model.id == engine_id, model.deleted_at.is_(None)).limit(1))
|
||||
engine = result.scalar_one_or_none()
|
||||
if not engine:
|
||||
detail = "图片积分规则绑定的图片引擎不存在" if gen_type == "image" else "视频积分规则绑定的视频引擎不存在"
|
||||
@@ -1488,10 +1504,14 @@ async def list_credit_ratios_grouped(
|
||||
|
||||
@router.get("/model-configs", response_model=list[ModelConfigOut])
|
||||
async def list_model_configs(
|
||||
include_deleted: bool = Query(False),
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(ModelConfig).order_by(ModelConfig.priority.desc()))
|
||||
query = select(ModelConfig)
|
||||
if not include_deleted:
|
||||
query = query.where(ModelConfig.deleted_at.is_(None))
|
||||
result = await db.execute(query.order_by(ModelConfig.priority.desc(), ModelConfig.id.desc()))
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
@@ -1529,7 +1549,7 @@ async def update_model_config(
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(ModelConfig).where(ModelConfig.id == config_id).limit(1))
|
||||
result = await db.execute(select(ModelConfig).where(ModelConfig.id == config_id, ModelConfig.deleted_at.is_(None)).limit(1))
|
||||
config = result.scalar_one_or_none()
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="配置不存在")
|
||||
@@ -1560,23 +1580,25 @@ async def delete_model_config(
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
result = await db.execute(select(ModelConfig).where(ModelConfig.id == config_id).limit(1))
|
||||
result = await db.execute(select(ModelConfig).where(ModelConfig.id == config_id, ModelConfig.deleted_at.is_(None)).limit(1))
|
||||
config = result.scalar_one_or_none()
|
||||
if not config:
|
||||
raise HTTPException(status_code=404, detail="配置不存在")
|
||||
await db.delete(config)
|
||||
config_name = config.name
|
||||
config.deleted_at = datetime.now(timezone.utc)
|
||||
config.is_active = False
|
||||
await db.flush()
|
||||
await log_operation(
|
||||
db,
|
||||
admin.id,
|
||||
admin.username,
|
||||
f"删除模型配置: {config.name}",
|
||||
f"软删除模型配置: {config_name}",
|
||||
"DELETE",
|
||||
f"/admin/model-configs/{config_id}",
|
||||
detail=json.dumps(
|
||||
{
|
||||
"config_id": config_id,
|
||||
"name": config.name,
|
||||
"name": config_name,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
@@ -1729,14 +1751,14 @@ async def get_stats(
|
||||
Project.created_at <= date_end,
|
||||
)
|
||||
)).scalar() or 0
|
||||
|
||||
|
||||
total_generations = (await db.execute(
|
||||
select(func.count(ChatGenerationTask.id)).where(
|
||||
ChatGenerationTask.created_at >= date_start,
|
||||
ChatGenerationTask.created_at <= date_end,
|
||||
)
|
||||
)).scalar() or 0
|
||||
|
||||
|
||||
total_records = (await db.execute(
|
||||
select(func.count(GenerationRecord.id)).where(
|
||||
GenerationRecord.deleted_at.is_(None),
|
||||
@@ -1744,7 +1766,7 @@ async def get_stats(
|
||||
GenerationRecord.created_at <= date_end,
|
||||
)
|
||||
)).scalar() or 0
|
||||
|
||||
|
||||
total_revenue = (await db.execute(
|
||||
select(func.coalesce(func.sum(PaymentOrder.amount), 0)).where(
|
||||
PaymentOrder.status == "paid",
|
||||
@@ -1780,7 +1802,7 @@ async def get_stats(
|
||||
)).scalar() or 0
|
||||
|
||||
period_duration = date_end - date_start
|
||||
|
||||
|
||||
last_period_start = date_start - period_duration
|
||||
last_period_end = date_start
|
||||
|
||||
@@ -1982,87 +2004,156 @@ async def admin_update_generation_status(
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Admin update generation record status (e.g., confirm/reject)."""
|
||||
result = await db.execute(
|
||||
select(GenerationRecord).where(
|
||||
GenerationRecord.id == record_id,
|
||||
GenerationRecord.deleted_at.is_(None),
|
||||
"""管理员只能终止正在执行或待生成的记录,禁止绕过流水线裸改生成/完成状态。"""
|
||||
try:
|
||||
result = await execute_with_lock_timeout(
|
||||
db,
|
||||
select(GenerationRecord).where(
|
||||
GenerationRecord.id == record_id,
|
||||
GenerationRecord.deleted_at.is_(None),
|
||||
)
|
||||
.with_for_update()
|
||||
.limit(1),
|
||||
)
|
||||
.with_for_update()
|
||||
.limit(1)
|
||||
)
|
||||
except DatabaseRowLockBusy as exc:
|
||||
raise HTTPException(status_code=409, detail=exc.detail) from exc
|
||||
record = result.scalar_one_or_none()
|
||||
if not record:
|
||||
raise HTTPException(status_code=404, detail="记录不存在")
|
||||
|
||||
new_status = body.get("status")
|
||||
if new_status not in ("prompt_optimized", "generating", "completed", "failed"):
|
||||
raise HTTPException(status_code=400, detail="无效状态")
|
||||
|
||||
if new_status == "failed":
|
||||
await mark_generation_record_failed_and_refund_once(
|
||||
db,
|
||||
record=record,
|
||||
error_message=body.get("error_message") or record.error_message or "管理员设置为失败",
|
||||
new_status = str(body.get("status") or "").strip()
|
||||
if new_status in {"generating", "completed", "prompt_optimized"}:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="禁止直接修改为该状态;生成请调用生成接口,完成必须由下载/超分流水线落库",
|
||||
)
|
||||
else:
|
||||
record.status = new_status
|
||||
if new_status != "failed":
|
||||
raise HTTPException(status_code=400, detail="该接口仅允许管理员终止任务")
|
||||
if record.status == "completed":
|
||||
raise HTTPException(status_code=409, detail="已完成记录不能直接改为失败")
|
||||
|
||||
error_message = body.get("error_message") or record.error_message or "管理员终止生成任务"
|
||||
await mark_generation_record_failed_and_refund_once(
|
||||
db,
|
||||
record=record,
|
||||
error_message=error_message,
|
||||
generation_attempt_no=int(record.generation_attempt_no or 1),
|
||||
)
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.FAILED.value
|
||||
record.provider_create_claim_token = None
|
||||
record.provider_create_lease_until = None
|
||||
record.poll_claim_token = None
|
||||
record.poll_lease_until = None
|
||||
record.next_poll_at = None
|
||||
record.download_claim_token = None
|
||||
record.download_lease_until = None
|
||||
record.download_next_retry_at = None
|
||||
|
||||
# 若任务已进入超分,必须同时撤销超分数据库租约;执行中的超分 Worker
|
||||
# 在回填前校验 lease_token,发现 token 被清除后会中止,不得覆盖管理员终止状态。
|
||||
from app.enums.video_upscale import VideoUpscaleStage, VideoUpscaleTaskStatus
|
||||
from app.models.video_upscale_task import VideoUpscaleTask
|
||||
|
||||
try:
|
||||
upscale_result = await execute_with_lock_timeout(
|
||||
db,
|
||||
select(VideoUpscaleTask)
|
||||
.where(VideoUpscaleTask.generation_record_id == record.id)
|
||||
.with_for_update()
|
||||
.limit(1),
|
||||
)
|
||||
except DatabaseRowLockBusy as exc:
|
||||
raise HTTPException(status_code=409, detail=exc.detail) from exc
|
||||
upscale = upscale_result.scalar_one_or_none()
|
||||
if upscale and upscale.status not in {
|
||||
VideoUpscaleTaskStatus.COMPLETED.value,
|
||||
VideoUpscaleTaskStatus.FAILED.value,
|
||||
}:
|
||||
upscale.status = VideoUpscaleTaskStatus.FAILED.value
|
||||
upscale.stage = VideoUpscaleStage.FAILED.value
|
||||
upscale.last_error = error_message
|
||||
upscale.failed_at = datetime.now(CST)
|
||||
upscale.next_retry_at = None
|
||||
upscale.lease_token = None
|
||||
upscale.lease_until = None
|
||||
|
||||
if body.get("video_url"):
|
||||
record.video_url = body["video_url"]
|
||||
if body.get("video_cover_url"):
|
||||
record.video_cover_url = body["video_cover_url"]
|
||||
if body.get("image_url"):
|
||||
record.image_url = body["image_url"]
|
||||
if new_status == "completed":
|
||||
record.generated_at = datetime.now(CST)
|
||||
await db.flush()
|
||||
await log_operation(
|
||||
db,
|
||||
admin.id,
|
||||
admin.username,
|
||||
f"更新生成记录状态: {new_status}",
|
||||
"管理员终止生成记录",
|
||||
"PUT",
|
||||
f"/admin/generation-records/{record_id}/status",
|
||||
detail=json.dumps(
|
||||
{
|
||||
"record_id": record_id,
|
||||
"new_status": new_status,
|
||||
"generation_attempt_no": int(record.generation_attempt_no or 1),
|
||||
"error_message": error_message,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
# Redis 注册表只做调度加速;删除失败不回滚已提交的业务终止状态。
|
||||
try:
|
||||
from app.services.celery_download_recovery_service import remove_download_active
|
||||
from app.services.generation.pipeline.owner_service import redis_owner_item_id
|
||||
from app.services.redis_registry_service import redis_remove_registry_item
|
||||
from app.config import settings
|
||||
|
||||
registry_id = redis_owner_item_id(
|
||||
"generation_record",
|
||||
record_id,
|
||||
int(record.generation_attempt_no or 1),
|
||||
)
|
||||
await remove_download_active(registry_id)
|
||||
await redis_remove_registry_item(
|
||||
hash_key=settings.POLL_ACTIVE_REDIS_HASH_KEY,
|
||||
zset_key=settings.POLL_ACTIVE_REDIS_ZSET_KEY,
|
||||
item_id=registry_id,
|
||||
log_context="admin_generation_record_terminate",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return {"message": "ok"}
|
||||
|
||||
|
||||
@router.post("/generation-records/{record_id}/generate")
|
||||
async def admin_generate_video(
|
||||
async def admin_generate_record_resource(
|
||||
record_id: str,
|
||||
body: dict,
|
||||
admin: User = Depends(get_admin_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Admin trigger video/image generation for a record with specified params."""
|
||||
from app.models.project import Project
|
||||
from app.services.video_queue import task_queue
|
||||
|
||||
result = await db.execute(
|
||||
select(GenerationRecord, Project.name)
|
||||
.join(Project, GenerationRecord.project_id == Project.id)
|
||||
.where(
|
||||
GenerationRecord.id == record_id,
|
||||
GenerationRecord.deleted_at.is_(None),
|
||||
Project.deleted_at.is_(None),
|
||||
)
|
||||
.with_for_update()
|
||||
"""管理员触发 GenerationRecord 图片或视频资源生成。"""
|
||||
from app.services.generation.pipeline.generation_record_service import (
|
||||
commit_and_enqueue_generation_record,
|
||||
prepare_generation_record_execution,
|
||||
)
|
||||
|
||||
try:
|
||||
result = await execute_with_lock_timeout(
|
||||
db,
|
||||
select(GenerationRecord, Project.name)
|
||||
.join(Project, GenerationRecord.project_id == Project.id)
|
||||
.where(
|
||||
GenerationRecord.id == record_id,
|
||||
GenerationRecord.deleted_at.is_(None),
|
||||
Project.deleted_at.is_(None),
|
||||
)
|
||||
.with_for_update(),
|
||||
)
|
||||
except DatabaseRowLockBusy as exc:
|
||||
raise HTTPException(status_code=409, detail=exc.detail) from exc
|
||||
row = result.first()
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="记录不存在")
|
||||
|
||||
record, project_name = row
|
||||
type_str = "视频" if record.gen_type == GenerationType.video else "图片"
|
||||
|
||||
if record.status not in ("prompt_optimized", "failed"):
|
||||
raise HTTPException(status_code=400, detail=f"当前状态不允许生成{type_str}")
|
||||
if record.pipeline_stage == GenerationRecordPipelineStage.UPSCALE_FAILED.value:
|
||||
@@ -2075,7 +2166,6 @@ async def admin_generate_video(
|
||||
)
|
||||
|
||||
if record.gen_type == GenerationType.video:
|
||||
# Video Generation
|
||||
aspect_ratio = body.get("aspect_ratio", "16:9")
|
||||
resolution = body.get("resolution", "720p")
|
||||
if aspect_ratio not in ASPECT_RATIOS:
|
||||
@@ -2097,97 +2187,34 @@ async def admin_generate_video(
|
||||
aspect_ratio=aspect_ratio,
|
||||
supported_provider_resolutions=supported_provider_resolutions,
|
||||
)
|
||||
|
||||
duration = record.duration or 5
|
||||
media_billing = await charge_generation_media_by_params(
|
||||
db,
|
||||
user_id=record.user_id,
|
||||
record_id=record.id,
|
||||
gen_type="video",
|
||||
duration=duration,
|
||||
resolution=resolution,
|
||||
project_name=project_name,
|
||||
description_prefix="视频生成(管理后台)",
|
||||
owner_type=OWNER_GENERATION_RECORD,
|
||||
attempt_no=attempt_no,
|
||||
)
|
||||
|
||||
record.aspect_ratio = aspect_ratio
|
||||
record.resolution = resolution
|
||||
record.provider_generation_resolution = provider_resolution
|
||||
record.video_upscale_enabled_snapshot = upscale_enabled
|
||||
record.video_upscale_snapshot_json = upscale_snapshot_json
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.CREATING_PROVIDER_TASK.value
|
||||
record.credits_cost = round(float(record.credits_cost or 0) + media_billing.total_charged, 2)
|
||||
record.status = "generating"
|
||||
record.error_message = None
|
||||
record.video_url = None
|
||||
record.video_cover_url = None
|
||||
record.image_url = None
|
||||
record.seedance_task_id = None
|
||||
await db.flush()
|
||||
|
||||
try:
|
||||
from app.services.video_gen import submit_video_task
|
||||
task_id = await submit_video_task(
|
||||
db,
|
||||
engine,
|
||||
record,
|
||||
include_media_references=False,
|
||||
)
|
||||
record.seedance_task_id = task_id
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.WAITING_REMOTE.value
|
||||
await db.flush()
|
||||
await task_queue.enqueue(record_id)
|
||||
except Exception as e:
|
||||
record.pipeline_stage = GenerationRecordPipelineStage.FAILED.value
|
||||
await mark_generation_record_failed_and_refund_once(
|
||||
db,
|
||||
record=record,
|
||||
error_message=str(e),
|
||||
)
|
||||
await db.flush()
|
||||
|
||||
elif record.gen_type == GenerationType.image:
|
||||
# Image generation
|
||||
from app.services.image_gen import get_active_image_engine
|
||||
|
||||
post_image_size = body.get("image_size", "")
|
||||
image_size = post_image_size or record.image_size or "2K"
|
||||
media_billing = await charge_generation_media_by_params(
|
||||
db,
|
||||
user_id=record.user_id,
|
||||
record_id=record.id,
|
||||
gen_type="image",
|
||||
image_size=image_size,
|
||||
project_name=project_name,
|
||||
description_prefix="图片生成(管理后台)",
|
||||
owner_type=OWNER_GENERATION_RECORD,
|
||||
attempt_no=attempt_no,
|
||||
)
|
||||
|
||||
record.image_size = image_size
|
||||
record.credits_cost = round(float(record.credits_cost or 0) + media_billing.total_charged, 2)
|
||||
record.status = "generating"
|
||||
record.error_message = None
|
||||
record.image_url = None
|
||||
record.video_url = None
|
||||
record.video_cover_url = None
|
||||
record.seedance_task_id = None
|
||||
engine = await get_active_image_engine(db)
|
||||
record.image_size = body.get("image_size") or record.image_size or "2K"
|
||||
record.provider_generation_resolution = None
|
||||
record.video_upscale_enabled_snapshot = False
|
||||
record.video_upscale_snapshot_json = None
|
||||
record.pipeline_stage = None
|
||||
await db.flush()
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="不支持的生成类型")
|
||||
|
||||
try:
|
||||
await task_queue.enqueue(record_id)
|
||||
except Exception as e:
|
||||
await mark_generation_record_failed_and_refund_once(
|
||||
db,
|
||||
record=record,
|
||||
error_message=str(e),
|
||||
)
|
||||
await db.flush()
|
||||
media_billing = await charge_generation_media_for_record(
|
||||
db,
|
||||
record=record,
|
||||
project_name=project_name,
|
||||
description_prefix=f"{type_str}生成(管理后台)-",
|
||||
attempt_no=attempt_no,
|
||||
engine_id=engine.id,
|
||||
)
|
||||
record.credits_cost = round(float(record.credits_cost or 0) + float(media_billing.total_charged or 0), 2)
|
||||
prepare_generation_record_execution(record, engine=engine, attempt_no=attempt_no)
|
||||
await db.flush()
|
||||
await commit_and_enqueue_generation_record(db, record, reason="generation_record_admin_generate")
|
||||
|
||||
await log_operation(
|
||||
db,
|
||||
@@ -2201,6 +2228,7 @@ async def admin_generate_video(
|
||||
"record_id": record_id,
|
||||
"gen_type": record.gen_type,
|
||||
"project_name": project_name,
|
||||
"generation_attempt_no": record.generation_attempt_no,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user