From 68ec5850ea62f4ff6c0b5541961601e4466d0e99 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Mon, 13 Jul 2026 21:06:32 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=E7=B4=A0=E6=9D=90=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E8=A7=86=E5=9B=BE=E7=AD=9B=E9=80=89=20+=20=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E4=BD=BF=E7=94=A8=E6=AC=A1=E6=95=B0=E9=97=AD=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 素材智能视图筛选(smart_view 参数) - 新增 list_assets 接口 smart_view 查询参数 - 支持 6 种视图:recommended / cautious / risky / unused / used / pending_review - 基于 quality_score + metadata.review_status + metadata.generation_use_count 筛选 - 与现有内存过滤路径集成,无破坏性变更 2. 标题使用次数闭环 - 标题库 repository 新增 increment_usage_count 方法 - 新增 IncrementTitleUsageUseCase - 生成任务成功后自动遍历 title_ids,逐个递增 usage_count - 异常兜底:计数失败不影响主流程,仅打 warning 日志 --- apps/api/app/api/routes/assets.py | 31 +++++++++++++++++-- apps/worker/worker_app/tasks/generation.py | 29 +++++++++++++++++ .../title_library_repository.py | 19 ++++++++++++ .../application/title_library/__init__.py | 4 +++ .../application/title_library/commands.py | 7 +++++ .../application/title_library/use_cases.py | 17 ++++++++++ 6 files changed, 104 insertions(+), 3 deletions(-) mode change 100644 => 100755 apps/api/app/api/routes/assets.py mode change 100644 => 100755 apps/worker/worker_app/tasks/generation.py mode change 100644 => 100755 packages/adapters/sqlalchemy_impl/title_library_repository.py mode change 100644 => 100755 packages/application/title_library/__init__.py mode change 100644 => 100755 packages/application/title_library/commands.py mode change 100644 => 100755 packages/application/title_library/use_cases.py diff --git a/apps/api/app/api/routes/assets.py b/apps/api/app/api/routes/assets.py old mode 100644 new mode 100755 index 0861a2cfb..40710aaaf --- a/apps/api/app/api/routes/assets.py +++ b/apps/api/app/api/routes/assets.py @@ -82,6 +82,11 @@ def list_assets( gender: Optional[str] = Query(None, description="按 metadata.gender 筛选"), style: Optional[str] = Query(None, description="按 metadata.style 筛选"), tag_ids: Optional[str] = Query(None, description="按标签 ID 筛选(逗号分隔,取交集)"), + smart_view: Optional[str] = Query( + None, + description="智能视图筛选:recommended=推荐(质量分≥80)、cautious=慎用(60-79)、risky=高风险(<60或已驳回)、unused=未使用、used=已使用、pending_review=待复核", + pattern="^(recommended|cautious|risky|unused|used|pending_review)$", + ), skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=500), authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -101,11 +106,11 @@ def list_assets( if not filter_tag_ids: filter_tag_ids = None - # 需要内存过滤的标志(keyword/gender/style/tag_ids 无法在 DB 层过滤) - needs_memory_filter = bool(keyword or gender or style or filter_tag_ids) + # 需要内存过滤的标志(keyword/gender/style/tag_ids/smart_view 无法在 DB 层过滤) + needs_memory_filter = bool(keyword or gender or style or filter_tag_ids or smart_view) def _apply_memory_filters(items): - """应用 keyword / gender / style / tag_ids 内存过滤。""" + """应用 keyword / gender / style / tag_ids / smart_view 内存过滤。""" result = items if keyword: kw = keyword.lower() @@ -117,6 +122,26 @@ def list_assets( if filter_tag_ids: tag_set = set(filter_tag_ids) result = [i for i in result if tag_set.issubset(set(getattr(i, "tag_ids", [])))] + if smart_view: + meta = lambda a: a.metadata or {} + use_count = lambda a: int(meta(a).get("generation_use_count") or 0) + review_status = lambda a: meta(a).get("review_status", "") + if smart_view == "recommended": + result = [i for i in result if i.quality_score is not None and i.quality_score >= 80] + elif smart_view == "cautious": + result = [i for i in result if i.quality_score is not None and 60 <= i.quality_score < 80] + elif smart_view == "risky": + result = [ + i for i in result + if (i.quality_score is not None and i.quality_score < 60) + or review_status(i) == "rejected" + ] + elif smart_view == "unused": + result = [i for i in result if use_count(i) == 0] + elif smart_view == "used": + result = [i for i in result if use_count(i) > 0] + elif smart_view == "pending_review": + result = [i for i in result if review_status(i) == "pending_review"] return result # ── 优化路径:无内存过滤时,使用 DB 级分页 ── diff --git a/apps/worker/worker_app/tasks/generation.py b/apps/worker/worker_app/tasks/generation.py old mode 100644 new mode 100755 index 5d7f6e356..7e01ea1f7 --- a/apps/worker/worker_app/tasks/generation.py +++ b/apps/worker/worker_app/tasks/generation.py @@ -1092,6 +1092,35 @@ def generate_video(self, task_id: str) -> dict: # ── 5. 标记完成 ────────────────────────────────────────────────── _update_task_status(task_id, "mark_completed", result_count=video_count) + # 5.1 更新标题使用次数 + try: + _title_session = SessionLocal() + try: + from packages.adapters.sqlalchemy_impl.generation_task_repository import ( + SQLAlchemyGenerationTaskRepository, + ) + from packages.adapters.sqlalchemy_impl.title_library_repository import ( + SQLAlchemyTitleLibraryRepository, + ) + + _task_repo = SQLAlchemyGenerationTaskRepository(_title_session) + _gen_task = _task_repo.get(task_id) + if _gen_task and _gen_task.title_ids and _gen_task.created_by_user_id: + _title_repo = SQLAlchemyTitleLibraryRepository(_title_session) + for _tid in _gen_task.title_ids: + try: + _title_repo.increment_usage_count( + _tid, _gen_task.created_by_user_id + ) + except Exception: + logger.warning( + "[task_id=%s] 更新标题使用次数失败: title_id=%s", + task_id, _tid, exc_info=True, + ) + finally: + _title_session.close() + except Exception: + logger.warning("[task_id=%s] 更新标题使用次数异常(不影响主流程)", task_id, exc_info=True) if gen_task: gen_task.append_log( "任务完成", diff --git a/packages/adapters/sqlalchemy_impl/title_library_repository.py b/packages/adapters/sqlalchemy_impl/title_library_repository.py old mode 100644 new mode 100755 index c784c55dd..709e539c3 --- a/packages/adapters/sqlalchemy_impl/title_library_repository.py +++ b/packages/adapters/sqlalchemy_impl/title_library_repository.py @@ -103,6 +103,25 @@ class SQLAlchemyTitleLibraryRepository: self.session.commit() return True + def increment_usage_count(self, title_id: str, user_id: str, increment: int = 1) -> bool: + """递增标题使用次数。返回是否成功。""" + from sqlalchemy import func + + model = ( + self.session.query(TitleLibraryModel) + .filter( + TitleLibraryModel.id == title_id, + TitleLibraryModel.user_id == user_id, + ) + .first() + ) + if model is None: + return False + model.usage_count = (model.usage_count or 0) + increment + model.updated_at = func.now() + self.session.commit() + return True + def count_by_user(self, user_id: str, is_active: bool = True) -> int: return ( self.session.query(TitleLibraryModel) diff --git a/packages/application/title_library/__init__.py b/packages/application/title_library/__init__.py old mode 100644 new mode 100755 index 71d9a794d..2d6438c92 --- a/packages/application/title_library/__init__.py +++ b/packages/application/title_library/__init__.py @@ -1,9 +1,11 @@ """Title library application module.""" +from packages.application.title_library.commands import IncrementTitleUsageCommand from packages.application.title_library.use_cases import ( CreateTitleLibraryUseCase, DeleteTitleLibraryUseCase, GetTitleLibraryUseCase, + IncrementTitleUsageUseCase, ListTitleLibraryUseCase, NotFoundError, QuotaExceededError, @@ -14,6 +16,8 @@ __all__ = [ "CreateTitleLibraryUseCase", "DeleteTitleLibraryUseCase", "GetTitleLibraryUseCase", + "IncrementTitleUsageUseCase", + "IncrementTitleUsageCommand", "ListTitleLibraryUseCase", "UpdateTitleLibraryUseCase", "QuotaExceededError", diff --git a/packages/application/title_library/commands.py b/packages/application/title_library/commands.py old mode 100644 new mode 100755 index 0f4cd7012..4c0dffa78 --- a/packages/application/title_library/commands.py +++ b/packages/application/title_library/commands.py @@ -28,3 +28,10 @@ class UpdateTitleLibraryCommand: tags: Optional[List[str]] = None is_active: Optional[bool] = None metadata_: Optional[dict] = None + + +@dataclass +class IncrementTitleUsageCommand: + title_id: str + user_id: str + increment: int = 1 diff --git a/packages/application/title_library/use_cases.py b/packages/application/title_library/use_cases.py old mode 100644 new mode 100755 index 4306b3402..bebed899a --- a/packages/application/title_library/use_cases.py +++ b/packages/application/title_library/use_cases.py @@ -8,6 +8,7 @@ from typing import List, Optional from packages.adapters.sqlalchemy_impl.title_library_repository import SQLAlchemyTitleLibraryRepository from packages.application.title_library.commands import ( CreateTitleLibraryCommand, + IncrementTitleUsageCommand, UpdateTitleLibraryCommand, ) from packages.domain.quota import QuotaDimension, quota_checker @@ -100,6 +101,22 @@ class DeleteTitleLibraryUseCase: return self.repository.delete(title_id, user_id) +class IncrementTitleUsageUseCase: + """递增标题使用次数。用于生成视频成功后,更新标题的使用统计。""" + + def __init__(self, repository: SQLAlchemyTitleLibraryRepository) -> None: + self.repository = repository + + def execute(self, command: IncrementTitleUsageCommand) -> bool: + if command.increment <= 0: + return False + return self.repository.increment_usage_count( + command.title_id, + command.user_id, + increment=command.increment, + ) + + class QuotaExceededError(Exception): def __init__(self, dimension: str, limit: float, used: float) -> None: self.dimension = dimension -- 2.54.0 From 1d376661144a41da05a71b884dbe3e04e1fcca0c Mon Sep 17 00:00:00 2001 From: CI Bot Date: Mon, 13 Jul 2026 21:09:25 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=80=89=E6=A0=87=E9=A2=98=E6=8E=A5=E5=8F=A3=20POST?= =?UTF-8?q?=20/titles/pick?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PickTitleUseCase:优先使用次数少的,从前5个中随机选,兼顾公平和多样性 - 支持 category 过滤 + exclude_ids 排除(批量生成避免重复) - 空库时返回 404 提示先添加标题 --- apps/api/app/api/routes/titles.py | 42 ++++++++++++++++- .../application/title_library/__init__.py | 5 +- .../application/title_library/commands.py | 7 +++ .../application/title_library/use_cases.py | 47 +++++++++++++++++++ 4 files changed, 99 insertions(+), 2 deletions(-) mode change 100644 => 100755 apps/api/app/api/routes/titles.py diff --git a/apps/api/app/api/routes/titles.py b/apps/api/app/api/routes/titles.py old mode 100644 new mode 100755 index e81730aa3..0237f5d43 --- a/apps/api/app/api/routes/titles.py +++ b/apps/api/app/api/routes/titles.py @@ -17,13 +17,18 @@ from fastapi import APIRouter, Depends, HTTPException, Query, Response, status from sqlalchemy.orm import Session from packages.adapters.sqlalchemy_impl.title_library_repository import SQLAlchemyTitleLibraryRepository -from packages.application.title_library.commands import CreateTitleLibraryCommand, UpdateTitleLibraryCommand +from packages.application.title_library.commands import ( + CreateTitleLibraryCommand, + PickTitleCommand, + UpdateTitleLibraryCommand, +) from packages.application.title_library.use_cases import ( CreateTitleLibraryUseCase, DeleteTitleLibraryUseCase, GetTitleLibraryUseCase, ListTitleLibraryUseCase, NotFoundError, + PickTitleUseCase, QuotaExceededError, UpdateTitleLibraryUseCase, ) @@ -70,6 +75,41 @@ def list_titles( ) +@router.post("/pick", response_model=TitleLibraryItemResponse) +def pick_title( + category: Optional[str] = Query(None, description="按分类筛选,不填则从全部标题中选"), + exclude_ids: Optional[str] = Query( + None, + description="排除的标题ID(逗号分隔),用于批量生成时避免重复", + ), + authenticated_user: AuthenticatedUser = Depends(get_current_user), + title_repository: SQLAlchemyTitleLibraryRepository = Depends(_get_title_repository), +) -> TitleLibraryItemResponse: + """智能选择一个标题。 + + 策略:优先使用次数少的,从最少的前5个中随机选一个,兼顾公平和多样性。 + """ + user_id = authenticated_user.user.id + exclude_list: list[str] = [] + if exclude_ids: + exclude_list = [t.strip() for t in exclude_ids.split(",") if t.strip()] + + use_case = PickTitleUseCase(title_repository) + item = use_case.execute( + PickTitleCommand( + user_id=user_id, + category=category, + exclude_ids=exclude_list, + ) + ) + if item is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="标题库为空,请先添加标题", + ) + return _to_response(item) + + @router.get("/{title_id}", response_model=TitleLibraryItemResponse) def get_title( title_id: str, diff --git a/packages/application/title_library/__init__.py b/packages/application/title_library/__init__.py index 2d6438c92..179f73cdc 100755 --- a/packages/application/title_library/__init__.py +++ b/packages/application/title_library/__init__.py @@ -1,6 +1,6 @@ """Title library application module.""" -from packages.application.title_library.commands import IncrementTitleUsageCommand +from packages.application.title_library.commands import IncrementTitleUsageCommand, PickTitleCommand from packages.application.title_library.use_cases import ( CreateTitleLibraryUseCase, DeleteTitleLibraryUseCase, @@ -8,6 +8,7 @@ from packages.application.title_library.use_cases import ( IncrementTitleUsageUseCase, ListTitleLibraryUseCase, NotFoundError, + PickTitleUseCase, QuotaExceededError, UpdateTitleLibraryUseCase, ) @@ -19,6 +20,8 @@ __all__ = [ "IncrementTitleUsageUseCase", "IncrementTitleUsageCommand", "ListTitleLibraryUseCase", + "PickTitleUseCase", + "PickTitleCommand", "UpdateTitleLibraryUseCase", "QuotaExceededError", "NotFoundError", diff --git a/packages/application/title_library/commands.py b/packages/application/title_library/commands.py index 4c0dffa78..7615fe3eb 100755 --- a/packages/application/title_library/commands.py +++ b/packages/application/title_library/commands.py @@ -35,3 +35,10 @@ class IncrementTitleUsageCommand: title_id: str user_id: str increment: int = 1 + + +@dataclass +class PickTitleCommand: + user_id: str + category: Optional[str] = None + exclude_ids: List[str] = field(default_factory=list) diff --git a/packages/application/title_library/use_cases.py b/packages/application/title_library/use_cases.py index bebed899a..1795de020 100755 --- a/packages/application/title_library/use_cases.py +++ b/packages/application/title_library/use_cases.py @@ -9,6 +9,7 @@ from packages.adapters.sqlalchemy_impl.title_library_repository import SQLAlchem from packages.application.title_library.commands import ( CreateTitleLibraryCommand, IncrementTitleUsageCommand, + PickTitleCommand, UpdateTitleLibraryCommand, ) from packages.domain.quota import QuotaDimension, quota_checker @@ -117,6 +118,52 @@ class IncrementTitleUsageUseCase: ) +class PickTitleUseCase: + """智能选择一个标题。 + + 策略: + 1. 可选按 category 过滤 + 2. 排除指定的 title_ids(如本轮已用过的) + 3. 按使用次数升序,取最少的前 5 个 + 4. 从中随机选一个,增加多样性 + 5. 无可用标题时返回 None + """ + + _CANDIDATE_POOL_SIZE = 5 + + def __init__(self, repository: SQLAlchemyTitleLibraryRepository) -> None: + self.repository = repository + + def execute(self, command: PickTitleCommand) -> TitleLibraryItem | None: + import random + + # 取该用户所有活跃标题(或指定分类) + all_titles = self.repository.list_by_user( + command.user_id, + category=command.category, + is_active=True, + skip=0, + limit=500, # 取足够多的候选 + ) + + if not all_titles: + return None + + # 排除已使用/指定排除的 + exclude_set = set(command.exclude_ids or []) + candidates = [t for t in all_titles if t.id not in exclude_set] + if not candidates: + # 排除后没了,就从全部里选 + candidates = all_titles + + # 按使用次数升序,取最少的前 N 个 + candidates.sort(key=lambda t: t.usage_count) + pool = candidates[: self._CANDIDATE_POOL_SIZE] + + # 随机选一个 + return random.choice(pool) + + class QuotaExceededError(Exception): def __init__(self, dimension: str, limit: float, used: float) -> None: self.dimension = dimension -- 2.54.0 From 1093be9124a632eba4d75c7b5ce28d84c16d6b94 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Mon, 13 Jul 2026 21:11:19 +0800 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=E7=B4=A0=E6=9D=90=E5=88=86?= =?UTF-8?q?=E7=B1=BB=E7=BB=93=E6=9E=9C=E5=9B=9E=E5=A1=AB=20+=20=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=88=86=E7=B1=BB=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 分类任务完成后回填 metadata - classification / classification_confidence 写入 asset.metadata - 保留原有 metadata 字段,不覆盖 2. 素材列表新增 classification 筛选参数 - 支持按内容分类筛选:person / scenic / product / food / tech 等 - 走内存过滤路径,与现有筛选兼容 --- apps/api/app/api/routes/assets.py | 15 ++++++++++++--- apps/worker/worker_app/tasks/classification.py | 6 ++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/api/app/api/routes/assets.py b/apps/api/app/api/routes/assets.py index 40710aaaf..1bf544b23 100755 --- a/apps/api/app/api/routes/assets.py +++ b/apps/api/app/api/routes/assets.py @@ -87,6 +87,10 @@ def list_assets( description="智能视图筛选:recommended=推荐(质量分≥80)、cautious=慎用(60-79)、risky=高风险(<60或已驳回)、unused=未使用、used=已使用、pending_review=待复核", pattern="^(recommended|cautious|risky|unused|used|pending_review)$", ), + classification: Optional[str] = Query( + None, + description="按内容分类筛选:scenic=风景、product=产品、person=人物、animal=动物、food=美食、tech=科技、sport=运动、music=音乐、other=其他", + ), skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=500), authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -106,11 +110,11 @@ def list_assets( if not filter_tag_ids: filter_tag_ids = None - # 需要内存过滤的标志(keyword/gender/style/tag_ids/smart_view 无法在 DB 层过滤) - needs_memory_filter = bool(keyword or gender or style or filter_tag_ids or smart_view) + # 需要内存过滤的标志(keyword/gender/style/tag_ids/smart_view/classification 无法在 DB 层过滤) + needs_memory_filter = bool(keyword or gender or style or filter_tag_ids or smart_view or classification) def _apply_memory_filters(items): - """应用 keyword / gender / style / tag_ids / smart_view 内存过滤。""" + """应用 keyword / gender / style / tag_ids / smart_view / classification 内存过滤。""" result = items if keyword: kw = keyword.lower() @@ -119,6 +123,11 @@ def list_assets( result = [i for i in result if (i.metadata or {}).get("gender") == gender] if style: result = [i for i in result if (i.metadata or {}).get("style") == style] + if classification: + result = [ + i for i in result + if (i.metadata or {}).get("classification") == classification + ] if filter_tag_ids: tag_set = set(filter_tag_ids) result = [i for i in result if tag_set.issubset(set(getattr(i, "tag_ids", [])))] diff --git a/apps/worker/worker_app/tasks/classification.py b/apps/worker/worker_app/tasks/classification.py index cb0fd5a1e..c4f60238a 100755 --- a/apps/worker/worker_app/tasks/classification.py +++ b/apps/worker/worker_app/tasks/classification.py @@ -68,6 +68,12 @@ def classify_asset(self, job_id: str) -> dict: # Update asset with classification status and result asset.classification_status = ClassificationStatus.COMPLETED + # 把分类结果写入 metadata,供列表筛选和智能视图使用 + asset.metadata = { + **(asset.metadata or {}), + "classification": classification, + "classification_confidence": confidence, + } asset_repo.update(asset) session.commit() -- 2.54.0 From d727948309737f637ac8f1d6921e059d143faf2f Mon Sep 17 00:00:00 2001 From: CI Bot Date: Mon, 13 Jul 2026 21:12:44 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20=E7=B4=A0=E6=9D=90=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=AC=A1=E6=95=B0=E9=97=AD=E7=8E=AF=EF=BC=88=E7=94=9F?= =?UTF-8?q?=E6=88=90=E5=90=8E=E6=9B=B4=E6=96=B0=E4=BD=BF=E7=94=A8=E6=AC=A1?= =?UTF-8?q?=E6=95=B0+=E6=9C=80=E8=BF=91=E4=BD=BF=E7=94=A8+=E5=A4=8D?= =?UTF-8?q?=E6=A0=B8=E7=8A=B6=E6=80=81=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调用已有但未接入的 mark_asset_used_for_generation 函数 - generation_use_count + 1 - 记录 last_used_at 时间戳 - 首次使用自动标记为 pending_review 待复核 - 异常兜底:失败不影响主流程,仅打 warning 日志 --- apps/worker/worker_app/tasks/generation.py | 33 ++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/apps/worker/worker_app/tasks/generation.py b/apps/worker/worker_app/tasks/generation.py index 7e01ea1f7..417c4c195 100755 --- a/apps/worker/worker_app/tasks/generation.py +++ b/apps/worker/worker_app/tasks/generation.py @@ -1121,6 +1121,39 @@ def generate_video(self, task_id: str) -> dict: _title_session.close() except Exception: logger.warning("[task_id=%s] 更新标题使用次数异常(不影响主流程)", task_id, exc_info=True) + + # 5.2 更新素材使用次数 + 最近使用时间 + try: + from worker_app.core.asset_usage import mark_asset_used_for_generation + + _asset_session = SessionLocal() + try: + from packages.adapters.sqlalchemy_impl.asset_repository import ( + SQLAlchemyAssetRepository, + ) + from packages.adapters.sqlalchemy_impl.generation_task_repository import ( + SQLAlchemyGenerationTaskRepository, + ) + + _task_repo = SQLAlchemyGenerationTaskRepository(_asset_session) + _asset_repo = SQLAlchemyAssetRepository(_asset_session) + _gen_task = _task_repo.get(task_id) + if _gen_task and _gen_task.asset_ids: + for _aid in _gen_task.asset_ids: + try: + _asset = _asset_repo.get(_aid) + if _asset: + mark_asset_used_for_generation(_asset) + _asset_repo.update(_asset) + except Exception: + logger.warning( + "[task_id=%s] 更新素材使用次数失败: asset_id=%s", + task_id, _aid, exc_info=True, + ) + finally: + _asset_session.close() + except Exception: + logger.warning("[task_id=%s] 更新素材使用次数异常(不影响主流程)", task_id, exc_info=True) if gen_task: gen_task.append_log( "任务完成", -- 2.54.0 From 42317a2508e9800dbefdb73ac4f65ee05b73fb8c Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 09:16:50 +0800 Subject: [PATCH 5/8] =?UTF-8?q?chore:=20rebase=E5=88=B0=E6=9C=80=E6=96=B0d?= =?UTF-8?q?evelop=20+=20=E4=BF=AE=E5=A4=8Dblack=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/worker/worker_app/tasks/generation.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/worker/worker_app/tasks/generation.py b/apps/worker/worker_app/tasks/generation.py index 417c4c195..3e4484e89 100755 --- a/apps/worker/worker_app/tasks/generation.py +++ b/apps/worker/worker_app/tasks/generation.py @@ -1109,13 +1109,13 @@ def generate_video(self, task_id: str) -> dict: _title_repo = SQLAlchemyTitleLibraryRepository(_title_session) for _tid in _gen_task.title_ids: try: - _title_repo.increment_usage_count( - _tid, _gen_task.created_by_user_id - ) + _title_repo.increment_usage_count(_tid, _gen_task.created_by_user_id) except Exception: logger.warning( "[task_id=%s] 更新标题使用次数失败: title_id=%s", - task_id, _tid, exc_info=True, + task_id, + _tid, + exc_info=True, ) finally: _title_session.close() @@ -1148,7 +1148,9 @@ def generate_video(self, task_id: str) -> dict: except Exception: logger.warning( "[task_id=%s] 更新素材使用次数失败: asset_id=%s", - task_id, _aid, exc_info=True, + task_id, + _aid, + exc_info=True, ) finally: _asset_session.close() -- 2.54.0 From f1df96d597a79a801017d9eacffbf4cec7732feb Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 09:20:01 +0800 Subject: [PATCH 6/8] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dflake8=20E731=20la?= =?UTF-8?q?mbda=E8=B5=8B=E5=80=BC=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/app/api/routes/assets.py | 36 +++++++++++++++++-------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/apps/api/app/api/routes/assets.py b/apps/api/app/api/routes/assets.py index 1bf544b23..65ddb927d 100755 --- a/apps/api/app/api/routes/assets.py +++ b/apps/api/app/api/routes/assets.py @@ -124,33 +124,37 @@ def list_assets( if style: result = [i for i in result if (i.metadata or {}).get("style") == style] if classification: - result = [ - i for i in result - if (i.metadata or {}).get("classification") == classification - ] + result = [i for i in result if (i.metadata or {}).get("classification") == classification] if filter_tag_ids: tag_set = set(filter_tag_ids) result = [i for i in result if tag_set.issubset(set(getattr(i, "tag_ids", [])))] if smart_view: - meta = lambda a: a.metadata or {} - use_count = lambda a: int(meta(a).get("generation_use_count") or 0) - review_status = lambda a: meta(a).get("review_status", "") + + def __meta(a): + return a.metadata or {} + + def __use_count(a): + return int(__meta(a).get("generation_use_count") or 0) + + def __review_status(a): + return __meta(a).get("review_status", "") + if smart_view == "recommended": result = [i for i in result if i.quality_score is not None and i.quality_score >= 80] elif smart_view == "cautious": result = [i for i in result if i.quality_score is not None and 60 <= i.quality_score < 80] elif smart_view == "risky": result = [ - i for i in result - if (i.quality_score is not None and i.quality_score < 60) - or review_status(i) == "rejected" + i + for i in result + if (i.quality_score is not None and i.quality_score < 60) or _review_status(i) == "rejected" ] elif smart_view == "unused": - result = [i for i in result if use_count(i) == 0] + result = [i for i in result if _use_count(i) == 0] elif smart_view == "used": - result = [i for i in result if use_count(i) > 0] + result = [i for i in result if _use_count(i) > 0] elif smart_view == "pending_review": - result = [i for i in result if review_status(i) == "pending_review"] + result = [i for i in result if _review_status(i) == "pending_review"] return result # ── 优化路径:无内存过滤时,使用 DB 级分页 ── @@ -269,7 +273,7 @@ def list_assets( ) -def _apply_asset_review_status(item, review_status: str): +def _apply_asset__review_status(item, review_status: str): item.metadata = { **item.metadata, "review_status": review_status, @@ -278,7 +282,7 @@ def _apply_asset_review_status(item, review_status: str): @router.patch("/{asset_id}/review", response_model=AssetResponse) -def update_asset_review_status( +def update_asset__review_status( asset_id: str, request: UpdateAssetReviewRequest, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -289,7 +293,7 @@ def update_asset_review_status( if item is None: raise HTTPException(status_code=404, detail=f"Asset {asset_id} not found") check_project_access(item.project_id, authenticated_user.user.id, project_repository) - _apply_asset_review_status(item, request.review_status) + _apply_asset__review_status(item, request.review_status) updated = asset_repository.update(item) return _to_asset_response(updated) -- 2.54.0 From 6d5383943e93af3f007b2ac92dde3295504075a0 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 09:35:05 +0800 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=A2=AB=E8=AF=AF?= =?UTF-8?q?=E6=94=B9=E7=9A=84review=5Fstatus=E5=87=BD=E6=95=B0=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/app/api/routes/assets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/app/api/routes/assets.py b/apps/api/app/api/routes/assets.py index 65ddb927d..81779d43c 100755 --- a/apps/api/app/api/routes/assets.py +++ b/apps/api/app/api/routes/assets.py @@ -273,7 +273,7 @@ def list_assets( ) -def _apply_asset__review_status(item, review_status: str): +def _apply_asset_review_status(item, review_status: str): item.metadata = { **item.metadata, "review_status": review_status, @@ -282,7 +282,7 @@ def _apply_asset__review_status(item, review_status: str): @router.patch("/{asset_id}/review", response_model=AssetResponse) -def update_asset__review_status( +def update_asset_review_status( asset_id: str, request: UpdateAssetReviewRequest, authenticated_user: AuthenticatedUser = Depends(get_current_user), @@ -293,7 +293,7 @@ def update_asset__review_status( if item is None: raise HTTPException(status_code=404, detail=f"Asset {asset_id} not found") check_project_access(item.project_id, authenticated_user.user.id, project_repository) - _apply_asset__review_status(item, request.review_status) + _apply_asset_review_status(item, request.review_status) updated = asset_repository.update(item) return _to_asset_response(updated) -- 2.54.0 From 3b1935b3ea62144fb5568df06ad2884e7f8a080a Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 09:47:11 +0800 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E8=A7=86=E5=9B=BE=E4=B8=AD=E5=87=BD=E6=95=B0=E5=90=8D=E6=8B=BC?= =?UTF-8?q?=E5=86=99=E9=94=99=E8=AF=AF=EF=BC=88=E5=8D=95=E4=B8=8B=E5=88=92?= =?UTF-8?q?=E7=BA=BF=E2=86=92=E5=8F=8C=E4=B8=8B=E5=88=92=E7=BA=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/app/api/routes/assets.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/api/app/api/routes/assets.py b/apps/api/app/api/routes/assets.py index 81779d43c..a7e7c6c6e 100755 --- a/apps/api/app/api/routes/assets.py +++ b/apps/api/app/api/routes/assets.py @@ -147,14 +147,14 @@ def list_assets( result = [ i for i in result - if (i.quality_score is not None and i.quality_score < 60) or _review_status(i) == "rejected" + if (i.quality_score is not None and i.quality_score < 60) or __review_status(i) == "rejected" ] elif smart_view == "unused": - result = [i for i in result if _use_count(i) == 0] + result = [i for i in result if __use_count(i) == 0] elif smart_view == "used": - result = [i for i in result if _use_count(i) > 0] + result = [i for i in result if __use_count(i) > 0] elif smart_view == "pending_review": - result = [i for i in result if _review_status(i) == "pending_review"] + result = [i for i in result if __review_status(i) == "pending_review"] return result # ── 优化路径:无内存过滤时,使用 DB 级分页 ── -- 2.54.0