This commit is contained in:
2026-07-02 09:36:55 +08:00
47 changed files with 3381 additions and 2069 deletions
+4 -2
View File
@@ -1400,9 +1400,10 @@ async def admin_list_generation_records(
):
"""List all generation records across all users, with optional filters."""
query = (
select(GenerationRecord, User.username, Project.name)
select(GenerationRecord, User.username, Project.name, Project.industry, IndustryConfig.label)
.join(User, GenerationRecord.user_id == User.id)
.join(Project, GenerationRecord.project_id == Project.id)
.outerjoin(IndustryConfig, Project.industry == IndustryConfig.key)
.where(GenerationRecord.deleted_at.is_(None), Project.deleted_at.is_(None))
.order_by(GenerationRecord.created_at.desc())
)
@@ -1427,7 +1428,7 @@ async def admin_list_generation_records(
rows = result.all()
items = []
for record, username, project_name in rows:
for record, username, project_name, industry, industry_label in rows:
refs = None
if record.media_references:
try:
@@ -1440,6 +1441,7 @@ async def admin_list_generation_records(
"username": username,
"project_id": record.project_id,
"project_name": project_name,
"industry": industry_label or industry,
"original_prompt": record.original_prompt,
"optimized_prompt": record.optimized_prompt,
"duration": record.duration,
+21 -51
View File
@@ -48,70 +48,40 @@ async def get_credit_ratios(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
import json
async def get_engine_with_ratios(gen_type: str, engines: list):
for engine in engines:
async def get_ratios_for_engine_type(gen_type: str, engine_ids: list):
for engine_id in engine_ids:
result = await db.execute(
select(CreditRatio)
.where(CreditRatio.gen_type == gen_type)
.where(CreditRatio.model_config_id == engine.id)
.where(CreditRatio.model_config_id == engine_id)
)
ratios = result.scalars().all()
if ratios:
ratios_out = [CreditRatioOut.model_validate(r) for r in ratios]
engine_info = {
"id": engine.id,
"name": engine.name,
"provider": engine.provider,
"ratios": ratios_out,
}
if gen_type == "video":
try:
supported_ratios = json.loads(engine.supported_ratios) if engine.supported_ratios else []
except Exception:
supported_ratios = []
try:
supported_resolutions = json.loads(engine.supported_resolutions) if engine.supported_resolutions else []
except Exception:
supported_resolutions = []
try:
supported_durations = json.loads(engine.supported_durations) if engine.supported_durations else []
except Exception:
supported_durations = []
engine_info.update({
"supported_ratios": supported_ratios,
"supported_resolutions": supported_resolutions,
"supported_durations": supported_durations,
"max_duration": engine.max_duration,
"max_image_count": engine.max_image_count,
"max_video_count": engine.max_video_count,
})
return engine_info
return None
return [CreditRatioOut.model_validate(r) for r in ratios]
return []
video_engines_result = await db.execute(
select(VideoEngine)
select(VideoEngine.id)
.where(VideoEngine.is_active == True)
.order_by(VideoEngine.priority.desc())
)
video_engines = video_engines_result.scalars().all()
video_engine_ids = video_engines_result.scalars().all()
image_engines_result = await db.execute(
select(ImageEngine)
select(ImageEngine.id)
.where(ImageEngine.is_active == True)
.order_by(ImageEngine.priority.desc())
)
image_engines = image_engines_result.scalars().all()
image_engine_ids = image_engines_result.scalars().all()
grouped = {}
video_data = await get_engine_with_ratios("video", video_engines)
if video_data:
grouped["video"] = video_data
image_data = await get_engine_with_ratios("image", image_engines)
if image_data:
grouped["image"] = image_data
video_ratios = await get_ratios_for_engine_type("video", video_engine_ids)
if video_ratios:
grouped["video"] = video_ratios
image_ratios = await get_ratios_for_engine_type("image", image_engine_ids)
if image_ratios:
grouped["image"] = image_ratios
return grouped
@@ -44,5 +44,6 @@ async def list_active_engines(
"supported_models": models,
"supported_sizes": sizes,
"default_size": e.default_size,
"max_image_count": e.max_image_count,
})
return {"items": items}
+106 -5
View File
@@ -1,11 +1,18 @@
import logging
from typing import Any, Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import APIRouter, Depends, Query, Body
from app.dependencies import get_db
from app.schemas.resources_material import ResourcesMaterialListResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.models.resources_material import ResourcesMaterial
from app.models.pre_test_template import PreTestTemplate
from app.dependencies import get_db, get_current_user
from app.schemas.resources_material import ResourcesMaterialListResponse, PreTestMaterialRequest
from app.services.resources_material_service import get_resources_material_list
from app.services.pre_test_queue import pre_test_queue
from app.utils.id_gen import generate_id
router = APIRouter(prefix="/resources-material", tags=["resources-material"])
@@ -25,6 +32,7 @@ async def get_resources_material_list_api(
page: int = Query(1, description="页码"),
page_size: int = Query(20, description="每页数量"),
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
) -> Any | dict:
items, total = await get_resources_material_list(
db=db,
@@ -35,6 +43,7 @@ async def get_resources_material_list_api(
resource_type=resource_type,
page=page,
page_size=page_size,
user_id=current_user.id,
)
return {
@@ -42,4 +51,96 @@ async def get_resources_material_list_api(
"message": "查询成功",
"data": items,
"total": total,
}
}
#如果素材上传的时候没有指定前测,现在可以对已经上传好的素材进行前测
@router.post(
"/pre-commit",
summary="素材列表提交前测",
description="通过素材列表提交未前测的素材,异步处理,立即返回任务ID,结果稍后通过列表查询",
)
async def pre_test_material(
req: PreTestMaterialRequest = Body(..., description="前测请求体"),
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(get_current_user),
) -> Any | dict:
try:
invalid_ids = []
grouped_videos = {}
#检查前测模板是否有效
pre_test_template = await db.execute(
select(PreTestTemplate)
.where(
PreTestTemplate.id == req.pre_test_template_id,
PreTestTemplate.user_id == current_user.id,
PreTestTemplate.deleted_at.is_(None),
)
)
pre_test_template = pre_test_template.scalar_one_or_none()
if not pre_test_template:
return {"code": 1, "message": f"前测模板{req.pre_test_template_id}不存在或不属于当前用户"}
for resource_material_id in req.resources_material_ids:
resource_material = await db.execute(
select(ResourcesMaterial)
.where(
ResourcesMaterial.id == resource_material_id,
ResourcesMaterial.user_id == current_user.id,
ResourcesMaterial.deleted_at.is_(None),
)
)
resource_material = resource_material.scalar_one_or_none()
if not resource_material:
invalid_ids.append(f"{resource_material_id}: 资源不存在或不属于当前用户")
continue
if resource_material.status is not None:
invalid_ids.append(f"{resource_material_id}: 该资源已进行过前测")
continue
if not resource_material.upload_id:
invalid_ids.append(f"{resource_material_id}: 该资源未上传到平台")
continue
if resource_material.resource_type != "video":
invalid_ids.append(f"{resource_material_id}: 前测仅支持视频类型")
continue
key = f"{resource_material.oauth_id}|{resource_material.advertiser_id}"
if key not in grouped_videos:
grouped_videos[key] = []
grouped_videos[key].append({
"id": resource_material.id,
"upload_id": resource_material.upload_id,
})
if invalid_ids:
return {"code": 1, "message": "; ".join(invalid_ids)}
if not grouped_videos:
return {"code": 1, "message": "没有有效的视频资源"}
task_id = generate_id()
await pre_test_queue.enqueue({
"task_id": task_id,
"grouped_videos": grouped_videos,
"pre_test_template_id": req.pre_test_template_id,
})
return {
"code": 0,
"message": "任务已提交,正在处理中",
"data": {
"task_id": task_id,
"total_groups": len(grouped_videos),
},
}
except ValueError as e:
return {"code": 1, "message": str(e)}
except Exception as e:
return {"code": 1, "message": str(e)}
+11 -37
View File
@@ -10,6 +10,8 @@ from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from sqlalchemy import select, func
from app.models.generated_resource import GeneratedResource
from app.dependencies import get_current_user, get_db
from app.models.user import User
@@ -355,51 +357,25 @@ async def batch_update_filename(
.where(GeneratedResource.file_name.is_not(None))
)
result = await db.execute(query)
db_existing_names = set(row[0] for row in result.all())
name_counters = {}
existing_names = set(row[0] for row in result.all())
for item in valid_items:
file_name = item["file_name"]
resource = item["resource"]
base_name, ext = os.path.splitext(file_name)
existing_names = db_existing_names.copy()
if resource.file_name and resource.file_name in existing_names:
existing_names.remove(resource.file_name)
if file_name not in name_counters:
counter = 1
new_file_name = file_name
while new_file_name in existing_names:
new_file_name = f"{base_name}{counter}{ext}"
counter += 1
name_counters[file_name] = {
"base_name": base_name,
"ext": ext,
"counter": counter,
}
existing_names.add(new_file_name)
db_existing_names.add(new_file_name)
else:
counter = name_counters[file_name]["counter"]
base_name = name_counters[file_name]["base_name"]
ext = name_counters[file_name]["ext"]
new_file_name = f"{base_name}{counter}{ext}"
while new_file_name in existing_names:
counter += 1
new_file_name = f"{base_name}{counter}{ext}"
name_counters[file_name]["counter"] = counter + 1
existing_names.add(new_file_name)
db_existing_names.add(new_file_name)
counter = 1
new_file_name = file_name
while new_file_name in existing_names:
new_file_name = f"{base_name}_{counter}{ext}"
counter += 1
item["resource"].file_name = new_file_name
db.add(item["resource"])
existing_names.add(new_file_name)
results.append({
"source_id": item["source_id"],
@@ -421,7 +397,7 @@ async def batch_update_filename(
}
except Exception as e:
return {
"code": 0,
"code": 1,
"message": f"批量修改文件名失败:{str(e)}",
"success_count": 0,
"fail_count": 0,
@@ -461,5 +437,3 @@ async def get_upload_history(
"code": 0,
"message": f"查询上传任务历史失败:{str(e)}",
}