mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-19 11:10:49 +08:00
feat(auth-files): support editing priority/excluded_models/disable_cooling and localize auth field editor
This commit is contained in:
@@ -419,15 +419,25 @@
|
|||||||
"status_toggle_label": "Enabled",
|
"status_toggle_label": "Enabled",
|
||||||
"status_enabled_success": "\"{{name}}\" enabled",
|
"status_enabled_success": "\"{{name}}\" enabled",
|
||||||
"status_disabled_success": "\"{{name}}\" disabled",
|
"status_disabled_success": "\"{{name}}\" disabled",
|
||||||
"prefix_proxy_button": "Edit prefix/proxy_url",
|
"prefix_proxy_button": "Edit Auth Fields",
|
||||||
"prefix_proxy_loading": "Loading credential...",
|
"auth_field_editor_title": "Edit Auth Fields - {{name}}",
|
||||||
"prefix_proxy_source_label": "Credential JSON",
|
"prefix_proxy_loading": "Loading auth file...",
|
||||||
"prefix_label": "prefix",
|
"prefix_proxy_source_label": "Auth file JSON (preview)",
|
||||||
"proxy_url_label": "proxy_url",
|
"prefix_label": "Prefix (prefix)",
|
||||||
|
"proxy_url_label": "Proxy URL (proxy_url)",
|
||||||
"prefix_placeholder": "",
|
"prefix_placeholder": "",
|
||||||
"proxy_url_placeholder": "socks5://username:password@proxy_ip:port/",
|
"proxy_url_placeholder": "socks5://username:password@proxy_ip:port/",
|
||||||
"prefix_proxy_invalid_json": "This credential is not a JSON object and cannot be edited.",
|
"priority_label": "Priority (priority)",
|
||||||
"prefix_proxy_saved_success": "Updated \"{{name}}\" successfully",
|
"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_success": "Quota refreshed for \"{{name}}\"",
|
||||||
"quota_refresh_failed": "Failed to refresh quota for \"{{name}}\": {{message}}"
|
"quota_refresh_failed": "Failed to refresh quota for \"{{name}}\": {{message}}"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -419,15 +419,25 @@
|
|||||||
"status_toggle_label": "Включено",
|
"status_toggle_label": "Включено",
|
||||||
"status_enabled_success": "\"{{name}}\" включён",
|
"status_enabled_success": "\"{{name}}\" включён",
|
||||||
"status_disabled_success": "\"{{name}}\" отключён",
|
"status_disabled_success": "\"{{name}}\" отключён",
|
||||||
"prefix_proxy_button": "Изменить prefix/proxy_url",
|
"prefix_proxy_button": "Редактировать поля файла авторизации",
|
||||||
"prefix_proxy_loading": "Загрузка учётных данных...",
|
"auth_field_editor_title": "Редактировать поля файла авторизации - {{name}}",
|
||||||
"prefix_proxy_source_label": "JSON учётных данных",
|
"prefix_proxy_loading": "Загрузка файла авторизации...",
|
||||||
"prefix_label": "prefix",
|
"prefix_proxy_source_label": "JSON файла авторизации (предпросмотр)",
|
||||||
"proxy_url_label": "proxy_url",
|
"prefix_label": "Префикс (prefix)",
|
||||||
|
"proxy_url_label": "URL прокси (proxy_url)",
|
||||||
"prefix_placeholder": "",
|
"prefix_placeholder": "",
|
||||||
"proxy_url_placeholder": "socks5://username:password@proxy_ip:port/",
|
"proxy_url_placeholder": "socks5://username:password@proxy_ip:port/",
|
||||||
"prefix_proxy_invalid_json": "Эти учётные данные не являются JSON-объектом и не могут быть изменены.",
|
"priority_label": "Приоритет (priority)",
|
||||||
"prefix_proxy_saved_success": "\"{{name}}\" успешно обновлён",
|
"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": "Инструменты",
|
"card_tools_title": "Инструменты",
|
||||||
"quota_refresh_single": "Обновить квоту",
|
"quota_refresh_single": "Обновить квоту",
|
||||||
"quota_refresh_hint": "Обновить квоту только для этих учётных данных",
|
"quota_refresh_hint": "Обновить квоту только для этих учётных данных",
|
||||||
|
|||||||
@@ -419,15 +419,25 @@
|
|||||||
"status_toggle_label": "启用",
|
"status_toggle_label": "启用",
|
||||||
"status_enabled_success": "已启用 \"{{name}}\"",
|
"status_enabled_success": "已启用 \"{{name}}\"",
|
||||||
"status_disabled_success": "已停用 \"{{name}}\"",
|
"status_disabled_success": "已停用 \"{{name}}\"",
|
||||||
"prefix_proxy_button": "配置 prefix/proxy_url",
|
"prefix_proxy_button": "编辑认证文件字段",
|
||||||
"prefix_proxy_loading": "正在加载凭证文件...",
|
"auth_field_editor_title": "编辑认证文件字段 - {{name}}",
|
||||||
"prefix_proxy_source_label": "凭证 JSON",
|
"prefix_proxy_loading": "正在加载认证文件...",
|
||||||
"prefix_label": "prefix",
|
"prefix_proxy_source_label": "认证文件 JSON(预览)",
|
||||||
"proxy_url_label": "proxy_url",
|
"prefix_label": "前缀(prefix)",
|
||||||
|
"proxy_url_label": "代理 URL(proxy_url)",
|
||||||
"prefix_placeholder": "",
|
"prefix_placeholder": "",
|
||||||
"proxy_url_placeholder": "socks5://username:password@proxy_ip:port/",
|
"proxy_url_placeholder": "socks5://username:password@proxy_ip:port/",
|
||||||
"prefix_proxy_invalid_json": "该凭证文件不是 JSON 对象,无法编辑。",
|
"priority_label": "优先级(priority)",
|
||||||
"prefix_proxy_saved_success": "已更新 \"{{name}}\"",
|
"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_success": "已刷新 \"{{name}}\" 的额度",
|
||||||
"quota_refresh_failed": "刷新 \"{{name}}\" 的额度失败:{{message}}"
|
"quota_refresh_failed": "刷新 \"{{name}}\" 的额度失败:{{message}}"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -95,6 +95,9 @@ const MIN_CARD_PAGE_SIZE = 3;
|
|||||||
const MAX_CARD_PAGE_SIZE = 30;
|
const MAX_CARD_PAGE_SIZE = 30;
|
||||||
const MAX_AUTH_FILE_SIZE = 50 * 1024;
|
const MAX_AUTH_FILE_SIZE = 50 * 1024;
|
||||||
const AUTH_FILES_UI_STATE_KEY = 'authFilesPage.uiState';
|
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) =>
|
const clampCardPageSize = (value: number) =>
|
||||||
Math.min(MAX_CARD_PAGE_SIZE, Math.max(MIN_CARD_PAGE_SIZE, Math.round(value)));
|
Math.min(MAX_CARD_PAGE_SIZE, Math.max(MIN_CARD_PAGE_SIZE, Math.round(value)));
|
||||||
@@ -208,7 +211,54 @@ interface PrefixProxyEditorState {
|
|||||||
json: Record<string, unknown> | null;
|
json: Record<string, unknown> | null;
|
||||||
prefix: string;
|
prefix: string;
|
||||||
proxyUrl: 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<string>();
|
||||||
|
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 保持一致)
|
// 标准化 auth_index 值(与 usage.ts 中的 normalizeAuthIndex 保持一致)
|
||||||
function normalizeAuthIndexValue(value: unknown): string | null {
|
function normalizeAuthIndexValue(value: unknown): string | null {
|
||||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||||
@@ -441,11 +491,36 @@ export function AuthFilesPage() {
|
|||||||
if ('proxy_url' in next || prefixProxyEditor.proxyUrl.trim()) {
|
if ('proxy_url' in next || prefixProxyEditor.proxyUrl.trim()) {
|
||||||
next.proxy_url = prefixProxyEditor.proxyUrl;
|
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);
|
return JSON.stringify(next);
|
||||||
}, [
|
}, [
|
||||||
prefixProxyEditor?.json,
|
prefixProxyEditor?.json,
|
||||||
prefixProxyEditor?.prefix,
|
prefixProxyEditor?.prefix,
|
||||||
prefixProxyEditor?.proxyUrl,
|
prefixProxyEditor?.proxyUrl,
|
||||||
|
prefixProxyEditor?.priority,
|
||||||
|
prefixProxyEditor?.excludedModelsText,
|
||||||
|
prefixProxyEditor?.disableCooling,
|
||||||
prefixProxyEditor?.rawText,
|
prefixProxyEditor?.rawText,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -857,6 +932,9 @@ export function AuthFilesPage() {
|
|||||||
json: null,
|
json: null,
|
||||||
prefix: '',
|
prefix: '',
|
||||||
proxyUrl: '',
|
proxyUrl: '',
|
||||||
|
priority: '',
|
||||||
|
excludedModelsText: '',
|
||||||
|
disableCooling: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -898,6 +976,9 @@ export function AuthFilesPage() {
|
|||||||
const originalText = JSON.stringify(json);
|
const originalText = JSON.stringify(json);
|
||||||
const prefix = typeof json.prefix === 'string' ? json.prefix : '';
|
const prefix = typeof json.prefix === 'string' ? json.prefix : '';
|
||||||
const proxyUrl = typeof json.proxy_url === 'string' ? json.proxy_url : '';
|
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) => {
|
setPrefixProxyEditor((prev) => {
|
||||||
if (!prev || prev.fileName !== name) return prev;
|
if (!prev || prev.fileName !== name) return prev;
|
||||||
@@ -909,6 +990,10 @@ export function AuthFilesPage() {
|
|||||||
json,
|
json,
|
||||||
prefix,
|
prefix,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
|
priority: priority !== undefined ? String(priority) : '',
|
||||||
|
excludedModelsText: excludedModels.join('\n'),
|
||||||
|
disableCooling:
|
||||||
|
disableCooling === undefined ? '' : disableCooling ? 'true' : 'false',
|
||||||
error: null,
|
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) => {
|
setPrefixProxyEditor((prev) => {
|
||||||
if (!prev) return prev;
|
if (!prev) return prev;
|
||||||
if (field === 'prefix') return { ...prev, prefix: value };
|
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() {
|
|||||||
)}
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{/* prefix/proxy_url 编辑弹窗 */}
|
{/* 认证文件字段编辑弹窗 */}
|
||||||
<Modal
|
<Modal
|
||||||
open={Boolean(prefixProxyEditor)}
|
open={Boolean(prefixProxyEditor)}
|
||||||
onClose={() => setPrefixProxyEditor(null)}
|
onClose={() => setPrefixProxyEditor(null)}
|
||||||
@@ -2179,7 +2270,7 @@ export function AuthFilesPage() {
|
|||||||
width={720}
|
width={720}
|
||||||
title={
|
title={
|
||||||
prefixProxyEditor?.fileName
|
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')
|
: t('auth_files.prefix_proxy_button')
|
||||||
}
|
}
|
||||||
footer={
|
footer={
|
||||||
@@ -2247,6 +2338,42 @@ export function AuthFilesPage() {
|
|||||||
}
|
}
|
||||||
onChange={(e) => handlePrefixProxyChange('proxyUrl', e.target.value)}
|
onChange={(e) => handlePrefixProxyChange('proxyUrl', e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
<Input
|
||||||
|
label={t('auth_files.priority_label')}
|
||||||
|
value={prefixProxyEditor.priority}
|
||||||
|
placeholder={t('auth_files.priority_placeholder')}
|
||||||
|
hint={t('auth_files.priority_hint')}
|
||||||
|
disabled={
|
||||||
|
disableControls || prefixProxyEditor.saving || !prefixProxyEditor.json
|
||||||
|
}
|
||||||
|
onChange={(e) => handlePrefixProxyChange('priority', e.target.value)}
|
||||||
|
/>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('auth_files.excluded_models_label')}</label>
|
||||||
|
<textarea
|
||||||
|
className="input"
|
||||||
|
value={prefixProxyEditor.excludedModelsText}
|
||||||
|
placeholder={t('auth_files.excluded_models_placeholder')}
|
||||||
|
rows={4}
|
||||||
|
disabled={
|
||||||
|
disableControls || prefixProxyEditor.saving || !prefixProxyEditor.json
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
handlePrefixProxyChange('excludedModelsText', e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div className="hint">{t('auth_files.excluded_models_hint')}</div>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
label={t('auth_files.disable_cooling_label')}
|
||||||
|
value={prefixProxyEditor.disableCooling}
|
||||||
|
placeholder={t('auth_files.disable_cooling_placeholder')}
|
||||||
|
hint={t('auth_files.disable_cooling_hint')}
|
||||||
|
disabled={
|
||||||
|
disableControls || prefixProxyEditor.saving || !prefixProxyEditor.json
|
||||||
|
}
|
||||||
|
onChange={(e) => handlePrefixProxyChange('disableCooling', e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user