From 544324ed0a23769fc506d79bfd41b634ee0a1465 Mon Sep 17 00:00:00 2001 From: cita-777 Date: Sat, 18 Apr 2026 14:01:26 +0800 Subject: [PATCH] Restore the full payload-rule protocol list in Settings One last review thread caught a real UI regression: the visual payload-rule editor had drifted away from the shared protocol option source and no longer exposed several active platform targets. Rebinding the dropdown to the shared option list fixes the regression without inventing another source of truth. Constraint: The fix should reuse the existing shared option list instead of creating another hardcoded protocol subset Rejected: Expand the inline array manually | would repeat the same drift problem that caused the regression Confidence: high Scope-risk: narrow Directive: Payload-rule protocol selectors should always read from the shared option source so Settings and the payload-rule parser stay aligned Tested: settings.payload-rules vitest; typecheck; git diff --check Not-tested: Full npm test --- src/web/pages/Settings.tsx | 10 +-- src/web/pages/settings.payload-rules.test.tsx | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/web/pages/Settings.tsx b/src/web/pages/Settings.tsx index 59ee6c1..691dc9a 100644 --- a/src/web/pages/Settings.tsx +++ b/src/web/pages/Settings.tsx @@ -18,6 +18,7 @@ import { type VisualPayloadRuleValueMode, visualRulesToPayloadRules, } from './settings/payloadRulesVisual.js'; +import { PAYLOAD_RULE_PROTOCOL_OPTIONS } from './settings/payloadRuleProtocolOptions.js'; import UpdateCenterSection from './settings/UpdateCenterSection.js'; import { applyRoutingProfilePreset, @@ -209,15 +210,6 @@ const PAYLOAD_RULE_ACTION_OPTIONS: Array<{ value: PayloadRuleAction; label: stri { value: 'filter', label: '删除字段' }, ]; -const PAYLOAD_RULE_PROTOCOL_OPTIONS = [ - { value: '', label: '全部协议' }, - { value: 'codex', label: 'Codex' }, - { value: 'openai', label: 'OpenAI' }, - { value: 'claude', label: 'Claude' }, - { value: 'gemini', label: 'Gemini' }, - { value: 'antigravity', label: 'Antigravity' }, -]; - const PAYLOAD_RULE_VALUE_MODE_OPTIONS: Array<{ value: VisualPayloadRuleValueMode; label: string }> = [ { value: 'text', label: '文本' }, { value: 'json', label: 'JSON' }, diff --git a/src/web/pages/settings.payload-rules.test.tsx b/src/web/pages/settings.payload-rules.test.tsx index 484139a..5e212ae 100644 --- a/src/web/pages/settings.payload-rules.test.tsx +++ b/src/web/pages/settings.payload-rules.test.tsx @@ -327,6 +327,69 @@ describe('Settings payload rules', () => { } }); + it('keeps the full payload-rule protocol option set in the visual editor', async () => { + apiMock.getRuntimeSettings.mockResolvedValueOnce({ + checkinCron: '0 8 * * *', + checkinScheduleMode: 'interval', + checkinIntervalHours: 6, + balanceRefreshCron: '0 * * * *', + logCleanupCron: '15 4 * * *', + logCleanupUsageLogsEnabled: true, + logCleanupProgramLogsEnabled: true, + logCleanupRetentionDays: 14, + routingFallbackUnitCost: 1, + proxyFirstByteTimeoutSec: 0, + routingWeights: {}, + tokenRouterFailureCooldownMaxSec: 30 * 24 * 60 * 60, + adminIpAllowlist: [], + systemProxyUrl: '', + payloadRules: {}, + }); + + let root!: ReactTestRenderer; + try { + await act(async () => { + root = create( + + + + + , + ); + }); + await flushMicrotasks(); + + const addButton = root.root.find((node) => ( + node.type === 'button' + && typeof node.props.onClick === 'function' + && collectText(node).trim() === '新增规则' + )); + + await act(async () => { + addButton.props.onClick(); + }); + + const protocolSelect = root.root.find((node) => ( + node.props['data-testid'] === 'payload-rule-protocol-1' + )); + const options = Array.isArray(protocolSelect.props.options) + ? protocolSelect.props.options + : []; + const values = options.map((option: { value: string }) => option.value); + + expect(values).toEqual(expect.arrayContaining([ + '', + 'sub2api', + 'new-api', + 'one-api', + 'gemini-cli', + 'anyrouter', + ])); + } finally { + root?.unmount(); + } + }); + it('blocks save when a payload-rule section contains invalid JSON', async () => { let root!: ReactTestRenderer; try {