From 3bfd4cfc1ce454a1b966c4c24bc1367d0f3198cd Mon Sep 17 00:00:00 2001 From: phamnazage-jpg Date: Sat, 30 May 2026 10:38:59 +0800 Subject: [PATCH] feat(portal): add logical group guidance config --- deploy/tksea-portal/admin/logical-groups.html | 23 +++++++++++++++ deploy/tksea-portal/index.html | 15 ++++++---- internal/app/logical_groups_api.go | 18 ++++++++++++ internal/app/logical_groups_api_test.go | 26 +++++++++++++++++ internal/app/portal_api.go | 6 ++++ internal/app/portal_api_test.go | 15 ++++++++++ .../0013_logical_group_guidance.sql | 3 ++ internal/store/sqlite/logical_groups_repo.go | 29 ++++++++++++++++--- .../store/sqlite/logical_groups_repo_test.go | 12 ++++++++ scripts/test/test_tksea_portal_assets.sh | 8 ++++- 10 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 internal/store/migrations/0013_logical_group_guidance.sql diff --git a/deploy/tksea-portal/admin/logical-groups.html b/deploy/tksea-portal/admin/logical-groups.html index d12b52ff..878c395a 100644 --- a/deploy/tksea-portal/admin/logical-groups.html +++ b/deploy/tksea-portal/admin/logical-groups.html @@ -531,6 +531,20 @@ description + +
+ + +
@@ -691,6 +705,9 @@ const groupDisplayNameInput = document.getElementById("group-display-name"); const groupStatusInput = document.getElementById("group-status-input"); const groupDescriptionInput = document.getElementById("group-description"); + const groupUsageScenarioInput = document.getElementById("group-usage-scenario"); + const groupRecommendationInput = document.getElementById("group-recommendation"); + const groupNextStepHintInput = document.getElementById("group-next-step-hint"); const groupRoutePolicyInput = document.getElementById("group-route-policy"); const groupStickyModeInput = document.getElementById("group-sticky-mode"); const groupConversationTTLInput = document.getElementById("group-conversation-ttl"); @@ -868,6 +885,9 @@ display_name: groupDisplayNameInput.value.trim(), status: groupStatusInput.value, description: groupDescriptionInput.value.trim(), + usage_scenario: groupUsageScenarioInput.value.trim(), + recommendation: groupRecommendationInput.value.trim(), + next_step_hint: groupNextStepHintInput.value.trim(), route_policy: groupRoutePolicyInput.value, sticky_mode: groupStickyModeInput.value, conversation_ttl_seconds: Number(groupConversationTTLInput.value || "0"), @@ -896,6 +916,9 @@ groupDisplayNameInput.value = group?.display_name || ""; groupStatusInput.value = group?.status || "active"; groupDescriptionInput.value = group?.description || ""; + groupUsageScenarioInput.value = group?.usage_scenario || ""; + groupRecommendationInput.value = group?.recommendation || ""; + groupNextStepHintInput.value = group?.next_step_hint || ""; groupRoutePolicyInput.value = group?.route_policy || "priority"; groupStickyModeInput.value = group?.sticky_mode || "conversation_preferred"; groupConversationTTLInput.value = String(group?.conversation_ttl_seconds || 7200); diff --git a/deploy/tksea-portal/index.html b/deploy/tksea-portal/index.html index f17d80aa..d91783ea 100644 --- a/deploy/tksea-portal/index.html +++ b/deploy/tksea-portal/index.html @@ -823,7 +823,7 @@ description: "适合 DeepSeek 官方 chat 路线需求。当前用户侧建议直接使用 deepseek-chat。" } }; - const MODEL_GUIDANCE = { + const LEGACY_MODEL_GUIDANCE = { "kimi-k2.6": { scenario: "适合日常聊天、长上下文问答和轻量智能体使用。", recommendation: "默认先从这条模型线开始验证接入是否通畅。" @@ -1254,11 +1254,14 @@ const group = row.logicalGroup; const models = portalLogicalGroupModels(group); const primaryModel = models[0] || ""; - const guidance = MODEL_GUIDANCE[primaryModel] || { + const configuredScenario = String(group.usage_scenario || "").trim(); + const configuredRecommendation = String(group.recommendation || "").trim(); + const configuredNextStep = String(group.next_step_hint || "").trim(); + const guidance = LEGACY_MODEL_GUIDANCE[primaryModel] || { scenario: "适合按该逻辑分组下的公开模型集合统一接入。", recommendation: "建议先用列表中的第一个公开模型做连通性验证。" }; - const nextStep = row.stateKey === "active" + const defaultNextStep = row.stateKey === "active" ? "当前已具备订阅与权限,建议直接创建测试 Key 并使用推荐模型发起第一次请求。" : row.stateKey === "granted" ? "当前已具备线路权限,但未发现活跃订阅;建议先确认订阅状态后再发起调用。" @@ -1274,9 +1277,9 @@ models, stateText: row.stateText, stateKey: row.stateKey, - scenario: guidance.scenario, - recommendation: guidance.recommendation, - nextStep, + scenario: configuredScenario || guidance.scenario, + recommendation: configuredRecommendation || guidance.recommendation, + nextStep: configuredNextStep || defaultNextStep, compatibility, stickyMode: group.sticky_mode || "conversation_preferred", routePolicy: group.route_policy || "priority" diff --git a/internal/app/logical_groups_api.go b/internal/app/logical_groups_api.go index 457f777c..a7d22571 100644 --- a/internal/app/logical_groups_api.go +++ b/internal/app/logical_groups_api.go @@ -16,6 +16,9 @@ type CreateLogicalGroupRequest struct { DisplayName string `json:"display_name"` Status string `json:"status"` Description string `json:"description,omitempty"` + UsageScenario string `json:"usage_scenario,omitempty"` + Recommendation string `json:"recommendation,omitempty"` + NextStepHint string `json:"next_step_hint,omitempty"` RoutePolicy string `json:"route_policy,omitempty"` StickyMode string `json:"sticky_mode,omitempty"` ConversationTTLSeconds int `json:"conversation_ttl_seconds,omitempty"` @@ -29,6 +32,9 @@ type UpdateLogicalGroupRequest struct { DisplayName string `json:"display_name"` Status string `json:"status"` Description string `json:"description,omitempty"` + UsageScenario string `json:"usage_scenario,omitempty"` + Recommendation string `json:"recommendation,omitempty"` + NextStepHint string `json:"next_step_hint,omitempty"` RoutePolicy string `json:"route_policy,omitempty"` StickyMode string `json:"sticky_mode,omitempty"` ConversationTTLSeconds int `json:"conversation_ttl_seconds,omitempty"` @@ -42,6 +48,9 @@ type LogicalGroupInfo struct { DisplayName string `json:"display_name"` Status string `json:"status"` Description string `json:"description,omitempty"` + UsageScenario string `json:"usage_scenario,omitempty"` + Recommendation string `json:"recommendation,omitempty"` + NextStepHint string `json:"next_step_hint,omitempty"` RoutePolicy string `json:"route_policy,omitempty"` StickyMode string `json:"sticky_mode,omitempty"` ConversationTTLSeconds int `json:"conversation_ttl_seconds,omitempty"` @@ -433,6 +442,9 @@ func buildUpdateLogicalGroupAction(sqliteDSN string) func(context.Context, Updat DisplayName: req.DisplayName, Status: req.Status, Description: req.Description, + UsageScenario: req.UsageScenario, + Recommendation: req.Recommendation, + NextStepHint: req.NextStepHint, RoutePolicy: req.RoutePolicy, StickyMode: req.StickyMode, ConversationTTLSeconds: req.ConversationTTLSeconds, @@ -683,6 +695,9 @@ func logicalGroupRequestToRow(req CreateLogicalGroupRequest) sqlite.LogicalGroup DisplayName: strings.TrimSpace(req.DisplayName), Status: strings.TrimSpace(req.Status), Description: strings.TrimSpace(req.Description), + UsageScenario: strings.TrimSpace(req.UsageScenario), + Recommendation: strings.TrimSpace(req.Recommendation), + NextStepHint: strings.TrimSpace(req.NextStepHint), RoutePolicy: strings.TrimSpace(req.RoutePolicy), StickyMode: strings.TrimSpace(req.StickyMode), ConversationTTLSeconds: req.ConversationTTLSeconds, @@ -745,6 +760,9 @@ func logicalGroupRowToInfo(group sqlite.LogicalGroup, models []LogicalGroupModel DisplayName: group.DisplayName, Status: group.Status, Description: group.Description, + UsageScenario: group.UsageScenario, + Recommendation: group.Recommendation, + NextStepHint: group.NextStepHint, RoutePolicy: group.RoutePolicy, StickyMode: group.StickyMode, ConversationTTLSeconds: group.ConversationTTLSeconds, diff --git a/internal/app/logical_groups_api_test.go b/internal/app/logical_groups_api_test.go index a50ca63b..13063cff 100644 --- a/internal/app/logical_groups_api_test.go +++ b/internal/app/logical_groups_api_test.go @@ -16,10 +16,14 @@ func TestAPICreateLogicalGroupReturnsCreated(t *testing.T) { if req.LogicalGroupID != "gpt-shared" { t.Fatalf("LogicalGroupID = %q, want gpt-shared", req.LogicalGroupID) } + if req.UsageScenario != "适合统一 GPT 产品入口" { + t.Fatalf("UsageScenario = %q, want configured guidance", req.UsageScenario) + } return LogicalGroupInfo{ LogicalGroupID: req.LogicalGroupID, DisplayName: req.DisplayName, Status: req.Status, + UsageScenario: req.UsageScenario, }, nil }, }) @@ -28,6 +32,7 @@ func TestAPICreateLogicalGroupReturnsCreated(t *testing.T) { "logical_group_id": "gpt-shared", "display_name": "GPT Shared", "status": "active", + "usage_scenario": "适合统一 GPT 产品入口", }, "secret-token") response := httptestRecorder(handler, request) assertStatusCode(t, response, http.StatusCreated) @@ -44,6 +49,9 @@ func TestAPIGetLogicalGroupReturnsAggregatedItem(t *testing.T) { LogicalGroupID: groupID, DisplayName: "GPT Shared", Status: "active", + UsageScenario: "适合统一 GPT 产品入口", + Recommendation: "优先使用 gpt-5.4", + NextStepHint: "先创建测试 Key", Models: []LogicalGroupModelInfo{{PublicModel: "gpt-5.4", Status: "active"}}, Routes: []LogicalGroupRouteInfo{{ RouteID: "asxs", @@ -83,6 +91,9 @@ func TestAPIGetLogicalGroupReturnsAggregatedItem(t *testing.T) { if !ok || firstRoute["route_id"] != "asxs" { t.Fatalf("first route = %#v, want route_id asxs", routes[0]) } + if group["usage_scenario"] != "适合统一 GPT 产品入口" || group["recommendation"] != "优先使用 gpt-5.4" || group["next_step_hint"] != "先创建测试 Key" { + t.Fatalf("group guidance = %#v, want configured guidance fields", group) + } } func TestAPICreateLogicalGroupRouteUsesPathGroupID(t *testing.T) { @@ -151,6 +162,9 @@ func TestNewActionSetLogicalGroupCRUDFlow(t *testing.T) { LogicalGroupID: "gpt-shared", DisplayName: "GPT Shared", Status: "active", + UsageScenario: "适合统一 GPT 产品入口", + Recommendation: "优先使用 gpt-5.4", + NextStepHint: "先创建测试 Key", }) if err != nil { t.Fatalf("CreateLogicalGroup() error = %v", err) @@ -158,6 +172,9 @@ func TestNewActionSetLogicalGroupCRUDFlow(t *testing.T) { if createdGroup.LogicalGroupID != "gpt-shared" { t.Fatalf("CreateLogicalGroup() = %+v, want logical_group_id gpt-shared", createdGroup) } + if createdGroup.UsageScenario != "适合统一 GPT 产品入口" || createdGroup.Recommendation != "优先使用 gpt-5.4" || createdGroup.NextStepHint != "先创建测试 Key" { + t.Fatalf("CreateLogicalGroup() guidance = %+v, want configured guidance fields", createdGroup) + } if _, err := actions.CreateLogicalGroupModel(ctx, CreateLogicalGroupModelRequest{ LogicalGroupID: "gpt-shared", @@ -199,11 +216,17 @@ func TestNewActionSetLogicalGroupCRUDFlow(t *testing.T) { if len(group.Routes[0].Models) != 1 || group.Routes[0].Models[0].ShadowModel != "gpt-5.4" { t.Fatalf("GetLogicalGroup().Routes[0].Models = %+v, want shadow gpt-5.4", group.Routes[0].Models) } + if group.UsageScenario != "适合统一 GPT 产品入口" || group.Recommendation != "优先使用 gpt-5.4" || group.NextStepHint != "先创建测试 Key" { + t.Fatalf("GetLogicalGroup() guidance = %+v, want configured guidance fields", group) + } if _, err := actions.UpdateLogicalGroup(ctx, UpdateLogicalGroupRequest{ LogicalGroupID: "gpt-shared", DisplayName: "GPT Shared Updated", Status: "paused", + UsageScenario: "适合升级后的 GPT 产品入口", + Recommendation: "先验证高质量推理链路", + NextStepHint: "升级后重新申请测试 Key", }); err != nil { t.Fatalf("UpdateLogicalGroup() error = %v", err) } @@ -228,6 +251,9 @@ func TestNewActionSetLogicalGroupCRUDFlow(t *testing.T) { if len(groups) != 1 || groups[0].DisplayName != "GPT Shared Updated" { t.Fatalf("ListLogicalGroups() = %+v, want updated group", groups) } + if groups[0].UsageScenario != "适合升级后的 GPT 产品入口" || groups[0].Recommendation != "先验证高质量推理链路" || groups[0].NextStepHint != "升级后重新申请测试 Key" { + t.Fatalf("ListLogicalGroups() guidance = %+v, want updated guidance fields", groups[0]) + } routeModels, err := actions.ListLogicalGroupRouteModels(ctx, ListLogicalGroupRouteModelsRequest{ LogicalGroupID: "gpt-shared", diff --git a/internal/app/portal_api.go b/internal/app/portal_api.go index 08062f79..886eb69a 100644 --- a/internal/app/portal_api.go +++ b/internal/app/portal_api.go @@ -13,6 +13,9 @@ type PortalLogicalGroupInfo struct { LogicalGroupID string `json:"logical_group_id"` DisplayName string `json:"display_name"` Description string `json:"description,omitempty"` + UsageScenario string `json:"usage_scenario,omitempty"` + Recommendation string `json:"recommendation,omitempty"` + NextStepHint string `json:"next_step_hint,omitempty"` Status string `json:"status"` StickyMode string `json:"sticky_mode,omitempty"` RoutePolicy string `json:"route_policy,omitempty"` @@ -159,6 +162,9 @@ func buildPortalLogicalGroupInfo(ctx context.Context, store *sqlite.DB, group sq LogicalGroupID: group.LogicalGroupID, DisplayName: group.DisplayName, Description: group.Description, + UsageScenario: group.UsageScenario, + Recommendation: group.Recommendation, + NextStepHint: group.NextStepHint, Status: group.Status, StickyMode: group.StickyMode, RoutePolicy: group.RoutePolicy, diff --git a/internal/app/portal_api_test.go b/internal/app/portal_api_test.go index 625ea3fc..a5886d40 100644 --- a/internal/app/portal_api_test.go +++ b/internal/app/portal_api_test.go @@ -15,6 +15,9 @@ func TestAPIListPortalLogicalGroups(t *testing.T) { return []PortalLogicalGroupInfo{{ LogicalGroupID: "gpt-shared", DisplayName: "GPT Shared", + UsageScenario: "适合统一 GPT 产品入口", + Recommendation: "优先使用 gpt-5.4", + NextStepHint: "先创建测试 Key", Status: "active", RouteCount: 2, ActiveRouteCount: 1, @@ -37,6 +40,9 @@ func TestAPIListPortalLogicalGroups(t *testing.T) { if len(listPayload.LogicalGroups) != 1 || listPayload.LogicalGroups[0].LogicalGroupID != "gpt-shared" || listPayload.LogicalGroups[0].RouteCount != 2 { t.Fatalf("portal logical groups payload = %+v", listPayload) } + if listPayload.LogicalGroups[0].UsageScenario != "适合统一 GPT 产品入口" || listPayload.LogicalGroups[0].Recommendation != "优先使用 gpt-5.4" || listPayload.LogicalGroups[0].NextStepHint != "先创建测试 Key" { + t.Fatalf("portal logical groups guidance = %+v", listPayload.LogicalGroups[0]) + } } func TestAPIGetPortalLogicalGroupModels(t *testing.T) { @@ -80,6 +86,9 @@ func TestNewActionSetPortalLogicalGroups(t *testing.T) { DisplayName: "GPT Shared", Status: "active", Description: "Public GPT product", + UsageScenario: "适合统一 GPT 产品入口", + Recommendation: "优先使用 gpt-5.4", + NextStepHint: "先创建测试 Key", RoutePolicy: "priority", StickyMode: "conversation_preferred", ConversationTTLSeconds: 7200, @@ -158,6 +167,9 @@ func TestNewActionSetPortalLogicalGroups(t *testing.T) { if group.DisplayName != "GPT Shared" || group.RoutePolicy != "priority" { t.Fatalf("GetPortalLogicalGroup() = %+v", group) } + if group.UsageScenario != "适合统一 GPT 产品入口" || group.Recommendation != "优先使用 gpt-5.4" || group.NextStepHint != "先创建测试 Key" { + t.Fatalf("GetPortalLogicalGroup() guidance = %+v", group) + } models, err := actions.ListPortalLogicalGroupModels(ctx, "gpt-shared") if err != nil { @@ -177,4 +189,7 @@ func TestNewActionSetPortalLogicalGroups(t *testing.T) { if payload["logical_group"].LogicalGroupID != "gpt-shared" { t.Fatalf("portal logical group payload = %+v", payload) } + if payload["logical_group"].Recommendation != "优先使用 gpt-5.4" { + t.Fatalf("portal logical group recommendation = %+v, want configured recommendation", payload["logical_group"]) + } } diff --git a/internal/store/migrations/0013_logical_group_guidance.sql b/internal/store/migrations/0013_logical_group_guidance.sql new file mode 100644 index 00000000..fe1abbeb --- /dev/null +++ b/internal/store/migrations/0013_logical_group_guidance.sql @@ -0,0 +1,3 @@ +ALTER TABLE logical_groups ADD COLUMN usage_scenario TEXT NOT NULL DEFAULT ''; +ALTER TABLE logical_groups ADD COLUMN recommendation TEXT NOT NULL DEFAULT ''; +ALTER TABLE logical_groups ADD COLUMN next_step_hint TEXT NOT NULL DEFAULT ''; diff --git a/internal/store/sqlite/logical_groups_repo.go b/internal/store/sqlite/logical_groups_repo.go index 15c85cb8..f30f950f 100644 --- a/internal/store/sqlite/logical_groups_repo.go +++ b/internal/store/sqlite/logical_groups_repo.go @@ -21,6 +21,9 @@ type LogicalGroup struct { DisplayName string Status string Description string + UsageScenario string + Recommendation string + NextStepHint string RoutePolicy string StickyMode string ConversationTTLSeconds int @@ -52,17 +55,23 @@ func (r *LogicalGroupsRepo) Create(ctx context.Context, group LogicalGroup) (int display_name, status, description, + usage_scenario, + recommendation, + next_step_hint, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, group.LogicalGroupID, group.DisplayName, group.Status, group.Description, + group.UsageScenario, + group.Recommendation, + group.NextStepHint, group.RoutePolicy, group.StickyMode, group.ConversationTTLSeconds, @@ -90,7 +99,7 @@ func (r *LogicalGroupsRepo) GetByLogicalGroupID(ctx context.Context, logicalGrou var group LogicalGroup if err := r.db.QueryRowContext( ctx, - `SELECT id, logical_group_id, display_name, status, description, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at + `SELECT id, logical_group_id, display_name, status, description, usage_scenario, recommendation, next_step_hint, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at FROM logical_groups WHERE logical_group_id = ?`, logicalGroupID, @@ -100,6 +109,9 @@ func (r *LogicalGroupsRepo) GetByLogicalGroupID(ctx context.Context, logicalGrou &group.DisplayName, &group.Status, &group.Description, + &group.UsageScenario, + &group.Recommendation, + &group.NextStepHint, &group.RoutePolicy, &group.StickyMode, &group.ConversationTTLSeconds, @@ -117,7 +129,7 @@ func (r *LogicalGroupsRepo) GetByLogicalGroupID(ctx context.Context, logicalGrou func (r *LogicalGroupsRepo) List(ctx context.Context) ([]LogicalGroup, error) { rows, err := r.db.QueryContext( ctx, - `SELECT id, logical_group_id, display_name, status, description, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at + `SELECT id, logical_group_id, display_name, status, description, usage_scenario, recommendation, next_step_hint, route_policy, sticky_mode, conversation_ttl_seconds, user_model_ttl_seconds, failover_threshold, cooldown_seconds, created_at, updated_at FROM logical_groups ORDER BY id ASC`, ) @@ -135,6 +147,9 @@ func (r *LogicalGroupsRepo) List(ctx context.Context) ([]LogicalGroup, error) { &group.DisplayName, &group.Status, &group.Description, + &group.UsageScenario, + &group.Recommendation, + &group.NextStepHint, &group.RoutePolicy, &group.StickyMode, &group.ConversationTTLSeconds, @@ -163,11 +178,14 @@ func (r *LogicalGroupsRepo) UpdateByLogicalGroupID(ctx context.Context, group Lo result, err := r.db.ExecContext( ctx, `UPDATE logical_groups - SET display_name = ?, status = ?, description = ?, route_policy = ?, sticky_mode = ?, conversation_ttl_seconds = ?, user_model_ttl_seconds = ?, failover_threshold = ?, cooldown_seconds = ?, updated_at = CURRENT_TIMESTAMP + SET display_name = ?, status = ?, description = ?, usage_scenario = ?, recommendation = ?, next_step_hint = ?, route_policy = ?, sticky_mode = ?, conversation_ttl_seconds = ?, user_model_ttl_seconds = ?, failover_threshold = ?, cooldown_seconds = ?, updated_at = CURRENT_TIMESTAMP WHERE logical_group_id = ?`, group.DisplayName, group.Status, group.Description, + group.UsageScenario, + group.Recommendation, + group.NextStepHint, group.RoutePolicy, group.StickyMode, group.ConversationTTLSeconds, @@ -214,6 +232,9 @@ func normalizeLogicalGroup(group LogicalGroup) (LogicalGroup, error) { group.DisplayName = strings.TrimSpace(group.DisplayName) group.Status = strings.TrimSpace(group.Status) group.Description = strings.TrimSpace(group.Description) + group.UsageScenario = strings.TrimSpace(group.UsageScenario) + group.Recommendation = strings.TrimSpace(group.Recommendation) + group.NextStepHint = strings.TrimSpace(group.NextStepHint) group.RoutePolicy = strings.TrimSpace(group.RoutePolicy) group.StickyMode = strings.TrimSpace(group.StickyMode) diff --git a/internal/store/sqlite/logical_groups_repo_test.go b/internal/store/sqlite/logical_groups_repo_test.go index be4cd5bb..8ca8a848 100644 --- a/internal/store/sqlite/logical_groups_repo_test.go +++ b/internal/store/sqlite/logical_groups_repo_test.go @@ -16,6 +16,9 @@ func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) { DisplayName: "GPT Shared", Status: "active", Description: "shared group", + UsageScenario: "适合统一 GPT 产品入口。", + Recommendation: "优先使用 gpt-5.4。", + NextStepHint: "先创建测试 Key。", }) if err != nil { t.Fatalf("Create() error = %v", err) @@ -34,12 +37,18 @@ func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) { if group.StickyMode != defaultLogicalGroupStickyMode { t.Fatalf("StickyMode = %q, want %q", group.StickyMode, defaultLogicalGroupStickyMode) } + if group.UsageScenario != "适合统一 GPT 产品入口。" || group.Recommendation != "优先使用 gpt-5.4。" || group.NextStepHint != "先创建测试 Key。" { + t.Fatalf("guidance fields = %+v, want persisted guidance", group) + } if err := store.LogicalGroups().UpdateByLogicalGroupID(ctx, LogicalGroup{ LogicalGroupID: "gpt-shared", DisplayName: "GPT Shared Updated", Status: "paused", Description: "updated", + UsageScenario: "适合更新后的产品入口。", + Recommendation: "优先做连通性验证。", + NextStepHint: "先确认订阅再调用。", RoutePolicy: "priority", StickyMode: "user_preferred", ConversationTTLSeconds: 3600, @@ -57,6 +66,9 @@ func TestLogicalGroupsRepoCreateGetUpdateDelete(t *testing.T) { if updated.DisplayName != "GPT Shared Updated" || updated.Status != "paused" { t.Fatalf("updated group = %+v, want updated fields", updated) } + if updated.UsageScenario != "适合更新后的产品入口。" || updated.Recommendation != "优先做连通性验证。" || updated.NextStepHint != "先确认订阅再调用。" { + t.Fatalf("updated guidance = %+v, want updated guidance fields", updated) + } if err := store.LogicalGroups().DeleteByLogicalGroupID(ctx, "gpt-shared"); err != nil { t.Fatalf("DeleteByLogicalGroupID() error = %v", err) diff --git a/scripts/test/test_tksea_portal_assets.sh b/scripts/test/test_tksea_portal_assets.sh index cff6df84..3e70f8d4 100755 --- a/scripts/test/test_tksea_portal_assets.sh +++ b/scripts/test/test_tksea_portal_assets.sh @@ -63,13 +63,16 @@ assert_contains_file "$HTML_FILE" "LEGACY_GROUP_CATALOG" assert_contains_file "$HTML_FILE" "逻辑分组权限" assert_contains_file "$HTML_FILE" "renderEntitlementView" assert_contains_file "$HTML_FILE" "使用建议与可用模型说明" -assert_contains_file "$HTML_FILE" "MODEL_GUIDANCE" +assert_contains_file "$HTML_FILE" "LEGACY_MODEL_GUIDANCE" assert_contains_file "$HTML_FILE" "buildGuideEntries" assert_contains_file "$HTML_FILE" "renderUsageGuides" assert_contains_file "$HTML_FILE" "推荐模型" assert_contains_file "$HTML_FILE" "接入建议" assert_contains_file "$HTML_FILE" "下一步" assert_contains_file "$HTML_FILE" "路由策略" +assert_contains_file "$HTML_FILE" "usage_scenario" +assert_contains_file "$HTML_FILE" "recommendation" +assert_contains_file "$HTML_FILE" "next_step_hint" assert_contains_file "$HTML_FILE" "已开通订阅" assert_contains_file "$HTML_FILE" "已授予权限" assert_contains_file "$HTML_FILE" "归属待整理" @@ -122,6 +125,9 @@ assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "/api/logical-groups" assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "logical_group" assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "shadow_group_id" assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "shadow_host_id" +assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "usage_scenario" +assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "recommendation" +assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "next_step_hint" assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "首版页面只覆盖新增与查看" assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" 'credentials: "include"' assert_contains_file "$ADMIN_LOGICAL_GROUPS_FILE" "/portal-admin-api"