Files
yingqing/core/backend/apps/ai/serializers.py
T
2026-09-04 18:16:43 +08:00

167 lines
6.1 KiB
Python

from rest_framework import serializers
from .models import (
AITask,
CreationConversation,
CreationMessage,
ImageConversation,
ModelConfig,
ModelProvider,
)
class ModelProviderSerializer(serializers.ModelSerializer):
class Meta:
model = ModelProvider
fields = ["id", "name", "display_name", "status", "base_url", "metadata"]
read_only_fields = fields
class ModelConfigSerializer(serializers.ModelSerializer):
provider = ModelProviderSerializer(read_only=True)
class Meta:
model = ModelConfig
fields = ["id", "provider", "name", "display_name", "capability", "endpoint", "unit_price", "status", "is_default", "metadata"]
read_only_fields = fields
class ImageConversationSerializer(serializers.ModelSerializer):
"""图片创作对话:左栏列表用。title 可写(重命名),mode/product 创建时可指定。"""
task_count = serializers.SerializerMethodField()
class Meta:
model = ImageConversation
fields = [
"id", "title", "mode", "product", "task_count",
"is_deleted", "purged_at", "last_active_at", "created_at", "updated_at",
]
read_only_fields = ["id", "task_count", "is_deleted", "purged_at", "last_active_at", "created_at", "updated_at"]
def get_task_count(self, obj) -> int:
# list 接口已 annotate;无 annotate 时回落实时 count(详情/创建场景)
cached = getattr(obj, "_task_count", None)
return cached if cached is not None else obj.tasks.count()
class ImageConversationTrashSerializer(ImageConversationSerializer):
"""垃圾桶专用对话契约:只额外暴露一张展示封面,不影响普通对话列表查询。"""
cover_preview_url = serializers.SerializerMethodField()
class Meta(ImageConversationSerializer.Meta):
fields = [*ImageConversationSerializer.Meta.fields, "cover_preview_url"]
read_only_fields = [*ImageConversationSerializer.Meta.read_only_fields, "cover_preview_url"]
def get_cover_preview_url(self, obj) -> str:
return str(getattr(obj, "_trash_cover_preview_url", "") or "")
class AITaskSerializer(serializers.ModelSerializer):
model_config = ModelConfigSerializer(read_only=True)
# 从 request_payload 抽出的分组/标签信息(由 AITaskViewSet annotate 提供;其它调用处无此注解则为 None)。
# 不直接读 obj.request_payload —— 那列已 defer,读了会触发懒加载把几 MB payload 整列拉回。
batch_id = serializers.SerializerMethodField()
mode = serializers.SerializerMethodField()
def get_batch_id(self, obj):
return getattr(obj, "rp_batch_id", None)
def get_mode(self, obj):
return getattr(obj, "rp_mode", None)
class Meta:
model = AITask
fields = [
"id",
"project",
"task_type",
"batch_id",
"mode",
"status",
"model_config",
"provider_task_id",
"estimated_cost",
"actual_cost",
"error_code",
"error_message",
"submitted_at",
"completed_at",
"is_deleted",
"purged_at",
"created_at",
"updated_at",
]
# batch_id / mode 是显式声明的 SerializerMethodField(本就只读),不能再列进 read_only_fields(DRF 会报错)
read_only_fields = [f for f in fields if f not in ("batch_id", "mode")]
class CreationMessageSerializer(serializers.ModelSerializer):
"""全能创作对话流里的一条消息。前端**按 kind 分发到不同卡片组件**,
结构化内容一律在 payload 里(契约 §2),不要从 text 里解析。"""
class Meta:
model = CreationMessage
fields = ["id", "role", "kind", "text", "payload", "refs", "task", "seq", "created_at"]
read_only_fields = fields
class CreationConversationSerializer(serializers.ModelSerializer):
"""会话列表 / 详情。title 可写(重命名);params 创建后也可改(对话页改模型/比例后立刻生效);
mode 创建后不可改。"""
message_count = serializers.SerializerMethodField()
cover_url = serializers.SerializerMethodField()
class Meta:
model = CreationConversation
fields = [
"id", "title", "mode", "preset", "params", "status",
"message_count", "cover_url",
"last_active_at", "created_at", "updated_at",
]
read_only_fields = [
"id", "status", "message_count", "cover_url",
"last_active_at", "created_at", "updated_at",
]
def get_message_count(self, obj) -> int:
cached = getattr(obj, "_message_count", None)
return cached if cached is not None else obj.messages.count()
def get_cover_url(self, obj) -> str:
"""历史页封面 = **最新一版**结果(重生成是往下叠加,所以取最后一条 RESULT)。"""
last = (
obj.messages.filter(kind=CreationMessage.Kind.RESULT)
.order_by("-seq")
.values_list("payload", flat=True)
.first()
)
if not last:
return ""
assets = (last or {}).get("assets") or []
if not assets:
return ""
first = assets[0] or {}
return first.get("cover") or first.get("url") or ""
def update(self, instance, validated_data):
# mode 定死:允许传但忽略,避免前端误改后顶栏参数与已生成内容对不上
validated_data.pop("mode", None)
return super().update(instance, validated_data)
class CreationConversationDetailSerializer(CreationConversationSerializer):
"""详情:带全量消息,进对话页一次性回填。"""
messages = CreationMessageSerializer(many=True, read_only=True)
pinned_refs = serializers.JSONField(read_only=True)
class Meta(CreationConversationSerializer.Meta):
fields = [*CreationConversationSerializer.Meta.fields, "messages", "pinned_refs"]
read_only_fields = [
*CreationConversationSerializer.Meta.read_only_fields, "messages", "pinned_refs",
]