mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 03:10:50 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73ee6eb2f3 | ||
|
|
161d5d1e7f | ||
|
|
3cbd04b296 | ||
|
|
859f7f120c | ||
|
|
fea29f7318 | ||
|
|
f663b83ac8 |
14
package-lock.json
generated
14
package-lock.json
generated
@@ -3780,9 +3780,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "7.10.1",
|
"version": "7.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz",
|
||||||
"integrity": "sha512-gHL89dRa3kwlUYtRQ+m8NmxGI6CgqN+k4XyGjwcFoQwwCWF6xXpOCUlDovkXClS0d0XJN/5q7kc5W3kiFEd0Yw==",
|
"integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cookie": "^1.0.1",
|
"cookie": "^1.0.1",
|
||||||
@@ -3802,12 +3802,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router-dom": {
|
"node_modules/react-router-dom": {
|
||||||
"version": "7.10.1",
|
"version": "7.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz",
|
||||||
"integrity": "sha512-JNBANI6ChGVjA5bwsUIwJk7LHKmqB4JYnYfzFwyp2t12Izva11elds2jx7Yfoup2zssedntwU0oZ5DEmk5Sdaw==",
|
"integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react-router": "7.10.1"
|
"react-router": "7.12.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Input } from '@/components/ui/Input';
|
|||||||
import { Modal } from '@/components/ui/Modal';
|
import { Modal } from '@/components/ui/Modal';
|
||||||
import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList';
|
import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList';
|
||||||
import type { ProviderKeyConfig } from '@/types';
|
import type { ProviderKeyConfig } from '@/types';
|
||||||
import { buildHeaderObject, headersToEntries } from '@/utils/headers';
|
import { headersToEntries } from '@/utils/headers';
|
||||||
import { excludedModelsToText } from '../utils';
|
import { excludedModelsToText } from '../utils';
|
||||||
import type { ProviderFormState, ProviderModalProps } from '../types';
|
import type { ProviderFormState, ProviderModalProps } from '../types';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ const buildEmptyForm = (): ProviderFormState => ({
|
|||||||
prefix: '',
|
prefix: '',
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
proxyUrl: '',
|
proxyUrl: '',
|
||||||
headers: {},
|
headers: [],
|
||||||
models: [],
|
models: [],
|
||||||
excludedModels: [],
|
excludedModels: [],
|
||||||
modelEntries: [{ name: '', alias: '' }],
|
modelEntries: [{ name: '', alias: '' }],
|
||||||
@@ -43,7 +43,7 @@ export function ClaudeModal({
|
|||||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
setForm({
|
setForm({
|
||||||
...initialData,
|
...initialData,
|
||||||
headers: initialData.headers ?? {},
|
headers: headersToEntries(initialData.headers),
|
||||||
modelEntries: modelsToEntries(initialData.models),
|
modelEntries: modelsToEntries(initialData.models),
|
||||||
excludedText: excludedModelsToText(initialData.excludedModels),
|
excludedText: excludedModelsToText(initialData.excludedModels),
|
||||||
});
|
});
|
||||||
@@ -95,8 +95,8 @@ export function ClaudeModal({
|
|||||||
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
|
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
|
||||||
/>
|
/>
|
||||||
<HeaderInputList
|
<HeaderInputList
|
||||||
entries={headersToEntries(form.headers)}
|
entries={form.headers}
|
||||||
onChange={(entries) => setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))}
|
onChange={(entries) => setForm((prev) => ({ ...prev, headers: entries }))}
|
||||||
addLabel={t('common.custom_headers_add')}
|
addLabel={t('common.custom_headers_add')}
|
||||||
keyPlaceholder={t('common.custom_headers_key_placeholder')}
|
keyPlaceholder={t('common.custom_headers_key_placeholder')}
|
||||||
valuePlaceholder={t('common.custom_headers_value_placeholder')}
|
valuePlaceholder={t('common.custom_headers_value_placeholder')}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { HeaderInputList } from '@/components/ui/HeaderInputList';
|
|||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { Modal } from '@/components/ui/Modal';
|
import { Modal } from '@/components/ui/Modal';
|
||||||
import type { ProviderKeyConfig } from '@/types';
|
import type { ProviderKeyConfig } from '@/types';
|
||||||
import { buildHeaderObject, headersToEntries } from '@/utils/headers';
|
import { headersToEntries } from '@/utils/headers';
|
||||||
import { modelsToEntries } from '@/components/ui/ModelInputList';
|
import { modelsToEntries } from '@/components/ui/ModelInputList';
|
||||||
import { excludedModelsToText } from '../utils';
|
import { excludedModelsToText } from '../utils';
|
||||||
import type { ProviderFormState, ProviderModalProps } from '../types';
|
import type { ProviderFormState, ProviderModalProps } from '../types';
|
||||||
@@ -19,7 +19,7 @@ const buildEmptyForm = (): ProviderFormState => ({
|
|||||||
prefix: '',
|
prefix: '',
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
proxyUrl: '',
|
proxyUrl: '',
|
||||||
headers: {},
|
headers: [],
|
||||||
models: [],
|
models: [],
|
||||||
excludedModels: [],
|
excludedModels: [],
|
||||||
modelEntries: [{ name: '', alias: '' }],
|
modelEntries: [{ name: '', alias: '' }],
|
||||||
@@ -43,7 +43,7 @@ export function CodexModal({
|
|||||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
setForm({
|
setForm({
|
||||||
...initialData,
|
...initialData,
|
||||||
headers: initialData.headers ?? {},
|
headers: headersToEntries(initialData.headers),
|
||||||
modelEntries: modelsToEntries(initialData.models),
|
modelEntries: modelsToEntries(initialData.models),
|
||||||
excludedText: excludedModelsToText(initialData.excludedModels),
|
excludedText: excludedModelsToText(initialData.excludedModels),
|
||||||
});
|
});
|
||||||
@@ -95,8 +95,8 @@ export function CodexModal({
|
|||||||
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
|
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
|
||||||
/>
|
/>
|
||||||
<HeaderInputList
|
<HeaderInputList
|
||||||
entries={headersToEntries(form.headers)}
|
entries={form.headers}
|
||||||
onChange={(entries) => setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))}
|
onChange={(entries) => setForm((prev) => ({ ...prev, headers: entries }))}
|
||||||
addLabel={t('common.custom_headers_add')}
|
addLabel={t('common.custom_headers_add')}
|
||||||
keyPlaceholder={t('common.custom_headers_key_placeholder')}
|
keyPlaceholder={t('common.custom_headers_key_placeholder')}
|
||||||
valuePlaceholder={t('common.custom_headers_value_placeholder')}
|
valuePlaceholder={t('common.custom_headers_value_placeholder')}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { HeaderInputList } from '@/components/ui/HeaderInputList';
|
|||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { Modal } from '@/components/ui/Modal';
|
import { Modal } from '@/components/ui/Modal';
|
||||||
import type { GeminiKeyConfig } from '@/types';
|
import type { GeminiKeyConfig } from '@/types';
|
||||||
import { buildHeaderObject, headersToEntries } from '@/utils/headers';
|
import { headersToEntries } from '@/utils/headers';
|
||||||
import { excludedModelsToText } from '../utils';
|
import { excludedModelsToText } from '../utils';
|
||||||
import type { GeminiFormState, ProviderModalProps } from '../types';
|
import type { GeminiFormState, ProviderModalProps } from '../types';
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ const buildEmptyForm = (): GeminiFormState => ({
|
|||||||
apiKey: '',
|
apiKey: '',
|
||||||
prefix: '',
|
prefix: '',
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
headers: {},
|
headers: [],
|
||||||
excludedModels: [],
|
excludedModels: [],
|
||||||
excludedText: '',
|
excludedText: '',
|
||||||
});
|
});
|
||||||
@@ -39,7 +39,7 @@ export function GeminiModal({
|
|||||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
setForm({
|
setForm({
|
||||||
...initialData,
|
...initialData,
|
||||||
headers: initialData.headers ?? {},
|
headers: headersToEntries(initialData.headers),
|
||||||
excludedText: excludedModelsToText(initialData.excludedModels),
|
excludedText: excludedModelsToText(initialData.excludedModels),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -91,8 +91,8 @@ export function GeminiModal({
|
|||||||
onChange={(e) => setForm((prev) => ({ ...prev, baseUrl: e.target.value }))}
|
onChange={(e) => setForm((prev) => ({ ...prev, baseUrl: e.target.value }))}
|
||||||
/>
|
/>
|
||||||
<HeaderInputList
|
<HeaderInputList
|
||||||
entries={headersToEntries(form.headers)}
|
entries={form.headers}
|
||||||
onChange={(entries) => setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))}
|
onChange={(entries) => setForm((prev) => ({ ...prev, headers: entries }))}
|
||||||
addLabel={t('common.custom_headers_add')}
|
addLabel={t('common.custom_headers_add')}
|
||||||
keyPlaceholder={t('common.custom_headers_key_placeholder')}
|
keyPlaceholder={t('common.custom_headers_key_placeholder')}
|
||||||
valuePlaceholder={t('common.custom_headers_value_placeholder')}
|
valuePlaceholder={t('common.custom_headers_value_placeholder')}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Input } from '@/components/ui/Input';
|
|||||||
import { Modal } from '@/components/ui/Modal';
|
import { Modal } from '@/components/ui/Modal';
|
||||||
import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList';
|
import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList';
|
||||||
import type { ProviderKeyConfig } from '@/types';
|
import type { ProviderKeyConfig } from '@/types';
|
||||||
import { buildHeaderObject, headersToEntries } from '@/utils/headers';
|
import { headersToEntries } from '@/utils/headers';
|
||||||
import type { ProviderModalProps, VertexFormState } from '../types';
|
import type { ProviderModalProps, VertexFormState } from '../types';
|
||||||
|
|
||||||
interface VertexModalProps extends ProviderModalProps<ProviderKeyConfig, VertexFormState> {
|
interface VertexModalProps extends ProviderModalProps<ProviderKeyConfig, VertexFormState> {
|
||||||
@@ -18,7 +18,7 @@ const buildEmptyForm = (): VertexFormState => ({
|
|||||||
prefix: '',
|
prefix: '',
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
proxyUrl: '',
|
proxyUrl: '',
|
||||||
headers: {},
|
headers: [],
|
||||||
models: [],
|
models: [],
|
||||||
modelEntries: [{ name: '', alias: '' }],
|
modelEntries: [{ name: '', alias: '' }],
|
||||||
});
|
});
|
||||||
@@ -40,7 +40,7 @@ export function VertexModal({
|
|||||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
setForm({
|
setForm({
|
||||||
...initialData,
|
...initialData,
|
||||||
headers: initialData.headers ?? {},
|
headers: headersToEntries(initialData.headers),
|
||||||
modelEntries: modelsToEntries(initialData.models),
|
modelEntries: modelsToEntries(initialData.models),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -94,8 +94,8 @@ export function VertexModal({
|
|||||||
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
|
onChange={(e) => setForm((prev) => ({ ...prev, proxyUrl: e.target.value }))}
|
||||||
/>
|
/>
|
||||||
<HeaderInputList
|
<HeaderInputList
|
||||||
entries={headersToEntries(form.headers)}
|
entries={form.headers}
|
||||||
onChange={(entries) => setForm((prev) => ({ ...prev, headers: buildHeaderObject(entries) }))}
|
onChange={(entries) => setForm((prev) => ({ ...prev, headers: entries }))}
|
||||||
addLabel={t('common.custom_headers_add')}
|
addLabel={t('common.custom_headers_add')}
|
||||||
keyPlaceholder={t('common.custom_headers_key_placeholder')}
|
keyPlaceholder={t('common.custom_headers_key_placeholder')}
|
||||||
valuePlaceholder={t('common.custom_headers_value_placeholder')}
|
valuePlaceholder={t('common.custom_headers_value_placeholder')}
|
||||||
|
|||||||
@@ -32,14 +32,19 @@ export interface AmpcodeFormState {
|
|||||||
mappingEntries: ModelEntry[];
|
mappingEntries: ModelEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GeminiFormState = GeminiKeyConfig & { excludedText: string };
|
export type GeminiFormState = Omit<GeminiKeyConfig, 'headers'> & {
|
||||||
|
headers: HeaderEntry[];
|
||||||
|
excludedText: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ProviderFormState = ProviderKeyConfig & {
|
export type ProviderFormState = Omit<ProviderKeyConfig, 'headers'> & {
|
||||||
|
headers: HeaderEntry[];
|
||||||
modelEntries: ModelEntry[];
|
modelEntries: ModelEntry[];
|
||||||
excludedText: string;
|
excludedText: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type VertexFormState = Omit<ProviderKeyConfig, 'excludedModels'> & {
|
export type VertexFormState = Omit<ProviderKeyConfig, 'headers' | 'excludedModels'> & {
|
||||||
|
headers: HeaderEntry[];
|
||||||
modelEntries: ModelEntry[];
|
modelEntries: ModelEntry[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -612,7 +612,7 @@
|
|||||||
"iflow_oauth_polling_error": "Failed to check authentication status:",
|
"iflow_oauth_polling_error": "Failed to check authentication status:",
|
||||||
"iflow_cookie_title": "iFlow Cookie Login",
|
"iflow_cookie_title": "iFlow Cookie Login",
|
||||||
"iflow_cookie_label": "Cookie Value:",
|
"iflow_cookie_label": "Cookie Value:",
|
||||||
"iflow_cookie_placeholder": "Paste browser cookie, e.g. sessionid=...;",
|
"iflow_cookie_placeholder": "Enter the BXAuth value, starting with BXAuth=",
|
||||||
"iflow_cookie_hint": "Submit an existing cookie to finish login without opening the authorization link; the credential file will be saved automatically.",
|
"iflow_cookie_hint": "Submit an existing cookie to finish login without opening the authorization link; the credential file will be saved automatically.",
|
||||||
"iflow_cookie_key_hint": "Note: Create a key on the platform first.",
|
"iflow_cookie_key_hint": "Note: Create a key on the platform first.",
|
||||||
"iflow_cookie_button": "Submit Cookie Login",
|
"iflow_cookie_button": "Submit Cookie Login",
|
||||||
|
|||||||
@@ -612,7 +612,7 @@
|
|||||||
"iflow_oauth_polling_error": "检查认证状态失败:",
|
"iflow_oauth_polling_error": "检查认证状态失败:",
|
||||||
"iflow_cookie_title": "iFlow Cookie 登录",
|
"iflow_cookie_title": "iFlow Cookie 登录",
|
||||||
"iflow_cookie_label": "Cookie 内容:",
|
"iflow_cookie_label": "Cookie 内容:",
|
||||||
"iflow_cookie_placeholder": "粘贴浏览器中的 Cookie,例如 sessionid=...;",
|
"iflow_cookie_placeholder": "填入BXAuth值 以BXAuth=开头",
|
||||||
"iflow_cookie_hint": "直接提交 Cookie 以完成登录(无需打开授权链接),服务端将自动保存凭据。",
|
"iflow_cookie_hint": "直接提交 Cookie 以完成登录(无需打开授权链接),服务端将自动保存凭据。",
|
||||||
"iflow_cookie_key_hint": "提示:需在平台上先创建 Key。",
|
"iflow_cookie_key_hint": "提示:需在平台上先创建 Key。",
|
||||||
"iflow_cookie_button": "提交 Cookie 登录",
|
"iflow_cookie_button": "提交 Cookie 登录",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
import { ampcodeApi, providersApi } from '@/services/api';
|
import { ampcodeApi, providersApi } from '@/services/api';
|
||||||
import { useAuthStore, useConfigStore, useNotificationStore, useThemeStore } from '@/stores';
|
import { useAuthStore, useConfigStore, useNotificationStore, useThemeStore } from '@/stores';
|
||||||
import type { GeminiKeyConfig, OpenAIProviderConfig, ProviderKeyConfig } from '@/types';
|
import type { GeminiKeyConfig, OpenAIProviderConfig, ProviderKeyConfig } from '@/types';
|
||||||
import { buildHeaderObject, headersToEntries } from '@/utils/headers';
|
import { buildHeaderObject } from '@/utils/headers';
|
||||||
import styles from './AiProvidersPage.module.scss';
|
import styles from './AiProvidersPage.module.scss';
|
||||||
|
|
||||||
export function AiProvidersPage() {
|
export function AiProvidersPage() {
|
||||||
@@ -151,7 +151,7 @@ export function AiProvidersPage() {
|
|||||||
apiKey: form.apiKey.trim(),
|
apiKey: form.apiKey.trim(),
|
||||||
prefix: form.prefix?.trim() || undefined,
|
prefix: form.prefix?.trim() || undefined,
|
||||||
baseUrl: form.baseUrl?.trim() || undefined,
|
baseUrl: form.baseUrl?.trim() || undefined,
|
||||||
headers: buildHeaderObject(headersToEntries(form.headers)),
|
headers: buildHeaderObject(form.headers),
|
||||||
excludedModels: parseExcludedModels(form.excludedText),
|
excludedModels: parseExcludedModels(form.excludedText),
|
||||||
};
|
};
|
||||||
const nextList =
|
const nextList =
|
||||||
@@ -307,7 +307,7 @@ export function AiProvidersPage() {
|
|||||||
prefix: form.prefix?.trim() || undefined,
|
prefix: form.prefix?.trim() || undefined,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
proxyUrl: form.proxyUrl?.trim() || undefined,
|
proxyUrl: form.proxyUrl?.trim() || undefined,
|
||||||
headers: buildHeaderObject(headersToEntries(form.headers)),
|
headers: buildHeaderObject(form.headers),
|
||||||
models: entriesToModels(form.modelEntries),
|
models: entriesToModels(form.modelEntries),
|
||||||
excludedModels: parseExcludedModels(form.excludedText),
|
excludedModels: parseExcludedModels(form.excludedText),
|
||||||
};
|
};
|
||||||
@@ -390,7 +390,7 @@ export function AiProvidersPage() {
|
|||||||
prefix: form.prefix?.trim() || undefined,
|
prefix: form.prefix?.trim() || undefined,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
proxyUrl: form.proxyUrl?.trim() || undefined,
|
proxyUrl: form.proxyUrl?.trim() || undefined,
|
||||||
headers: buildHeaderObject(headersToEntries(form.headers)),
|
headers: buildHeaderObject(form.headers),
|
||||||
models: form.modelEntries
|
models: form.modelEntries
|
||||||
.map((entry) => {
|
.map((entry) => {
|
||||||
const name = entry.name.trim();
|
const name = entry.name.trim();
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ const TYPE_COLORS: Record<string, TypeColorSet> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const OAUTH_PROVIDER_PRESETS = [
|
const OAUTH_PROVIDER_PRESETS = [
|
||||||
'gemini',
|
|
||||||
'gemini-cli',
|
'gemini-cli',
|
||||||
'vertex',
|
'vertex',
|
||||||
'aistudio',
|
'aistudio',
|
||||||
@@ -160,11 +159,11 @@ function resolveAuthFileStats(
|
|||||||
return defaultStats;
|
return defaultStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AuthFilesPage() {
|
export function AuthFilesPage() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { showNotification } = useNotificationStore();
|
const { showNotification } = useNotificationStore();
|
||||||
const connectionStatus = useAuthStore((state) => state.connectionStatus);
|
const connectionStatus = useAuthStore((state) => state.connectionStatus);
|
||||||
const resolvedTheme: ResolvedTheme = useThemeStore((state) => state.resolvedTheme);
|
const resolvedTheme: ResolvedTheme = useThemeStore((state) => state.resolvedTheme);
|
||||||
|
|
||||||
const [files, setFiles] = useState<AuthFileItem[]>([]);
|
const [files, setFiles] = useState<AuthFileItem[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -215,6 +214,8 @@ export function AuthFilesPage() {
|
|||||||
|
|
||||||
const disableControls = connectionStatus !== 'connected';
|
const disableControls = connectionStatus !== 'connected';
|
||||||
|
|
||||||
|
const normalizeProviderKey = (value: string) => value.trim().toLowerCase();
|
||||||
|
|
||||||
const handlePageSizeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handlePageSizeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = event.currentTarget.valueAsNumber;
|
const value = event.currentTarget.valueAsNumber;
|
||||||
if (!Number.isFinite(value)) return;
|
if (!Number.isFinite(value)) return;
|
||||||
@@ -626,13 +627,14 @@ export function AuthFilesPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 检查模型是否被 OAuth 排除
|
// 检查模型是否被 OAuth 排除
|
||||||
const isModelExcluded = (modelId: string, providerType: string): boolean => {
|
const isModelExcluded = (modelId: string, providerType: string): boolean => {
|
||||||
const excludedModels = excluded[providerType] || [];
|
const providerKey = normalizeProviderKey(providerType);
|
||||||
return excludedModels.some(pattern => {
|
const excludedModels = excluded[providerKey] || excluded[providerType] || [];
|
||||||
if (pattern.includes('*')) {
|
return excludedModels.some(pattern => {
|
||||||
// 支持通配符匹配
|
if (pattern.includes('*')) {
|
||||||
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$', 'i');
|
// 支持通配符匹配
|
||||||
return regex.test(modelId);
|
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$', 'i');
|
||||||
|
return regex.test(modelId);
|
||||||
}
|
}
|
||||||
return pattern.toLowerCase() === modelId.toLowerCase();
|
return pattern.toLowerCase() === modelId.toLowerCase();
|
||||||
});
|
});
|
||||||
@@ -655,11 +657,10 @@ export function AuthFilesPage() {
|
|||||||
|
|
||||||
// OAuth 排除相关方法
|
// OAuth 排除相关方法
|
||||||
const openExcludedModal = (provider?: string) => {
|
const openExcludedModal = (provider?: string) => {
|
||||||
const normalizedProvider = (provider || '').trim();
|
const normalizedProvider = normalizeProviderKey(provider || '');
|
||||||
const fallbackProvider = normalizedProvider || (filter !== 'all' ? String(filter) : '');
|
const fallbackProvider =
|
||||||
const lookupKey = fallbackProvider
|
normalizedProvider || (filter !== 'all' ? normalizeProviderKey(String(filter)) : '');
|
||||||
? excludedProviderLookup.get(fallbackProvider.toLowerCase())
|
const lookupKey = fallbackProvider ? excludedProviderLookup.get(fallbackProvider) : undefined;
|
||||||
: undefined;
|
|
||||||
const models = lookupKey ? excluded[lookupKey] : [];
|
const models = lookupKey ? excluded[lookupKey] : [];
|
||||||
setExcludedForm({
|
setExcludedForm({
|
||||||
provider: lookupKey || fallbackProvider,
|
provider: lookupKey || fallbackProvider,
|
||||||
@@ -669,7 +670,7 @@ export function AuthFilesPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const saveExcludedModels = async () => {
|
const saveExcludedModels = async () => {
|
||||||
const provider = excludedForm.provider.trim();
|
const provider = normalizeProviderKey(excludedForm.provider);
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
showNotification(t('oauth_excluded.provider_required'), 'error');
|
showNotification(t('oauth_excluded.provider_required'), 'error');
|
||||||
return;
|
return;
|
||||||
@@ -679,13 +680,13 @@ export function AuthFilesPage() {
|
|||||||
.map((item) => item.trim())
|
.map((item) => item.trim())
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
setSavingExcluded(true);
|
setSavingExcluded(true);
|
||||||
try {
|
try {
|
||||||
if (models.length) {
|
if (models.length) {
|
||||||
await authFilesApi.saveOauthExcludedModels(provider, models);
|
await authFilesApi.saveOauthExcludedModels(provider, models);
|
||||||
} else {
|
} else {
|
||||||
await authFilesApi.deleteOauthExcludedEntry(provider);
|
await authFilesApi.deleteOauthExcludedEntry(provider);
|
||||||
}
|
}
|
||||||
await loadExcluded();
|
await loadExcluded();
|
||||||
showNotification(t('oauth_excluded.save_success'), 'success');
|
showNotification(t('oauth_excluded.save_success'), 'success');
|
||||||
setExcludedModalOpen(false);
|
setExcludedModalOpen(false);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
@@ -697,14 +698,32 @@ export function AuthFilesPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteExcluded = async (provider: string) => {
|
const deleteExcluded = async (provider: string) => {
|
||||||
if (!window.confirm(t('oauth_excluded.delete_confirm', { provider }))) return;
|
const providerLabel = provider.trim() || provider;
|
||||||
try {
|
if (!window.confirm(t('oauth_excluded.delete_confirm', { provider: providerLabel }))) return;
|
||||||
await authFilesApi.deleteOauthExcludedEntry(provider);
|
const providerKey = normalizeProviderKey(provider);
|
||||||
await loadExcluded();
|
if (!providerKey) {
|
||||||
showNotification(t('oauth_excluded.delete_success'), 'success');
|
showNotification(t('oauth_excluded.provider_required'), 'error');
|
||||||
} catch (err: unknown) {
|
return;
|
||||||
const errorMessage = err instanceof Error ? err.message : '';
|
}
|
||||||
showNotification(`${t('oauth_excluded.delete_failed')}: ${errorMessage}`, 'error');
|
try {
|
||||||
|
await authFilesApi.deleteOauthExcludedEntry(providerKey);
|
||||||
|
await loadExcluded();
|
||||||
|
showNotification(t('oauth_excluded.delete_success'), 'success');
|
||||||
|
} catch (err: unknown) {
|
||||||
|
try {
|
||||||
|
const current = await authFilesApi.getOauthExcludedModels();
|
||||||
|
const next: Record<string, string[]> = {};
|
||||||
|
Object.entries(current).forEach(([key, models]) => {
|
||||||
|
if (normalizeProviderKey(key) === providerKey) return;
|
||||||
|
next[key] = models;
|
||||||
|
});
|
||||||
|
await authFilesApi.replaceOauthExcludedModels(next);
|
||||||
|
await loadExcluded();
|
||||||
|
showNotification(t('oauth_excluded.delete_success'), 'success');
|
||||||
|
} catch (fallbackErr: unknown) {
|
||||||
|
const errorMessage = fallbackErr instanceof Error ? fallbackErr.message : err instanceof Error ? err.message : '';
|
||||||
|
showNotification(`${t('oauth_excluded.delete_failed')}: ${errorMessage}`, 'error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
||||||
import { Navigate, useNavigate, useLocation } from 'react-router-dom';
|
import { Navigate, useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
@@ -81,6 +81,13 @@ export function LoginPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmitKeyDown = useCallback((event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter' && !loading) {
|
||||||
|
event.preventDefault();
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
}, [loading, handleSubmit]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="login-page">
|
<div className="login-page">
|
||||||
<div className="login-card">
|
<div className="login-card">
|
||||||
@@ -129,11 +136,13 @@ export function LoginPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
|
autoFocus
|
||||||
label={t('login.management_key_label')}
|
label={t('login.management_key_label')}
|
||||||
placeholder={t('login.management_key_placeholder')}
|
placeholder={t('login.management_key_placeholder')}
|
||||||
type={showKey ? 'text' : 'password'}
|
type={showKey ? 'text' : 'password'}
|
||||||
value={managementKey}
|
value={managementKey}
|
||||||
onChange={(e) => setManagementKey(e.target.value)}
|
onChange={(e) => setManagementKey(e.target.value)}
|
||||||
|
onKeyDown={handleSubmitKeyDown}
|
||||||
rightElement={
|
rightElement={
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -6,6 +6,43 @@ import { apiClient } from './client';
|
|||||||
import type { AuthFilesResponse } from '@/types/authFile';
|
import type { AuthFilesResponse } from '@/types/authFile';
|
||||||
import type { OAuthModelMappingEntry } from '@/types';
|
import type { OAuthModelMappingEntry } from '@/types';
|
||||||
|
|
||||||
|
const normalizeOauthExcludedModels = (payload: unknown): Record<string, string[]> => {
|
||||||
|
if (!payload || typeof payload !== 'object') return {};
|
||||||
|
|
||||||
|
const source = (payload as any)['oauth-excluded-models'] ?? (payload as any).items ?? payload;
|
||||||
|
if (!source || typeof source !== 'object') return {};
|
||||||
|
|
||||||
|
const result: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
Object.entries(source as Record<string, unknown>).forEach(([provider, models]) => {
|
||||||
|
const key = String(provider ?? '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
if (!key) return;
|
||||||
|
|
||||||
|
const rawList = Array.isArray(models)
|
||||||
|
? models
|
||||||
|
: typeof models === 'string'
|
||||||
|
? models.split(/[\n,]+/)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const seen = new Set<string>();
|
||||||
|
const normalized: string[] = [];
|
||||||
|
rawList.forEach((item) => {
|
||||||
|
const trimmed = String(item ?? '').trim();
|
||||||
|
if (!trimmed) return;
|
||||||
|
const modelKey = trimmed.toLowerCase();
|
||||||
|
if (seen.has(modelKey)) return;
|
||||||
|
seen.add(modelKey);
|
||||||
|
normalized.push(trimmed);
|
||||||
|
});
|
||||||
|
|
||||||
|
result[key] = normalized;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
export const authFilesApi = {
|
export const authFilesApi = {
|
||||||
list: () => apiClient.get<AuthFilesResponse>('/auth-files'),
|
list: () => apiClient.get<AuthFilesResponse>('/auth-files'),
|
||||||
|
|
||||||
@@ -22,8 +59,7 @@ export const authFilesApi = {
|
|||||||
// OAuth 排除模型
|
// OAuth 排除模型
|
||||||
async getOauthExcludedModels(): Promise<Record<string, string[]>> {
|
async getOauthExcludedModels(): Promise<Record<string, string[]>> {
|
||||||
const data = await apiClient.get('/oauth-excluded-models');
|
const data = await apiClient.get('/oauth-excluded-models');
|
||||||
const payload = (data && (data['oauth-excluded-models'] ?? data.items ?? data)) as any;
|
return normalizeOauthExcludedModels(data);
|
||||||
return payload && typeof payload === 'object' ? payload : {};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
saveOauthExcludedModels: (provider: string, models: string[]) =>
|
saveOauthExcludedModels: (provider: string, models: string[]) =>
|
||||||
@@ -32,6 +68,9 @@ export const authFilesApi = {
|
|||||||
deleteOauthExcludedEntry: (provider: string) =>
|
deleteOauthExcludedEntry: (provider: string) =>
|
||||||
apiClient.delete(`/oauth-excluded-models?provider=${encodeURIComponent(provider)}`),
|
apiClient.delete(`/oauth-excluded-models?provider=${encodeURIComponent(provider)}`),
|
||||||
|
|
||||||
|
replaceOauthExcludedModels: (map: Record<string, string[]>) =>
|
||||||
|
apiClient.put('/oauth-excluded-models', normalizeOauthExcludedModels(map)),
|
||||||
|
|
||||||
// OAuth 模型映射
|
// OAuth 模型映射
|
||||||
async getOauthModelMappings(): Promise<Record<string, OAuthModelMappingEntry[]>> {
|
async getOauthModelMappings(): Promise<Record<string, OAuthModelMappingEntry[]>> {
|
||||||
const data = await apiClient.get('/oauth-model-mappings');
|
const data = await apiClient.get('/oauth-model-mappings');
|
||||||
|
|||||||
Reference in New Issue
Block a user