baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。 当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包), M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
146
backend/app/api/v1/task_actions.py
Normal file
146
backend/app/api/v1/task_actions.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""
|
||||
app/api/v1/task_actions.py — 任务操作端点
|
||||
选文案 / 选图 / 导入文案 / 重新生成 / 提交审核 / 偏好上下文
|
||||
DTOs 和辅助函数从 tasks.py 导入。
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.response import ok, raise_not_found, raise_state_invalid
|
||||
from app.middleware.workspace_guard import CurrentUser, require_write_permission
|
||||
from app.models.task import GenerationTask, ImageCandidate, TextCandidate
|
||||
|
||||
from app.api.v1.tasks import (
|
||||
SelectCandidateRequest,
|
||||
ImportTextRequest,
|
||||
_fmt_text,
|
||||
_fmt_image,
|
||||
_check_task_ownership,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter(prefix="/tasks", tags=["tasks"])
|
||||
|
||||
|
||||
@router.post("/{task_id}/text-candidates/select")
|
||||
def select_text(
|
||||
task_id: int, body: SelectCandidateRequest,
|
||||
current_user: Annotated[CurrentUser, Depends(require_write_permission)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""选文案(飞轮信号 text_select +3)。"""
|
||||
from app.services.flywheel_service import record_signal
|
||||
task = _check_task_ownership(
|
||||
db.query(GenerationTask).filter(GenerationTask.id == task_id).first(),
|
||||
current_user.workspace_id,
|
||||
)
|
||||
tc = db.query(TextCandidate).filter(
|
||||
TextCandidate.id == body.candidate_id, TextCandidate.task_id == task_id
|
||||
).first()
|
||||
if not tc:
|
||||
raise_not_found("文案候选不存在")
|
||||
tc.is_selected = True
|
||||
db.commit()
|
||||
record_signal(db, current_user, task, "text_select", candidate_id=tc.id, angle_label=tc.angle_label)
|
||||
return ok({"selected": body.candidate_id})
|
||||
|
||||
|
||||
@router.post("/{task_id}/image-candidates/select")
|
||||
def select_image(
|
||||
task_id: int, body: SelectCandidateRequest,
|
||||
current_user: Annotated[CurrentUser, Depends(require_write_permission)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""选图(飞轮信号 image_select +3)。"""
|
||||
from app.services.flywheel_service import record_signal
|
||||
task = _check_task_ownership(
|
||||
db.query(GenerationTask).filter(GenerationTask.id == task_id).first(),
|
||||
current_user.workspace_id,
|
||||
)
|
||||
ic = db.query(ImageCandidate).filter(
|
||||
ImageCandidate.id == body.candidate_id, ImageCandidate.task_id == task_id
|
||||
).first()
|
||||
if not ic:
|
||||
raise_not_found("图片候选不存在")
|
||||
ic.is_selected = True
|
||||
db.commit()
|
||||
record_signal(db, current_user, task, "image_select", candidate_id=ic.id)
|
||||
return ok({"selected": body.candidate_id})
|
||||
|
||||
|
||||
@router.post("/{task_id}/import-text")
|
||||
def import_text(
|
||||
task_id: int, body: ImportTextRequest,
|
||||
current_user: Annotated[CurrentUser, Depends(require_write_permission)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""轨B:导入外部文案直接进候选池,跳过 AI 生成(洞2)。"""
|
||||
_check_task_ownership(
|
||||
db.query(GenerationTask).filter(GenerationTask.id == task_id).first(),
|
||||
current_user.workspace_id,
|
||||
)
|
||||
tc = TextCandidate(
|
||||
workspace_id=current_user.workspace_id, task_id=task_id,
|
||||
source="import", content=body.content, angle_label=body.angle_label,
|
||||
)
|
||||
db.add(tc)
|
||||
db.commit()
|
||||
db.refresh(tc)
|
||||
return ok(_fmt_text(tc))
|
||||
|
||||
|
||||
@router.post("/{task_id}/regenerate")
|
||||
def regenerate(
|
||||
task_id: int,
|
||||
current_user: Annotated[CurrentUser, Depends(require_write_permission)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""重新生成(飞轮信号 regenerate -1)。"""
|
||||
from app.services.flywheel_service import record_signal
|
||||
from app.services.task_service import enqueue_generation
|
||||
task = _check_task_ownership(
|
||||
db.query(GenerationTask).filter(GenerationTask.id == task_id).first(),
|
||||
current_user.workspace_id,
|
||||
)
|
||||
record_signal(db, current_user, task, "regenerate")
|
||||
enqueue_generation(task.id)
|
||||
return ok({"task_id": task_id, "status": "regenerating"})
|
||||
|
||||
|
||||
@router.post("/{task_id}/submit-review")
|
||||
def submit_review(
|
||||
task_id: int,
|
||||
current_user: Annotated[CurrentUser, Depends(require_write_permission)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""提交审核(状态 pending_selection → pending_review)。"""
|
||||
task = _check_task_ownership(
|
||||
db.query(GenerationTask).filter(GenerationTask.id == task_id).first(),
|
||||
current_user.workspace_id,
|
||||
)
|
||||
if task.status != "pending_selection":
|
||||
raise_state_invalid("只有 pending_selection 状态可提审")
|
||||
task.status = "pending_review"
|
||||
db.commit()
|
||||
return ok({"task_id": task_id, "status": "pending_review"})
|
||||
|
||||
|
||||
@router.get("/{task_id}/preference/context")
|
||||
def get_preference_context(
|
||||
task_id: int,
|
||||
current_user: Annotated[CurrentUser, Depends(require_write_permission)] = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""取本任务飞轮偏好上下文(最近选中样本+打回原因)。"""
|
||||
task = _check_task_ownership(
|
||||
db.query(GenerationTask).filter(GenerationTask.id == task_id).first(),
|
||||
current_user.workspace_id,
|
||||
)
|
||||
from app.services.flywheel_service import get_preference_context
|
||||
ctx = get_preference_context(db, current_user.workspace_id, task.product_id)
|
||||
return ok(ctx)
|
||||
Reference in New Issue
Block a user