From f53b6e103506fc6a65c10e56d3456e2d81a6c53e Mon Sep 17 00:00:00 2001 From: "Azmat@qq.com" Date: Thu, 3 Sep 2026 16:00:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=BB=98=E8=AE=A4=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E6=A8=A1=E5=9E=8B=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/backend/apps/ai/services.py | 21 ++------------ .../apps/ai/test_entity_extraction_routing.py | 28 ++++++++++++++++--- core/frontend/src/design-restraint.css | 2 +- core/frontend/src/library-page.css | 2 +- core/frontend/src/models-page.css | 6 +++- core/frontend/src/products-page.css | 18 ++++++++++-- 6 files changed, 49 insertions(+), 28 deletions(-) diff --git a/core/backend/apps/ai/services.py b/core/backend/apps/ai/services.py index 2e6fd83..64c6abe 100644 --- a/core/backend/apps/ai/services.py +++ b/core/backend/apps/ai/services.py @@ -520,25 +520,10 @@ def _parse_extracted_entities_response(text: str) -> tuple[list[dict], list[dict return entities, seg_refs -# 提取步固定锁定豆包 2.0 Pro(与脚本生成同款),不靠 get_default_model 的「最早创建」排序—— -# 避免不同环境 DB 创建序漂移把提取路由到别的(中转站)推理模型。取不到再回落默认文本模型。 -EXTRACT_TEXT_MODEL_NAME = "doubao-seed-2-0-pro-260215" - - def _resolve_extract_model_config(): - """提取实体用的文本模型:优先豆包 2.0 Pro(active 且 provider active),否则回落默认文本模型。""" - pinned = ( - ModelConfig.objects.select_related("provider") - .filter( - name=EXTRACT_TEXT_MODEL_NAME, - capability=ModelConfig.Capability.TEXT, - status=ModelConfig.Status.ACTIVE, - provider__status="active", - ) - .order_by("created_at") - .first() - ) - return pinned or get_default_model(ModelConfig.Capability.TEXT) + """提取实体用平台后台钦定的默认文本模型(ModelConfig.is_default)。 + 未设默认时与 get_default_model 一致:回落最早创建的 active 文本模型。""" + return get_default_model(ModelConfig.Capability.TEXT) def _collect_extract_text( diff --git a/core/backend/apps/ai/test_entity_extraction_routing.py b/core/backend/apps/ai/test_entity_extraction_routing.py index 58e2a73..30338b9 100644 --- a/core/backend/apps/ai/test_entity_extraction_routing.py +++ b/core/backend/apps/ai/test_entity_extraction_routing.py @@ -6,7 +6,7 @@ from django.test import TestCase from apps.accounts.models import Team, TeamMember, User from apps.ai.models import AITask, ModelConfig, ModelProvider -from apps.ai.services import EXTRACT_TEXT_MODEL_NAME, run_extract_entities_task, submit_extract_entities +from apps.ai.services import run_extract_entities_task, submit_extract_entities from apps.billing.models import CreditAccount, CreditLedger from apps.products.models import Product from apps.projects.models import Project, ScriptSegment, ScriptVersion @@ -66,7 +66,7 @@ class EntityExtractionRoutingTests(TestCase): metadata={"routing": {"fallback_priority": priority}}, ) - def model(self, provider, name, *, outbound=True, base_cost="0.50"): + def model(self, provider, name, *, outbound=True, base_cost="0.50", is_default=False): return ModelConfig.objects.create( provider=provider, name=name, @@ -75,6 +75,7 @@ class EntityExtractionRoutingTests(TestCase): endpoint="chat/completions", unit_price=Decimal("10"), status=ModelConfig.Status.ACTIVE, + is_default=is_default, metadata=_metadata(outbound=outbound, base_cost=base_cost), ) @@ -114,7 +115,7 @@ class EntityExtractionRoutingTests(TestCase): return CreditLedger.objects.filter(task=task, ledger_type=ledger_type).count() def test_first_success_records_streaming_structured_attempt_and_persists_entities(self): - primary = self.model(self.provider("entity-primary", 20), "entity-primary") + primary = self.model(self.provider("entity-primary", 20), "entity-primary", is_default=True) task = self.submit() run_extract_entities_task(task_id=str(task.id)) @@ -213,7 +214,7 @@ class EntityExtractionRoutingTests(TestCase): def test_direct_doubao_primary_retries_but_does_not_switch_when_outbound_disabled(self): primary = self.model( - self.provider("doubao", 10), EXTRACT_TEXT_MODEL_NAME, outbound=False + self.provider("doubao", 10), "doubao-seed-2-0-pro-260215", outbound=False ) candidate = self.model( self.provider("entity-unused-candidate", 20), "entity-unused", outbound=False @@ -236,3 +237,22 @@ class EntityExtractionRoutingTests(TestCase): self.assertTrue(all(attempt.public_model_name == primary.display_name for attempt in attempts)) self.assertEqual(self.ledger_count(task, CreditLedger.Type.RESERVE), 1) self.assertEqual(self.ledger_count(task, CreditLedger.Type.RELEASE), 1) + + def test_uses_database_default_text_model_not_older_doubao_pin(self): + older = self.model(self.provider("volcengine-old", 20), "doubao-seed-2-0-pro-260215") + default = self.model( + self.provider("volcengine-default", 10), + "deepseek-v4-pro", + is_default=True, + ) + task = self.submit() + + run_extract_entities_task(task_id=str(task.id)) + + task.refresh_from_db() + attempt = task.model_attempts.get() + self.assertEqual(task.status, AITask.Status.SUCCEEDED) + self.assertEqual(attempt.model_config_id, default.id) + self.assertNotEqual(attempt.model_config_id, older.id) + self.assertEqual(task.request_payload["model"], "deepseek-v4-pro") + diff --git a/core/frontend/src/design-restraint.css b/core/frontend/src/design-restraint.css index 17f2dd8..216effb 100644 --- a/core/frontend/src/design-restraint.css +++ b/core/frontend/src/design-restraint.css @@ -1314,7 +1314,7 @@ body.sidebar-collapsed .user::after { display: none; } .chip.active { border-color: var(--heat-40); color: var(--heat); background: var(--heat-12); } .chip svg { width: 12px; height: 12px; } /* 清空筛选:幽灵态(无边框/透明底),仅在有筛选时出现,点击重置全部筛选 */ -.chip.chip-clear { border-color: transparent; background: transparent; color: var(--black-alpha-48); padding: 0 8px; } +.chip.chip-clear { border-color: transparent; background: transparent; color: var(--black-alpha-48); padding: 0 8px; line-height: 1; white-space: nowrap; } .chip.chip-clear:hover { background: var(--black-alpha-4); color: var(--accent-black); } /* ─── Toolbar ─── */ diff --git a/core/frontend/src/library-page.css b/core/frontend/src/library-page.css index 2c5e697..e3faf69 100644 --- a/core/frontend/src/library-page.css +++ b/core/frontend/src/library-page.css @@ -26,7 +26,7 @@ .library-page .lib-head { display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 18px; diff --git a/core/frontend/src/models-page.css b/core/frontend/src/models-page.css index 3355616..9cdfe7f 100644 --- a/core/frontend/src/models-page.css +++ b/core/frontend/src/models-page.css @@ -25,7 +25,7 @@ .models-page .ml-head { display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 18px; @@ -91,12 +91,16 @@ .models-page .ml-seg-btn { height: 32px; padding: 0 12px; + display: inline-flex; + align-items: center; + justify-content: center; border: 0; border-radius: 8px; color: #50555d; background: transparent; font: inherit; font-size: 13px; + line-height: 1; cursor: pointer; } .models-page .ml-seg-btn:hover { color: var(--accent-black); } diff --git a/core/frontend/src/products-page.css b/core/frontend/src/products-page.css index e688a8f..c208cb7 100644 --- a/core/frontend/src/products-page.css +++ b/core/frontend/src/products-page.css @@ -21,7 +21,7 @@ .pl-head { display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 18px; @@ -134,9 +134,9 @@ flex-wrap: wrap; } - .pl-select { position: relative; width: 150px; flex: 0 0 150px; z-index: 40; } + .pl-select { position: relative; width: auto; min-width: 132px; flex: 0 0 auto; z-index: 40; } .pl-select.open { z-index: 50; } - .pl-select[data-key="cat"] { width: 168px; flex-basis: 168px; } + .pl-select[data-key="cat"] { min-width: 148px; } .pl-select-trigger { width: 100%; height: 34px; @@ -226,14 +226,26 @@ .pl-clear { height: 34px; padding: 0 12px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; border: 0; border-radius: 10px; color: var(--pl-muted); background: transparent; font: inherit; font-size: 13px; + line-height: 1; + white-space: nowrap; + flex-shrink: 0; cursor: pointer; } + .pl-clear svg { + width: 13px; + height: 13px; + flex: 0 0 auto; + } .pl-clear:hover { color: var(--accent-black); background: rgba(34, 42, 54, 0.06); } .pl-view {