style: format Python files for CI

This commit is contained in:
Xiaoxia AI
2026-06-24 19:54:36 +08:00
parent 1c883ec704
commit f85e389ced
19 changed files with 583 additions and 354 deletions
+47 -13
View File
@@ -42,10 +42,15 @@ def _build_diagnosis(workspace_id: str, project_id: str, assets: list[Asset]) ->
video_assets = [asset for asset in ready_assets if _asset_kind(asset) == AssetLibraryKind.VIDEO]
image_assets = [asset for asset in ready_assets if _asset_kind(asset) == AssetLibraryKind.IMAGE]
voice_assets = [asset for asset in ready_assets if _asset_kind(asset) == AssetLibraryKind.VOICE]
problem_assets = [asset for asset in assets if asset.status in {AssetStatus.ERROR, AssetStatus.UPLOADING, AssetStatus.PROCESSING}]
unclassified_assets = [asset for asset in ready_assets if asset.classification_status.value in {"pending", "failed"}]
problem_assets = [
asset for asset in assets if asset.status in {AssetStatus.ERROR, AssetStatus.UPLOADING, AssetStatus.PROCESSING}
]
unclassified_assets = [
asset for asset in ready_assets if asset.classification_status.value in {"pending", "failed"}
]
risky_assets = [
asset for asset in ready_assets
asset
for asset in ready_assets
if (asset.quality_score is not None and asset.quality_score < 60)
or asset.metadata.get("review_status") == "rejected"
or asset.status == AssetStatus.ERROR
@@ -54,7 +59,9 @@ def _build_diagnosis(workspace_id: str, project_id: str, assets: list[Asset]) ->
unused_assets = [asset for asset in ready_assets if int(asset.metadata.get("generation_use_count") or 0) == 0]
pending_review_assets = [asset for asset in ready_assets if asset.metadata.get("review_status") == "pending_review"]
total_duration = round(sum(float(asset.duration or 0) for asset in video_assets), 2)
estimated_video_count = max(0, min(len(video_assets), int(total_duration // 5) if total_duration else len(video_assets)))
estimated_video_count = max(
0, min(len(video_assets), int(total_duration // 5) if total_duration else len(video_assets))
)
score = 20
if video_assets:
@@ -128,15 +135,43 @@ def _build_diagnosis(workspace_id: str, project_id: str, assets: list[Asset]) ->
)
smart_views = [
AssetSmartViewItem(key="recommended", label="推荐素材", count=len(video_assets), description="已导入完成、可参与生成的视频素材"),
AssetSmartViewItem(key="needs_attention", label="慎用素材", count=len(problem_assets) + len(risky_assets), description="导入未完成、失败或质量分偏低的素材"),
AssetSmartViewItem(key="high_risk", label="高风险素材", count=len(risky_assets), description="质量分偏低或复核拒绝的素材"),
AssetSmartViewItem(key="unclassified", label="未分类素材", count=len(unclassified_assets), description="尚未完成分类或分类失败的 ready 素材"),
AssetSmartViewItem(key="recent", label="最近上传", count=min(len(assets), 10), description="最近进入素材库的素材,可用于快速复核"),
AssetSmartViewItem(key="unused", label="未使用素材", count=len(unused_assets), description="尚未参与生成的 ready 素材"),
AssetSmartViewItem(
key="recommended", label="推荐素材", count=len(video_assets), description="已导入完成、可参与生成的视频素材"
),
AssetSmartViewItem(
key="needs_attention",
label="慎用素材",
count=len(problem_assets) + len(risky_assets),
description="导入未完成、失败或质量分偏低的素材",
),
AssetSmartViewItem(
key="high_risk", label="高风险素材", count=len(risky_assets), description="质量分偏低或复核拒绝的素材"
),
AssetSmartViewItem(
key="unclassified",
label="未分类素材",
count=len(unclassified_assets),
description="尚未完成分类或分类失败的 ready 素材",
),
AssetSmartViewItem(
key="recent",
label="最近上传",
count=min(len(assets), 10),
description="最近进入素材库的素材,可用于快速复核",
),
AssetSmartViewItem(
key="unused", label="未使用素材", count=len(unused_assets), description="尚未参与生成的 ready 素材"
),
AssetSmartViewItem(key="used", label="已使用素材", count=len(used_assets), description="已经参与过生成的素材"),
AssetSmartViewItem(key="pending_review", label="待复核素材", count=len(pending_review_assets), description="生成后待人工复核的素材"),
AssetSmartViewItem(key="voice", label="配音素材", count=len(voice_assets), description="可用于后续配音/旁白工作流的素材"),
AssetSmartViewItem(
key="pending_review",
label="待复核素材",
count=len(pending_review_assets),
description="生成后待人工复核的素材",
),
AssetSmartViewItem(
key="voice", label="配音素材", count=len(voice_assets), description="可用于后续配音/旁白工作流的素材"
),
]
return ProjectAssetDiagnosisResponse(
@@ -179,4 +214,3 @@ def get_project_asset_diagnosis(
assets.extend(asset_repository.list_by_library(library.id))
return _build_diagnosis(project.workspace_id, project_id, assets)
+60 -22
View File
@@ -3,7 +3,12 @@ from uuid import uuid4
from app.api.routes.permissions import require_workspace_member
from app.auth import AuthenticatedUser, get_current_user
from app.dependencies import get_asset_repository, get_db_session, get_project_repository, get_workspace_member_repository
from app.dependencies import (
get_asset_repository,
get_db_session,
get_project_repository,
get_workspace_member_repository,
)
from app.schemas.edit_plan import CreateEditPlanRequest, EditPlanClipResponse, EditPlanResponse, EditTemplateResponse
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
@@ -24,10 +29,15 @@ def _ensure_project(project_id: str, workspace_id: str, user: AuthenticatedUser,
def _default_template(session: Session, workspace_id: str, project_id: str, user_id: str) -> EditTemplateModel:
template = session.query(EditTemplateModel).filter(
EditTemplateModel.project_id == project_id,
EditTemplateModel.is_active.is_(True),
).order_by(EditTemplateModel.created_at.asc()).first()
template = (
session.query(EditTemplateModel)
.filter(
EditTemplateModel.project_id == project_id,
EditTemplateModel.is_active.is_(True),
)
.order_by(EditTemplateModel.created_at.asc())
.first()
)
if template is not None:
return template
template = EditTemplateModel(
@@ -59,7 +69,9 @@ def _to_template_response(template: EditTemplateModel) -> EditTemplateResponse:
)
def _to_plan_response(plan: EditPlanModel, clips: list[EditPlanClipModel], asset_names: dict[str, str]) -> EditPlanResponse:
def _to_plan_response(
plan: EditPlanModel, clips: list[EditPlanClipModel], asset_names: dict[str, str]
) -> EditPlanResponse:
return EditPlanResponse(
id=plan.id,
workspace_id=plan.workspace_id,
@@ -69,15 +81,18 @@ def _to_plan_response(plan: EditPlanModel, clips: list[EditPlanClipModel], asset
title_id=plan.title_id,
status=plan.status,
summary=plan.summary,
clips=[EditPlanClipResponse(
id=clip.id,
asset_id=clip.asset_id,
asset_name=asset_names.get(clip.asset_id, clip.asset_id),
sequence=clip.sequence,
start_time=float(clip.start_time or 0),
duration=float(clip.duration or 0),
reason=clip.reason,
) for clip in clips],
clips=[
EditPlanClipResponse(
id=clip.id,
asset_id=clip.asset_id,
asset_name=asset_names.get(clip.asset_id, clip.asset_id),
sequence=clip.sequence,
start_time=float(clip.start_time or 0),
duration=float(clip.duration or 0),
reason=clip.reason,
)
for clip in clips
],
created_at=plan.created_at,
updated_at=plan.updated_at,
)
@@ -94,7 +109,11 @@ def list_edit_templates(
) -> list[EditTemplateResponse]:
_ensure_project(project_id, workspace_id, authenticated_user, project_repository, workspace_member_repository)
template = _default_template(session, workspace_id, project_id, authenticated_user.user.id)
templates = session.query(EditTemplateModel).filter(EditTemplateModel.project_id == project_id, EditTemplateModel.is_active.is_(True)).all()
templates = (
session.query(EditTemplateModel)
.filter(EditTemplateModel.project_id == project_id, EditTemplateModel.is_active.is_(True))
.all()
)
return [_to_template_response(item) for item in templates or [template]]
@@ -108,14 +127,26 @@ def create_edit_plan(
workspace_member_repository: WorkspaceMemberRepository = Depends(get_workspace_member_repository),
session: Session = Depends(get_db_session),
) -> EditPlanResponse:
_ensure_project(project_id, request.workspace_id, authenticated_user, project_repository, workspace_member_repository)
template = session.query(EditTemplateModel).filter(EditTemplateModel.id == request.template_id).first() if request.template_id else None
_ensure_project(
project_id, request.workspace_id, authenticated_user, project_repository, workspace_member_repository
)
template = (
session.query(EditTemplateModel).filter(EditTemplateModel.id == request.template_id).first()
if request.template_id
else None
)
if template is None:
template = _default_template(session, request.workspace_id, project_id, authenticated_user.user.id)
assets = [asset for asset in asset_repository.list_by_library(request.asset_library_id) if asset.status == AssetStatus.READY and asset.mime_type.startswith("video/")]
assets = [
asset
for asset in asset_repository.list_by_library(request.asset_library_id)
if asset.status == AssetStatus.READY and asset.mime_type.startswith("video/")
]
if not assets:
raise HTTPException(status_code=422, detail="素材库暂无可用于剪辑计划的视频素材")
selected = sorted(assets, key=lambda asset: (-(asset.quality_score or 0), asset.created_at))[: max(1, int(template.clip_count or 3))]
selected = sorted(assets, key=lambda asset: (-(asset.quality_score or 0), asset.created_at))[
: max(1, int(template.clip_count or 3))
]
plan = EditPlanModel(
id=uuid4().hex,
workspace_id=request.workspace_id,
@@ -157,10 +188,17 @@ def get_edit_plan(
workspace_member_repository: WorkspaceMemberRepository = Depends(get_workspace_member_repository),
session: Session = Depends(get_db_session),
) -> EditPlanResponse:
plan = session.query(EditPlanModel).filter(EditPlanModel.id == plan_id, EditPlanModel.project_id == project_id).first()
plan = (
session.query(EditPlanModel).filter(EditPlanModel.id == plan_id, EditPlanModel.project_id == project_id).first()
)
if plan is None:
raise HTTPException(status_code=404, detail="Edit plan not found")
_ensure_project(project_id, plan.workspace_id, authenticated_user, project_repository, workspace_member_repository)
clips = session.query(EditPlanClipModel).filter(EditPlanClipModel.edit_plan_id == plan.id).order_by(EditPlanClipModel.sequence.asc()).all()
clips = (
session.query(EditPlanClipModel)
.filter(EditPlanClipModel.edit_plan_id == plan.id)
.order_by(EditPlanClipModel.sequence.asc())
.all()
)
assets = asset_repository.list_by_library(plan.asset_library_id)
return _to_plan_response(plan, clips, {asset.id: asset.name for asset in assets})
+9 -3
View File
@@ -68,8 +68,7 @@ def _to_generated_video_response(item) -> GeneratedVideoResponse:
def _ensure_library_has_ready_video_assets(assets) -> None:
ready_video_assets = [
asset for asset in assets
if asset.status.value == "ready" and asset.mime_type.startswith("video")
asset for asset in assets if asset.status.value == "ready" and asset.mime_type.startswith("video")
]
if not ready_video_assets:
raise HTTPException(
@@ -82,7 +81,14 @@ def _select_title_id(project_title_repository: Any, project_id: str) -> str:
active_titles = project_title_repository.list_by_project(project_id, active_only=True)
if not active_titles:
return ""
selected = sorted(active_titles, key=lambda title: (0 if getattr(title, "favorite", False) else 1, int(title.usage_count or 0), title.created_at))[0]
selected = sorted(
active_titles,
key=lambda title: (
0 if getattr(title, "favorite", False) else 1,
int(title.usage_count or 0),
title.created_at,
),
)[0]
return selected.id
+3 -1
View File
@@ -53,7 +53,9 @@ def list_project_titles(
) -> ListProjectTitlesResponse:
project = _get_project_or_404(project_id, project_repository)
require_workspace_member(project.workspace_id, authenticated_user, workspace_member_repository)
return ListProjectTitlesResponse(items=[_to_response(item) for item in title_repository.list_by_project(project_id, active_only)])
return ListProjectTitlesResponse(
items=[_to_response(item) for item in title_repository.list_by_project(project_id, active_only)]
)
@router.post("/projects/{project_id}/titles", response_model=ProjectTitleResponse)
+77 -53
View File
@@ -12,7 +12,12 @@ from app.schemas.task_center import ListProjectTasksResponse, ProjectTaskRespons
from app.core.celery_app import celery_app
from fastapi import APIRouter, Depends, HTTPException
from packages.application import CreateGenerationTaskCommand, CreateGenerationTaskUseCase, SubmitIngestJobCommand, SubmitIngestJobUseCase
from packages.application import (
CreateGenerationTaskCommand,
CreateGenerationTaskUseCase,
SubmitIngestJobCommand,
SubmitIngestJobUseCase,
)
from packages.ports.workspace_member_repository import WorkspaceMemberRepository
router = APIRouter()
@@ -72,37 +77,41 @@ def list_project_tasks(
items: list[ProjectTaskResponse] = []
for job in ingest_job_repository.list_by_project(project_id):
items.append(ProjectTaskResponse(
id=f"ingest:{job.id}",
task_type="ingest",
workspace_id=job.workspace_id,
project_id=job.project_id,
status=job.status.value,
progress=100.0 if job.status.value == "completed" else 0.0,
current_step=_ingest_step(job),
error_message=job.error_message,
user_message=_humanize_task_error(job.error_message),
retryable=job.status.value == "failed",
source_id=job.id,
created_at=job.created_at,
updated_at=job.updated_at,
))
items.append(
ProjectTaskResponse(
id=f"ingest:{job.id}",
task_type="ingest",
workspace_id=job.workspace_id,
project_id=job.project_id,
status=job.status.value,
progress=100.0 if job.status.value == "completed" else 0.0,
current_step=_ingest_step(job),
error_message=job.error_message,
user_message=_humanize_task_error(job.error_message),
retryable=job.status.value == "failed",
source_id=job.id,
created_at=job.created_at,
updated_at=job.updated_at,
)
)
for task in generation_task_repository.list_by_project(project_id):
items.append(ProjectTaskResponse(
id=f"generation:{task.id}",
task_type="generation",
workspace_id=task.workspace_id,
project_id=task.project_id,
status=task.status.value,
progress=task.progress,
current_step=_generation_step(task),
error_message=task.error_message,
user_message=_humanize_task_error(task.error_message),
retryable=task.status.value == "failed",
source_id=task.id,
created_at=task.created_at,
updated_at=task.completed_at or task.started_at or task.created_at,
))
items.append(
ProjectTaskResponse(
id=f"generation:{task.id}",
task_type="generation",
workspace_id=task.workspace_id,
project_id=task.project_id,
status=task.status.value,
progress=task.progress,
current_step=_generation_step(task),
error_message=task.error_message,
user_message=_humanize_task_error(task.error_message),
retryable=task.status.value == "failed",
source_id=task.id,
created_at=task.created_at,
updated_at=task.completed_at or task.started_at or task.created_at,
)
)
items.sort(key=lambda item: item.updated_at or item.created_at or "", reverse=True)
return ListProjectTasksResponse(items=items)
@@ -124,20 +133,28 @@ def retry_project_task(
if task.status.value != "failed":
raise HTTPException(status_code=409, detail="Only failed tasks can be retried")
use_case = CreateGenerationTaskUseCase(generation_task_repository)
retried = use_case.execute(CreateGenerationTaskCommand(
workspace_id=task.workspace_id,
project_id=task.project_id,
asset_library_id=task.asset_library_id,
strategy_id=task.strategy_id,
voice_library_id=task.voice_library_id,
edit_plan_id=task.edit_plan_id,
created_by_user_id=authenticated_user.user.id,
))
retried = use_case.execute(
CreateGenerationTaskCommand(
workspace_id=task.workspace_id,
project_id=task.project_id,
asset_library_id=task.asset_library_id,
strategy_id=task.strategy_id,
voice_library_id=task.voice_library_id,
edit_plan_id=task.edit_plan_id,
created_by_user_id=authenticated_user.user.id,
)
)
celery_app.send_task("worker.generate_video", args=[retried.id])
return ProjectTaskResponse(
id=f"generation:{retried.id}", task_type="generation", workspace_id=retried.workspace_id,
project_id=retried.project_id, status=retried.status.value, progress=retried.progress,
current_step=_generation_step(retried), source_id=retried.id, created_at=retried.created_at,
id=f"generation:{retried.id}",
task_type="generation",
workspace_id=retried.workspace_id,
project_id=retried.project_id,
status=retried.status.value,
progress=retried.progress,
current_step=_generation_step(retried),
source_id=retried.id,
created_at=retried.created_at,
updated_at=retried.created_at,
)
if task_type == "ingest":
@@ -148,18 +165,25 @@ def retry_project_task(
if job.status.value != "failed":
raise HTTPException(status_code=409, detail="Only failed tasks can be retried")
use_case = SubmitIngestJobUseCase(ingest_job_repository)
retried = use_case.execute(SubmitIngestJobCommand(
workspace_id=job.workspace_id,
project_id=job.project_id,
library_id=job.library_id,
storage_key=job.storage_key,
))
retried = use_case.execute(
SubmitIngestJobCommand(
workspace_id=job.workspace_id,
project_id=job.project_id,
library_id=job.library_id,
storage_key=job.storage_key,
)
)
celery_app.send_task("worker.ingest_asset", args=[retried.id])
return ProjectTaskResponse(
id=f"ingest:{retried.id}", task_type="ingest", workspace_id=retried.workspace_id,
project_id=retried.project_id, status=retried.status.value, progress=0,
current_step=_ingest_step(retried), source_id=retried.id, created_at=retried.created_at,
id=f"ingest:{retried.id}",
task_type="ingest",
workspace_id=retried.workspace_id,
project_id=retried.project_id,
status=retried.status.value,
progress=0,
current_step=_ingest_step(retried),
source_id=retried.id,
created_at=retried.created_at,
updated_at=retried.updated_at,
)
raise HTTPException(status_code=400, detail="Unsupported task type")