feat(admin): Phase 8 模型供应商管理 — provider/model CRUD + 启停 + 定价 + 设默认

后端:ModelConfig.is_default + migration;get_default_model 优先 is_default 否则回落最早 active(零回归);
adminpanel providers CRUD(api_key write-only 不回传)+ models CRUD(?provider/capability 筛)+ set-default(同 capability 清旧);
IsPlatformAdmin + 审计。
前端:adminApi providers/models/setDefault;Admin 模型供应商页(供应商表+模型表+启停+定价+设默认+供应商/模型弹窗)。
测试:adminpanel 51 单测过(api_key 隐藏+入库/CRUD/set-default 改 get_default_model);
apps.ai 仍仅 3 个进场前既有失败(零回归);无头 e2e _admin-p8.mjs 5 断言过 + 0 console error
(用一次性禁用 provider+model capability=export,无副作用,跑完删);tsc+build 绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-19 22:45:14 +08:00
co-authored by Claude Opus 4.8
parent 39b4467258
commit 4ce7e957d2
13 changed files with 673 additions and 5 deletions
+33 -1
View File
@@ -3,7 +3,7 @@ from decimal import Decimal
from rest_framework import serializers
from apps.accounts.models import Team, TeamMember, User
from apps.ai.models import AITask, QualityWord
from apps.ai.models import AITask, ModelConfig, ModelProvider, QualityWord
from apps.assets.models import Asset
from apps.billing.models import CreditLedger, QuotaPolicy
@@ -133,3 +133,35 @@ class AdminQuotaPolicySerializer(serializers.ModelSerializer):
"monthly_limit", "project_limit", "per_task_limit", "is_active", "created_at",
]
read_only_fields = ["id", "team_name", "created_at"]
class AdminModelProviderSerializer(serializers.ModelSerializer):
has_api_key = serializers.SerializerMethodField()
model_count = serializers.SerializerMethodField()
class Meta:
model = ModelProvider
fields = ["id", "name", "display_name", "status", "base_url", "api_key", "metadata", "has_api_key", "model_count", "created_at"]
# api_key 只写不读(密钥不回传前端)
extra_kwargs = {"api_key": {"write_only": True, "required": False}}
read_only_fields = ["id", "has_api_key", "model_count", "created_at"]
def get_has_api_key(self, obj) -> bool:
return bool(obj.api_key)
def get_model_count(self, obj) -> int:
anno = getattr(obj, "model_count_anno", None)
return anno if anno is not None else obj.models.count()
class AdminModelConfigSerializer(serializers.ModelSerializer):
provider_name = serializers.CharField(source="provider.name", read_only=True, default=None)
class Meta:
model = ModelConfig
fields = [
"id", "provider", "provider_name", "name", "display_name", "capability",
"endpoint", "unit_price", "status", "is_default", "rate_limit_per_minute", "metadata", "created_at",
]
# is_default 只经 set-default 端点改,不在普通编辑里直接写
read_only_fields = ["id", "provider_name", "is_default", "created_at"]