mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
Compare commits
1 Commits
@@ -2,7 +2,14 @@ import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
|
||||
import { ToggleSwitch } from '@/components/ui/ToggleSwitch';
|
||||
import { IconBot, IconCheck, IconCode, IconDownload, IconInfo, IconTrash2 } from '@/components/ui/icons';
|
||||
import {
|
||||
IconBot,
|
||||
IconCheck,
|
||||
IconCode,
|
||||
IconDownload,
|
||||
IconInfo,
|
||||
IconTrash2,
|
||||
} from '@/components/ui/icons';
|
||||
import { ProviderStatusBar } from '@/components/providers/ProviderStatusBar';
|
||||
import type { AuthFileItem } from '@/types';
|
||||
import { resolveAuthProvider } from '@/utils/quota';
|
||||
@@ -16,7 +23,7 @@ import {
|
||||
isRuntimeOnlyAuthFile,
|
||||
resolveAuthFileStats,
|
||||
type QuotaProviderType,
|
||||
type ResolvedTheme
|
||||
type ResolvedTheme,
|
||||
} from '@/features/authFiles/constants';
|
||||
import type { AuthFileStatusBarData } from '@/features/authFiles/hooks/useAuthFilesStatusBarCache';
|
||||
import { AuthFileQuotaSection } from '@/features/authFiles/components/AuthFileQuotaSection';
|
||||
@@ -37,7 +44,7 @@ export type AuthFileCardProps = {
|
||||
onShowModels: (file: AuthFileItem) => void;
|
||||
onShowDetails: (file: AuthFileItem) => void;
|
||||
onDownload: (name: string) => void;
|
||||
onOpenPrefixProxyEditor: (name: string) => void;
|
||||
onOpenPrefixProxyEditor: (file: AuthFileItem) => void;
|
||||
onDelete: (name: string) => void;
|
||||
onToggleStatus: (file: AuthFileItem, enabled: boolean) => void;
|
||||
onToggleSelect: (name: string) => void;
|
||||
@@ -67,7 +74,7 @@ export function AuthFileCard(props: AuthFileCardProps) {
|
||||
onOpenPrefixProxyEditor,
|
||||
onDelete,
|
||||
onToggleStatus,
|
||||
onToggleSelect
|
||||
onToggleSelect,
|
||||
} = props;
|
||||
|
||||
const fileStats = resolveAuthFileStats(file, keyStats);
|
||||
@@ -110,7 +117,9 @@ export function AuthFileCard(props: AuthFileCardProps) {
|
||||
type="button"
|
||||
className={`${styles.selectionToggle} ${selected ? styles.selectionToggleActive : ''}`}
|
||||
onClick={() => onToggleSelect(file.name)}
|
||||
aria-label={selected ? t('auth_files.batch_deselect') : t('auth_files.batch_select_all')}
|
||||
aria-label={
|
||||
selected ? t('auth_files.batch_deselect') : t('auth_files.batch_select_all')
|
||||
}
|
||||
aria-pressed={selected}
|
||||
title={selected ? t('auth_files.batch_deselect') : t('auth_files.batch_select_all')}
|
||||
>
|
||||
@@ -122,7 +131,7 @@ export function AuthFileCard(props: AuthFileCardProps) {
|
||||
style={{
|
||||
backgroundColor: typeColor.bg,
|
||||
color: typeColor.text,
|
||||
...(typeColor.border ? { border: typeColor.border } : {})
|
||||
...(typeColor.border ? { border: typeColor.border } : {}),
|
||||
}}
|
||||
>
|
||||
{getTypeLabel(t, file.type || 'unknown')}
|
||||
@@ -157,7 +166,11 @@ export function AuthFileCard(props: AuthFileCardProps) {
|
||||
<ProviderStatusBar statusData={statusData} styles={styles} />
|
||||
|
||||
{showQuotaLayout && quotaType && (
|
||||
<AuthFileQuotaSection file={file} quotaType={quotaType} disableControls={disableControls} />
|
||||
<AuthFileQuotaSection
|
||||
file={file}
|
||||
quotaType={quotaType}
|
||||
disableControls={disableControls}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={styles.cardActions}>
|
||||
@@ -198,7 +211,7 @@ export function AuthFileCard(props: AuthFileCardProps) {
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => onOpenPrefixProxyEditor(file.name)}
|
||||
onClick={() => onOpenPrefixProxyEditor(file)}
|
||||
className={styles.iconButton}
|
||||
title={t('auth_files.prefix_proxy_button')}
|
||||
disabled={disableControls}
|
||||
@@ -232,7 +245,9 @@ export function AuthFileCard(props: AuthFileCardProps) {
|
||||
</div>
|
||||
)}
|
||||
{isRuntimeOnly && (
|
||||
<div className={styles.virtualBadge}>{t('auth_files.type_virtual') || '虚拟认证文件'}</div>
|
||||
<div className={styles.virtualBadge}>
|
||||
{t('auth_files.type_virtual') || '虚拟认证文件'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,11 @@ import { Modal } from '@/components/ui/Modal';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { ToggleSwitch } from '@/components/ui/ToggleSwitch';
|
||||
import type {
|
||||
PrefixProxyEditorField,
|
||||
PrefixProxyEditorState
|
||||
PrefixProxyEditorFieldValue,
|
||||
PrefixProxyEditorState,
|
||||
} from '@/features/authFiles/hooks/useAuthFilesPrefixProxyEditor';
|
||||
import styles from '@/pages/AuthFilesPage.module.scss';
|
||||
|
||||
@@ -16,7 +18,7 @@ export type AuthFilesPrefixProxyEditorModalProps = {
|
||||
dirty: boolean;
|
||||
onClose: () => void;
|
||||
onSave: () => void;
|
||||
onChange: (field: PrefixProxyEditorField, value: string) => void;
|
||||
onChange: (field: PrefixProxyEditorField, value: PrefixProxyEditorFieldValue) => void;
|
||||
};
|
||||
|
||||
export function AuthFilesPrefixProxyEditorModal(props: AuthFilesPrefixProxyEditorModalProps) {
|
||||
@@ -42,9 +44,7 @@ export function AuthFilesPrefixProxyEditorModal(props: AuthFilesPrefixProxyEdito
|
||||
<Button
|
||||
onClick={onSave}
|
||||
loading={editor?.saving === true}
|
||||
disabled={
|
||||
disableControls || editor?.saving === true || !dirty || !editor?.json
|
||||
}
|
||||
disabled={disableControls || editor?.saving === true || !dirty || !editor?.json}
|
||||
>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
@@ -114,6 +114,18 @@ export function AuthFilesPrefixProxyEditorModal(props: AuthFilesPrefixProxyEdito
|
||||
disabled={disableControls || editor.saving || !editor.json}
|
||||
onChange={(e) => onChange('disableCooling', e.target.value)}
|
||||
/>
|
||||
{editor.isCodexFile && (
|
||||
<div className="form-group">
|
||||
<label>{t('ai_providers.codex_websockets_label')}</label>
|
||||
<ToggleSwitch
|
||||
checked={Boolean(editor.websocket)}
|
||||
disabled={disableControls || editor.saving || !editor.json}
|
||||
ariaLabel={t('ai_providers.codex_websockets_label')}
|
||||
onChange={(value) => onChange('websocket', value)}
|
||||
/>
|
||||
<div className="hint">{t('ai_providers.codex_websockets_hint')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -122,4 +134,3 @@ export function AuthFilesPrefixProxyEditorModal(props: AuthFilesPrefixProxyEdito
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { authFilesApi } from '@/services/api';
|
||||
import type { AuthFileItem } from '@/types';
|
||||
import { useNotificationStore } from '@/stores';
|
||||
import { formatFileSize } from '@/utils/format';
|
||||
import { MAX_AUTH_FILE_SIZE } from '@/utils/constants';
|
||||
@@ -8,7 +9,7 @@ import {
|
||||
normalizeExcludedModels,
|
||||
parseDisableCoolingValue,
|
||||
parseExcludedModelsText,
|
||||
parsePriorityValue
|
||||
parsePriorityValue,
|
||||
} from '@/features/authFiles/constants';
|
||||
|
||||
export type PrefixProxyEditorField =
|
||||
@@ -16,10 +17,14 @@ export type PrefixProxyEditorField =
|
||||
| 'proxyUrl'
|
||||
| 'priority'
|
||||
| 'excludedModelsText'
|
||||
| 'disableCooling';
|
||||
| 'disableCooling'
|
||||
| 'websocket';
|
||||
|
||||
export type PrefixProxyEditorFieldValue = string | boolean;
|
||||
|
||||
export type PrefixProxyEditorState = {
|
||||
fileName: string;
|
||||
isCodexFile: boolean;
|
||||
loading: boolean;
|
||||
saving: boolean;
|
||||
error: string | null;
|
||||
@@ -31,6 +36,7 @@ export type PrefixProxyEditorState = {
|
||||
priority: string;
|
||||
excludedModelsText: string;
|
||||
disableCooling: string;
|
||||
websocket: boolean;
|
||||
};
|
||||
|
||||
export type UseAuthFilesPrefixProxyEditorOptions = {
|
||||
@@ -43,9 +49,12 @@ export type UseAuthFilesPrefixProxyEditorResult = {
|
||||
prefixProxyEditor: PrefixProxyEditorState | null;
|
||||
prefixProxyUpdatedText: string;
|
||||
prefixProxyDirty: boolean;
|
||||
openPrefixProxyEditor: (name: string) => Promise<void>;
|
||||
openPrefixProxyEditor: (file: Pick<AuthFileItem, 'name' | 'type' | 'provider'>) => Promise<void>;
|
||||
closePrefixProxyEditor: () => void;
|
||||
handlePrefixProxyChange: (field: PrefixProxyEditorField, value: string) => void;
|
||||
handlePrefixProxyChange: (
|
||||
field: PrefixProxyEditorField,
|
||||
value: PrefixProxyEditorFieldValue
|
||||
) => void;
|
||||
handlePrefixProxySave: () => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -80,6 +89,10 @@ const buildPrefixProxyUpdatedText = (editor: PrefixProxyEditorState | null): str
|
||||
delete next.disable_cooling;
|
||||
}
|
||||
|
||||
if (editor.isCodexFile) {
|
||||
next.websocket = editor.websocket;
|
||||
}
|
||||
|
||||
return JSON.stringify(next);
|
||||
};
|
||||
|
||||
@@ -102,7 +115,16 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
setPrefixProxyEditor(null);
|
||||
};
|
||||
|
||||
const openPrefixProxyEditor = async (name: string) => {
|
||||
const openPrefixProxyEditor = async (file: Pick<AuthFileItem, 'name' | 'type' | 'provider'>) => {
|
||||
const name = file.name;
|
||||
const normalizedType = String(file.type ?? '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const normalizedProvider = String(file.provider ?? '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const isCodexFile = normalizedType === 'codex' || normalizedProvider === 'codex';
|
||||
|
||||
if (disableControls) return;
|
||||
if (prefixProxyEditor?.fileName === name) {
|
||||
setPrefixProxyEditor(null);
|
||||
@@ -111,6 +133,7 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
|
||||
setPrefixProxyEditor({
|
||||
fileName: name,
|
||||
isCodexFile,
|
||||
loading: true,
|
||||
saving: false,
|
||||
error: null,
|
||||
@@ -121,7 +144,8 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
proxyUrl: '',
|
||||
priority: '',
|
||||
excludedModelsText: '',
|
||||
disableCooling: ''
|
||||
disableCooling: '',
|
||||
websocket: false,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -139,7 +163,7 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
loading: false,
|
||||
error: t('auth_files.prefix_proxy_invalid_json'),
|
||||
rawText: trimmed,
|
||||
originalText: trimmed
|
||||
originalText: trimmed,
|
||||
};
|
||||
});
|
||||
return;
|
||||
@@ -153,19 +177,24 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
loading: false,
|
||||
error: t('auth_files.prefix_proxy_invalid_json'),
|
||||
rawText: trimmed,
|
||||
originalText: trimmed
|
||||
originalText: trimmed,
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const json = parsed as Record<string, unknown>;
|
||||
const json = { ...(parsed as Record<string, unknown>) };
|
||||
if (isCodexFile) {
|
||||
const websocketValue = parseDisableCoolingValue(json.websocket);
|
||||
json.websocket = websocketValue ?? false;
|
||||
}
|
||||
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 disableCoolingValue = parseDisableCoolingValue(json.disable_cooling);
|
||||
const websocketValue = parseDisableCoolingValue(json.websocket);
|
||||
|
||||
setPrefixProxyEditor((prev) => {
|
||||
if (!prev || prev.fileName !== name) return prev;
|
||||
@@ -181,7 +210,8 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
excludedModelsText: excludedModels.join('\n'),
|
||||
disableCooling:
|
||||
disableCoolingValue === undefined ? '' : disableCoolingValue ? 'true' : 'false',
|
||||
error: null
|
||||
websocket: websocketValue ?? false,
|
||||
error: null,
|
||||
};
|
||||
});
|
||||
} catch (err: unknown) {
|
||||
@@ -194,14 +224,18 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrefixProxyChange = (field: PrefixProxyEditorField, value: string) => {
|
||||
const handlePrefixProxyChange = (
|
||||
field: PrefixProxyEditorField,
|
||||
value: PrefixProxyEditorFieldValue
|
||||
) => {
|
||||
setPrefixProxyEditor((prev) => {
|
||||
if (!prev) return prev;
|
||||
if (field === 'prefix') return { ...prev, prefix: 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 };
|
||||
if (field === 'prefix') return { ...prev, prefix: String(value) };
|
||||
if (field === 'proxyUrl') return { ...prev, proxyUrl: String(value) };
|
||||
if (field === 'priority') return { ...prev, priority: String(value) };
|
||||
if (field === 'excludedModelsText') return { ...prev, excludedModelsText: String(value) };
|
||||
if (field === 'disableCooling') return { ...prev, disableCooling: String(value) };
|
||||
return { ...prev, websocket: Boolean(value) };
|
||||
});
|
||||
};
|
||||
|
||||
@@ -249,6 +283,6 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
openPrefixProxyEditor,
|
||||
closePrefixProxyEditor,
|
||||
handlePrefixProxyChange,
|
||||
handlePrefixProxySave
|
||||
handlePrefixProxySave,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user