fix: 统一AI生成失败提示

This commit is contained in:
hh
2026-07-15 13:12:35 +08:00
parent 38d8c8ad54
commit e81d7170ed
19 changed files with 1220 additions and 88 deletions
+47 -23
View File
@@ -13,6 +13,7 @@ from apps.assets.serializers import AssetSerializer
from apps.common.api import TeamScopedViewSetMixin, get_current_team
from apps.common.celery_health import require_worker
from .generation_errors import classify_generation_error, public_error_for_task
from .models import AITask, ImageConversation, ModelConfig
from .serializers import AITaskSerializer, ImageConversationSerializer, ModelConfigSerializer
from .services import enqueue_standalone_images
@@ -69,7 +70,14 @@ class GenerateImageView(APIView):
try:
tasks = enqueue_standalone_images(team=team, user=request.user, prompt=prompt, mode=mode, count=count, product_id=product_id, reference_product=reference_product, model_id=model_id, model_entity_id=model_entity_id, ratio=ratio, image_model=image_model, conversation=conversation, reference_image_ids=reference_image_ids, platform_id=platform_id, batch_id=batch_id)
except ValueError as exc: # 无可用模型 / 余额不足等,立即反馈
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
internal_kind = "user_credit_insufficient" if str(exc).strip().lower() == "insufficient credit" else ""
public_error = classify_generation_error(
exc, operation="image_generate", internal_kind=internal_kind
)
return Response(
{"detail": public_error.fallback_message, "error": public_error.as_dict()},
status=status.HTTP_400_BAD_REQUEST,
)
# 本次提交即刷新对话活跃时间,左栏「最近」据此置顶
ImageConversation.objects.filter(id=conversation.id).update(last_active_at=timezone.now())
# batch_id 回传给前端存进批次卡:后续「重跑这张 / 重跑整批」带它回来即可归回原批次
@@ -90,17 +98,18 @@ class GenerateImageView(APIView):
tasks = AITask.objects.filter(team=team, id__in=ids).prefetch_related(
"generated_assets", "generated_assets__files"
)
data = [
{
"id": str(t.id),
"status": t.status,
"error_message": t.error_message,
data = []
for task in tasks:
public_error = public_error_for_task(task)
data.append({
"id": str(task.id),
"status": task.status,
"error": public_error.as_dict() if public_error else None,
"error_message": public_error.fallback_message if public_error else "",
"assets": AssetSerializer(
[a for a in t.generated_assets.all() if not a.is_deleted and a.purged_at is None], many=True
[a for a in task.generated_assets.all() if not a.is_deleted and a.purged_at is None], many=True
).data,
}
for t in tasks
]
})
return Response({"tasks": data})
@@ -199,11 +208,14 @@ class AITaskViewSet(TeamScopedViewSetMixin, ReadOnlyModelViewSet):
qs = qs.filter(request_payload__product_id=product_id)
tasks = list(qs.order_by("-created_at").prefetch_related("generated_assets", "generated_assets__files")[:limit])
tasks.reverse() # 旧 → 新,与对话流/工作台批次流的时间序一致
data = [
{
data = []
for t in tasks:
public_error = public_error_for_task(t)
data.append({
"id": str(t.id),
"status": t.status,
"error_message": t.error_message,
"error": public_error.as_dict() if public_error else None,
"error_message": public_error.fallback_message if public_error else "",
"prompt": t.rp_prompt or "",
"batch_id": t.rp_batch_id or "",
"ratio": t.rp_ratio or "",
@@ -217,9 +229,7 @@ class AITaskViewSet(TeamScopedViewSetMixin, ReadOnlyModelViewSet):
"assets": AssetSerializer(
[a for a in t.generated_assets.all() if not a.is_deleted and a.purged_at is None], many=True
).data,
}
for t in tasks
]
})
return Response({"tasks": data})
@action(detail=False, methods=["post"], url_path="mark-read")
@@ -335,11 +345,14 @@ class ImageConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
out.append(ref_cache[rid])
return out
data = [
{
data = []
for t in tasks:
public_error = public_error_for_task(t)
data.append({
"id": str(t.id),
"status": t.status,
"error_message": t.error_message,
"error": public_error.as_dict() if public_error else None,
"error_message": public_error.fallback_message if public_error else "",
"prompt": (t.request_payload or {}).get("prompt", ""),
"batch_id": (t.request_payload or {}).get("batch_id", ""),
"ratio": (t.request_payload or {}).get("ratio") or "",
@@ -350,9 +363,7 @@ class ImageConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
"assets": AssetSerializer(
[a for a in t.generated_assets.all() if not a.is_deleted and a.purged_at is None], many=True
).data,
}
for t in tasks
]
})
return Response({"conversation_id": str(conversation.id), "tasks": data})
@@ -409,7 +420,20 @@ class FreeVideoView(APIView):
try:
task = submit_free_video(team=team, user=request.user, params=request.data or {})
except ValueError as exc:
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
message = str(exc)
internal_kind = (
"user_credit_insufficient" if "余额不足" in message
else "model_unavailable" if "模型未配置" in message
else "provider_rate_limited" if "任务进行中" in message
else "invalid_input"
)
public_error = classify_generation_error(
exc, operation="video_generate", internal_kind=internal_kind
)
return Response(
{"detail": public_error.fallback_message, "error": public_error.as_dict()},
status=status.HTTP_400_BAD_REQUEST,
)
# 重取带 prefetch 的实例,序列化统一走同一条路
task = _free_video_task_queryset(team).get(id=task.id)
return Response({"task": serialize_free_video_task(task)}, status=status.HTTP_202_ACCEPTED)