diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 9780587..e1766c6 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -419,15 +419,25 @@ "status_toggle_label": "Enabled", "status_enabled_success": "\"{{name}}\" enabled", "status_disabled_success": "\"{{name}}\" disabled", - "prefix_proxy_button": "Edit prefix/proxy_url", - "prefix_proxy_loading": "Loading credential...", - "prefix_proxy_source_label": "Credential JSON", - "prefix_label": "prefix", - "proxy_url_label": "proxy_url", + "prefix_proxy_button": "Edit Auth Fields", + "auth_field_editor_title": "Edit Auth Fields - {{name}}", + "prefix_proxy_loading": "Loading auth file...", + "prefix_proxy_source_label": "Auth file JSON (preview)", + "prefix_label": "Prefix (prefix)", + "proxy_url_label": "Proxy URL (proxy_url)", "prefix_placeholder": "", "proxy_url_placeholder": "socks5://username:password@proxy_ip:port/", - "prefix_proxy_invalid_json": "This credential is not a JSON object and cannot be edited.", - "prefix_proxy_saved_success": "Updated \"{{name}}\" successfully", + "priority_label": "Priority (priority)", + "priority_placeholder": "e.g. 10 or -1", + "priority_hint": "Integers only. Invalid values are ignored. Larger value means higher priority.", + "excluded_models_label": "Excluded models (excluded_models)", + "excluded_models_placeholder": "Comma or newline separated, e.g. model-a, gpt-5-*, *-preview", + "excluded_models_hint": "Saved as an array and normalized by trim/lowercase/dedup/sort.", + "disable_cooling_label": "Disable cooling (disable_cooling)", + "disable_cooling_placeholder": "e.g. true / false / 1 / 0", + "disable_cooling_hint": "Supports booleans, numeric 0/non-0, and strings like true/false/1/0; unparseable values are ignored.", + "prefix_proxy_invalid_json": "This auth file is not a JSON object, so fields cannot be edited.", + "prefix_proxy_saved_success": "Updated auth file \"{{name}}\" successfully", "quota_refresh_success": "Quota refreshed for \"{{name}}\"", "quota_refresh_failed": "Failed to refresh quota for \"{{name}}\": {{message}}" }, diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index 487127f..87b45ab 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -419,15 +419,25 @@ "status_toggle_label": "Включено", "status_enabled_success": "\"{{name}}\" включён", "status_disabled_success": "\"{{name}}\" отключён", - "prefix_proxy_button": "Изменить prefix/proxy_url", - "prefix_proxy_loading": "Загрузка учётных данных...", - "prefix_proxy_source_label": "JSON учётных данных", - "prefix_label": "prefix", - "proxy_url_label": "proxy_url", + "prefix_proxy_button": "Редактировать поля файла авторизации", + "auth_field_editor_title": "Редактировать поля файла авторизации - {{name}}", + "prefix_proxy_loading": "Загрузка файла авторизации...", + "prefix_proxy_source_label": "JSON файла авторизации (предпросмотр)", + "prefix_label": "Префикс (prefix)", + "proxy_url_label": "URL прокси (proxy_url)", "prefix_placeholder": "", "proxy_url_placeholder": "socks5://username:password@proxy_ip:port/", - "prefix_proxy_invalid_json": "Эти учётные данные не являются JSON-объектом и не могут быть изменены.", - "prefix_proxy_saved_success": "\"{{name}}\" успешно обновлён", + "priority_label": "Приоритет (priority)", + "priority_placeholder": "например: 10 или -1", + "priority_hint": "Только целые числа. Некорректные значения игнорируются. Чем больше число, тем выше приоритет.", + "excluded_models_label": "Исключённые модели (excluded_models)", + "excluded_models_placeholder": "Через запятую или с новой строки, например: model-a, gpt-5-*, *-preview", + "excluded_models_hint": "Сохраняется как массив; значения trim/нижний регистр/без дублей/с сортировкой.", + "disable_cooling_label": "Отключение охлаждения (disable_cooling)", + "disable_cooling_placeholder": "например: true / false / 1 / 0", + "disable_cooling_hint": "Поддерживает boolean, числа 0/не 0 и строки true/false/1/0; непарсируемые значения игнорируются.", + "prefix_proxy_invalid_json": "Этот файл авторизации не является JSON-объектом, поэтому поля нельзя редактировать.", + "prefix_proxy_saved_success": "Файл авторизации \"{{name}}\" успешно обновлён", "card_tools_title": "Инструменты", "quota_refresh_single": "Обновить квоту", "quota_refresh_hint": "Обновить квоту только для этих учётных данных", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index f9794e1..a316360 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -419,15 +419,25 @@ "status_toggle_label": "启用", "status_enabled_success": "已启用 \"{{name}}\"", "status_disabled_success": "已停用 \"{{name}}\"", - "prefix_proxy_button": "配置 prefix/proxy_url", - "prefix_proxy_loading": "正在加载凭证文件...", - "prefix_proxy_source_label": "凭证 JSON", - "prefix_label": "prefix", - "proxy_url_label": "proxy_url", + "prefix_proxy_button": "编辑认证文件字段", + "auth_field_editor_title": "编辑认证文件字段 - {{name}}", + "prefix_proxy_loading": "正在加载认证文件...", + "prefix_proxy_source_label": "认证文件 JSON(预览)", + "prefix_label": "前缀(prefix)", + "proxy_url_label": "代理 URL(proxy_url)", "prefix_placeholder": "", "proxy_url_placeholder": "socks5://username:password@proxy_ip:port/", - "prefix_proxy_invalid_json": "该凭证文件不是 JSON 对象,无法编辑。", - "prefix_proxy_saved_success": "已更新 \"{{name}}\"", + "priority_label": "优先级(priority)", + "priority_placeholder": "例如: 10 或 -1", + "priority_hint": "仅支持整数;非法值会被忽略。数值越大优先级越高。", + "excluded_models_label": "排除模型(excluded_models)", + "excluded_models_placeholder": "用逗号或换行分隔,例如: model-a, gpt-5-*, *-preview", + "excluded_models_hint": "保存为数组;会自动 trim、小写、去重并排序。", + "disable_cooling_label": "禁用冷却(disable_cooling)", + "disable_cooling_placeholder": "例如: true / false / 1 / 0", + "disable_cooling_hint": "支持布尔值、0/非0 数字或字符串 true/false/1/0;无法解析时忽略。", + "prefix_proxy_invalid_json": "该认证文件不是 JSON 对象,无法编辑字段。", + "prefix_proxy_saved_success": "已更新认证文件 \"{{name}}\"", "quota_refresh_success": "已刷新 \"{{name}}\" 的额度", "quota_refresh_failed": "刷新 \"{{name}}\" 的额度失败:{{message}}" }, diff --git a/src/pages/AuthFilesPage.tsx b/src/pages/AuthFilesPage.tsx index f1ad0e0..d7504c0 100644 --- a/src/pages/AuthFilesPage.tsx +++ b/src/pages/AuthFilesPage.tsx @@ -95,6 +95,9 @@ const MIN_CARD_PAGE_SIZE = 3; const MAX_CARD_PAGE_SIZE = 30; const MAX_AUTH_FILE_SIZE = 50 * 1024; const AUTH_FILES_UI_STATE_KEY = 'authFilesPage.uiState'; +const INTEGER_STRING_PATTERN = /^[+-]?\d+$/; +const TRUTHY_TEXT_VALUES = new Set(['true', '1', 'yes', 'y', 'on']); +const FALSY_TEXT_VALUES = new Set(['false', '0', 'no', 'n', 'off']); const clampCardPageSize = (value: number) => Math.min(MAX_CARD_PAGE_SIZE, Math.max(MIN_CARD_PAGE_SIZE, Math.round(value))); @@ -208,7 +211,54 @@ interface PrefixProxyEditorState { json: Record | null; prefix: string; proxyUrl: string; + priority: string; + excludedModelsText: string; + disableCooling: string; } + +const parsePriorityValue = (value: unknown): number | undefined => { + if (typeof value === 'number') { + return Number.isInteger(value) ? value : undefined; + } + + if (typeof value !== 'string') return undefined; + const trimmed = value.trim(); + if (!trimmed || !INTEGER_STRING_PATTERN.test(trimmed)) return undefined; + const parsed = Number.parseInt(trimmed, 10); + return Number.isSafeInteger(parsed) ? parsed : undefined; +}; + +const normalizeExcludedModels = (value: unknown): string[] => { + if (!Array.isArray(value)) return []; + + const seen = new Set(); + const normalized: string[] = []; + value.forEach((entry) => { + const model = String(entry ?? '') + .trim() + .toLowerCase(); + if (!model || seen.has(model)) return; + seen.add(model); + normalized.push(model); + }); + + return normalized.sort((a, b) => a.localeCompare(b)); +}; + +const parseExcludedModelsText = (value: string): string[] => + normalizeExcludedModels(value.split(/[\n,]+/)); + +const parseDisableCoolingValue = (value: unknown): boolean | undefined => { + if (typeof value === 'boolean') return value; + if (typeof value === 'number' && Number.isFinite(value)) return value !== 0; + if (typeof value !== 'string') return undefined; + + const normalized = value.trim().toLowerCase(); + if (!normalized) return undefined; + if (TRUTHY_TEXT_VALUES.has(normalized)) return true; + if (FALSY_TEXT_VALUES.has(normalized)) return false; + return undefined; +}; // 标准化 auth_index 值(与 usage.ts 中的 normalizeAuthIndex 保持一致) function normalizeAuthIndexValue(value: unknown): string | null { if (typeof value === 'number' && Number.isFinite(value)) { @@ -441,11 +491,36 @@ export function AuthFilesPage() { if ('proxy_url' in next || prefixProxyEditor.proxyUrl.trim()) { next.proxy_url = prefixProxyEditor.proxyUrl; } + + const parsedPriority = parsePriorityValue(prefixProxyEditor.priority); + if (parsedPriority !== undefined) { + next.priority = parsedPriority; + } else if ('priority' in next) { + delete next.priority; + } + + const excludedModels = parseExcludedModelsText(prefixProxyEditor.excludedModelsText); + if (excludedModels.length > 0) { + next.excluded_models = excludedModels; + } else if ('excluded_models' in next) { + delete next.excluded_models; + } + + const parsedDisableCooling = parseDisableCoolingValue(prefixProxyEditor.disableCooling); + if (parsedDisableCooling !== undefined) { + next.disable_cooling = parsedDisableCooling; + } else if ('disable_cooling' in next) { + delete next.disable_cooling; + } + return JSON.stringify(next); }, [ prefixProxyEditor?.json, prefixProxyEditor?.prefix, prefixProxyEditor?.proxyUrl, + prefixProxyEditor?.priority, + prefixProxyEditor?.excludedModelsText, + prefixProxyEditor?.disableCooling, prefixProxyEditor?.rawText, ]); @@ -857,6 +932,9 @@ export function AuthFilesPage() { json: null, prefix: '', proxyUrl: '', + priority: '', + excludedModelsText: '', + disableCooling: '', }); try { @@ -898,6 +976,9 @@ export function AuthFilesPage() { const originalText = JSON.stringify(json); const prefix = typeof json.prefix === 'string' ? json.prefix : ''; const proxyUrl = typeof json.proxy_url === 'string' ? json.proxy_url : ''; + const priority = parsePriorityValue(json.priority); + const excludedModels = normalizeExcludedModels(json.excluded_models); + const disableCooling = parseDisableCoolingValue(json.disable_cooling); setPrefixProxyEditor((prev) => { if (!prev || prev.fileName !== name) return prev; @@ -909,6 +990,10 @@ export function AuthFilesPage() { json, prefix, proxyUrl, + priority: priority !== undefined ? String(priority) : '', + excludedModelsText: excludedModels.join('\n'), + disableCooling: + disableCooling === undefined ? '' : disableCooling ? 'true' : 'false', error: null, }; }); @@ -922,11 +1007,17 @@ export function AuthFilesPage() { } }; - const handlePrefixProxyChange = (field: 'prefix' | 'proxyUrl', value: string) => { + const handlePrefixProxyChange = ( + field: 'prefix' | 'proxyUrl' | 'priority' | 'excludedModelsText' | 'disableCooling', + value: string + ) => { setPrefixProxyEditor((prev) => { if (!prev) return prev; if (field === 'prefix') return { ...prev, prefix: value }; - return { ...prev, proxyUrl: value }; + if (field === 'proxyUrl') return { ...prev, proxyUrl: value }; + if (field === 'priority') return { ...prev, priority: value }; + if (field === 'excludedModelsText') return { ...prev, excludedModelsText: value }; + return { ...prev, disableCooling: value }; }); }; @@ -2171,7 +2262,7 @@ export function AuthFilesPage() { )} - {/* prefix/proxy_url 编辑弹窗 */} + {/* 认证文件字段编辑弹窗 */} setPrefixProxyEditor(null)} @@ -2179,7 +2270,7 @@ export function AuthFilesPage() { width={720} title={ prefixProxyEditor?.fileName - ? `${t('auth_files.prefix_proxy_button')} - ${prefixProxyEditor.fileName}` + ? t('auth_files.auth_field_editor_title', { name: prefixProxyEditor.fileName }) : t('auth_files.prefix_proxy_button') } footer={ @@ -2247,6 +2338,42 @@ export function AuthFilesPage() { } onChange={(e) => handlePrefixProxyChange('proxyUrl', e.target.value)} /> + handlePrefixProxyChange('priority', e.target.value)} + /> +
+ +