From 2c2bd618fa4295fdad8d1077383f19653deb715f Mon Sep 17 00:00:00 2001 From: Xiaoxia AI Date: Wed, 17 Jun 2026 19:20:59 +0800 Subject: [PATCH] feat(phase7): auto-trigger asset classification --- .../web/src/pages/workspace/ProjectAssets.tsx | 12 ++++++---- apps/worker/tasks.py | 23 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/apps/web/src/pages/workspace/ProjectAssets.tsx b/apps/web/src/pages/workspace/ProjectAssets.tsx index 0792a6770..57ecb792d 100644 --- a/apps/web/src/pages/workspace/ProjectAssets.tsx +++ b/apps/web/src/pages/workspace/ProjectAssets.tsx @@ -75,6 +75,7 @@ const ProjectAssets: React.FC = () => { queryKey: ['assets', libraryId], queryFn: () => getAssets(libraryId), enabled: !!libraryId, + refetchInterval: 3000, }); const ingestJobQuery = useQuery({ @@ -102,7 +103,7 @@ const ProjectAssets: React.FC = () => { if (status === 'completed') { setUploading(false); assetsQuery.refetch(); - message.success('素材导入完成'); + message.success('素材导入完成,已自动发起分类'); } if (status === 'failed') { setUploading(false); @@ -166,7 +167,8 @@ const ProjectAssets: React.FC = () => { }, [assetsQuery.data, classificationFilter]); const renderClassification = (asset: AssetItem) => { - if (classifyingAssetId === asset.id && classificationJobQuery.data?.status !== 'failed') { + const autoClassification = String(asset.metadata?.auto_classification || ''); + if (classifyingAssetId === asset.id || autoClassification === 'queued') { return 分类中; } const classification = String(asset.metadata?.classification || ''); @@ -178,6 +180,7 @@ const ProjectAssets: React.FC = () => { {classification} {Math.round(confidence * 100)}% + {autoClassification === 'completed' ? 自动 : null} ); }; @@ -207,11 +210,12 @@ const ProjectAssets: React.FC = () => { render: (_: unknown, record: AssetItem) => { const hasClassification = !!record.metadata?.classification; const isCurrentClassifying = classifyingAssetId === record.id; + const isAutoClassifying = record.metadata?.auto_classification === 'queued'; return ( ); }, diff --git a/apps/worker/tasks.py b/apps/worker/tasks.py index 596503111..cd9d9fc87 100644 --- a/apps/worker/tasks.py +++ b/apps/worker/tasks.py @@ -1,13 +1,13 @@ from datetime import datetime, timezone import random -from app.core.config import get_settings +from app.config import get_settings from .celery_app import celery_app from packages.adapters.sqlalchemy_impl.session import SessionLocal, build_session_factory from packages.adapters.sqlalchemy_impl.ingest_job_repository import SQLAlchemyIngestJobRepository from packages.adapters.sqlalchemy_impl.asset_repository import SQLAlchemyAssetRepository from packages.adapters.sqlalchemy_impl.classification_job_repository import SQLAlchemyClassificationJobRepository -from packages.domain import Asset, AssetClassification, ClassificationJobStatus, IngestJobStatus +from packages.domain import Asset, AssetClassification, ClassificationJob, ClassificationJobStatus, IngestJobStatus settings = get_settings() if SessionLocal is None: @@ -25,6 +25,7 @@ def ingest_asset(job_id: str) -> dict: try: ingest_repo = SQLAlchemyIngestJobRepository(session) asset_repo = SQLAlchemyAssetRepository(session) + classification_repo = SQLAlchemyClassificationJobRepository(session) job = ingest_repo.get(job_id) if job is None: @@ -55,7 +56,7 @@ def ingest_asset(job_id: str) -> dict: name=filename, storage_key=storage_key, mime_type=mime_type, - metadata={"source": "ingest_task"}, + metadata={"source": "ingest_task", "auto_classification": "queued"}, ) asset_repo.create(asset) @@ -64,7 +65,20 @@ def ingest_asset(job_id: str) -> dict: job.updated_at = datetime.now(timezone.utc) ingest_repo.update(job) - return {"ok": True, "job_id": job.id, "asset_id": asset.id} + classification_job = ClassificationJob.create( + workspace_id=job.workspace_id, + project_id=job.project_id, + asset_id=asset.id, + ) + classification_repo.create(classification_job) + celery_app.send_task("worker.classify_asset", args=[classification_job.id]) + + return { + "ok": True, + "job_id": job.id, + "asset_id": asset.id, + "classification_job_id": classification_job.id, + } except Exception as error: try: ingest_repo = SQLAlchemyIngestJobRepository(session) @@ -125,6 +139,7 @@ def classify_asset(job_id: str) -> dict: **asset.metadata, "classification": classification, "classification_confidence": confidence, + "auto_classification": "completed", } asset_repo.update(asset)