Compare commits

...

3 Commits

8 changed files with 77 additions and 5 deletions
@@ -888,6 +888,13 @@ export function VisualConfigEditor({
}
/>
</FieldShell>
<Input
label={t('config_management.visual.sections.network.session_affinity_ttl')}
placeholder="1h"
value={values.routingSessionAffinityTTL}
onChange={(e) => onChange({ routingSessionAffinityTTL: e.target.value })}
disabled={disabled}
/>
</SectionGrid>
<SectionGrid>
@@ -900,6 +907,12 @@ export function VisualConfigEditor({
disabled={disabled}
onChange={(forceModelPrefix) => onChange({ forceModelPrefix })}
/>
<ToggleRow
title={t('config_management.visual.sections.network.session_affinity')}
checked={values.routingSessionAffinity}
disabled={disabled}
onChange={(routingSessionAffinity) => onChange({ routingSessionAffinity })}
/>
<ToggleRow
title={t('config_management.visual.sections.network.ws_auth')}
description={t('config_management.visual.sections.network.ws_auth_desc')}
+7
View File
@@ -974,6 +974,13 @@ const resolveClaudePlanType = (profile: ClaudeProfileResponse | null): string |
const hasClaudePro = normalizeFlagValue(profile.account?.has_claude_pro);
if (hasClaudePro) return 'plan_pro';
const organizationType = normalizeStringValue(profile.organization?.organization_type)?.toLowerCase();
const subscriptionStatus = normalizeStringValue(profile.organization?.subscription_status)?.toLowerCase();
if (organizationType === 'claude_team' && subscriptionStatus === 'active') {
return 'plan_team';
}
if (hasClaudeMax === false && hasClaudePro === false) return 'plan_free';
return null;
+37 -1
View File
@@ -656,6 +656,18 @@ function getNextDirtyFields(
if (Object.prototype.hasOwnProperty.call(patch, 'routingStrategy')) {
updateDirty('routingStrategy', nextValues.routingStrategy === baselineValues.routingStrategy);
}
if (Object.prototype.hasOwnProperty.call(patch, 'routingSessionAffinity')) {
updateDirty(
'routingSessionAffinity',
nextValues.routingSessionAffinity === baselineValues.routingSessionAffinity
);
}
if (Object.prototype.hasOwnProperty.call(patch, 'routingSessionAffinityTTL')) {
updateDirty(
'routingSessionAffinityTTL',
nextValues.routingSessionAffinityTTL === baselineValues.routingSessionAffinityTTL
);
}
if (Object.prototype.hasOwnProperty.call(patch, 'payloadDefaultRules')) {
updateDirty(
'payloadDefaultRules',
@@ -836,6 +848,19 @@ export function useVisualConfig() {
quotaAntigravityCredits: Boolean(quotaExceeded?.['antigravity-credits'] ?? true),
routingStrategy: routing?.strategy === 'fill-first' ? 'fill-first' : 'round-robin',
routingSessionAffinity: Boolean(
routing?.['session-affinity'] ??
routing?.sessionAffinity ??
routing?.['sessionAffinity']
),
routingSessionAffinityTTL:
typeof routing?.['session-affinity-ttl'] === 'string'
? routing['session-affinity-ttl']
: typeof routing?.sessionAffinityTTL === 'string'
? routing.sessionAffinityTTL
: typeof routing?.['sessionAffinityTTL'] === 'string'
? routing['sessionAffinityTTL']
: '',
payloadDefaultRules: parsePayloadRules(payload?.default),
payloadDefaultRawRules: parseRawPayloadRules(payload?.['default-raw']),
@@ -949,9 +974,20 @@ export function useVisualConfig() {
deleteIfMapEmpty(doc, ['quota-exceeded']);
}
if (docHas(doc, ['routing']) || values.routingStrategy !== 'round-robin') {
if (
docHas(doc, ['routing']) ||
values.routingStrategy !== 'round-robin' ||
values.routingSessionAffinity ||
values.routingSessionAffinityTTL.trim()
) {
ensureMapInDoc(doc, ['routing']);
doc.setIn(['routing', 'strategy'], values.routingStrategy);
setBooleanInDoc(doc, ['routing', 'session-affinity'], values.routingSessionAffinity);
setStringInDoc(
doc,
['routing', 'session-affinity-ttl'],
values.routingSessionAffinityTTL
);
deleteIfMapEmpty(doc, ['routing']);
}
+4 -1
View File
@@ -645,7 +645,8 @@
"plan_pro": "Pro",
"plan_max": "Max",
"plan_max5": "Max 5x",
"plan_max20": "Max 20x"
"plan_max20": "Max 20x",
"plan_team": "Team"
},
"codex_quota": {
"title": "Codex Quota",
@@ -1231,8 +1232,10 @@
"routing_strategy_hint": "Select credential selection strategy",
"strategy_round_robin": "Round Robin",
"strategy_fill_first": "Fill First",
"session_affinity_ttl": "Session Affinity TTL",
"force_model_prefix": "Force Model Prefix",
"force_model_prefix_desc": "Unprefixed model requests only use credentials without prefix",
"session_affinity": "Session Affinity Routing",
"ws_auth": "WebSocket Authentication",
"ws_auth_desc": "Enable WebSocket authentication (/v1/ws)"
},
+4 -1
View File
@@ -642,7 +642,8 @@
"plan_pro": "Pro",
"plan_max": "Max",
"plan_max5": "Max 5x",
"plan_max20": "Max 20x"
"plan_max20": "Max 20x",
"plan_team": "Team"
},
"codex_quota": {
"title": "Квота Codex",
@@ -1230,8 +1231,10 @@
"routing_strategy_hint": "Выберите стратегию подбора учётных данных",
"strategy_round_robin": "По кругу",
"strategy_fill_first": "Сначала заполнить",
"session_affinity_ttl": "TTL привязки сессии",
"force_model_prefix": "Принудительный префикс модели",
"force_model_prefix_desc": "Запросы к моделям без префикса используют только учётные данные без префикса",
"session_affinity": "Маршрутизация с привязкой к сессии",
"ws_auth": "Аутентификация WebSocket",
"ws_auth_desc": "Включить аутентификацию WebSocket (/v1/ws)"
},
+4 -1
View File
@@ -645,7 +645,8 @@
"plan_pro": "专业版",
"plan_max": "Max",
"plan_max5": "Max 5x",
"plan_max20": "Max 20x"
"plan_max20": "Max 20x",
"plan_team": "团队版"
},
"codex_quota": {
"title": "Codex 额度",
@@ -1231,8 +1232,10 @@
"routing_strategy_hint": "选择凭据选择策略",
"strategy_round_robin": "轮询 (Round Robin)",
"strategy_fill_first": "填充优先 (Fill First)",
"session_affinity_ttl": "会话粘性 TTL",
"force_model_prefix": "强制模型前缀",
"force_model_prefix_desc": "未带前缀的模型请求只使用无前缀凭据",
"session_affinity": "会话粘性路由",
"ws_auth": "WebSocket 认证",
"ws_auth_desc": "启用 WebSocket 连接认证 (/v1/ws)"
},
+4 -1
View File
@@ -645,7 +645,8 @@
"plan_pro": "專業版",
"plan_max": "Max",
"plan_max5": "Max 5x",
"plan_max20": "Max 20x"
"plan_max20": "Max 20x",
"plan_team": "團隊版"
},
"codex_quota": {
"title": "Codex 配額",
@@ -1259,8 +1260,10 @@
"routing_strategy_hint": "選擇憑證選擇策略",
"strategy_round_robin": "輪詢(Round Robin",
"strategy_fill_first": "填充優先(Fill First",
"session_affinity_ttl": "會話黏性 TTL",
"force_model_prefix": "強制模型前綴",
"force_model_prefix_desc": "未帶前綴的模型請求只使用無前綴憑證",
"session_affinity": "會話黏性路由",
"ws_auth": "WebSocket 驗證",
"ws_auth_desc": "啟用 WebSocket 連線驗證(/v1/ws"
},
+4
View File
@@ -77,6 +77,8 @@ export type VisualConfigValues = {
quotaSwitchPreviewModel: boolean;
quotaAntigravityCredits: boolean;
routingStrategy: 'round-robin' | 'fill-first';
routingSessionAffinity: boolean;
routingSessionAffinityTTL: string;
wsAuth: boolean;
payloadDefaultRules: PayloadRule[];
payloadDefaultRawRules: PayloadRule[];
@@ -117,6 +119,8 @@ export const DEFAULT_VISUAL_VALUES: VisualConfigValues = {
quotaSwitchPreviewModel: true,
quotaAntigravityCredits: true,
routingStrategy: 'round-robin',
routingSessionAffinity: false,
routingSessionAffinityTTL: '',
wsAuth: false,
payloadDefaultRules: [],
payloadDefaultRawRules: [],