mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
refactor(hooks): extract shared api-key resolution for model fetching
DashboardPage and SystemPage carried byte-identical copies of normalizeApiKeyList plus a per-connection cache for resolving the key used by /models probes. Move the logic into useApiKeysForModels and reuse it from both pages.
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { apiKeysApi } from '@/services/api/apiKeys';
|
||||
import { useAuthStore, useConfigStore } from '@/stores';
|
||||
|
||||
const normalizeApiKeyList = (input: unknown): string[] => {
|
||||
if (!Array.isArray(input)) return [];
|
||||
const seen = new Set<string>();
|
||||
const keys: string[] = [];
|
||||
|
||||
input.forEach((item) => {
|
||||
const record =
|
||||
item !== null && typeof item === 'object' && !Array.isArray(item)
|
||||
? (item as Record<string, unknown>)
|
||||
: null;
|
||||
const value =
|
||||
typeof item === 'string'
|
||||
? item
|
||||
: record
|
||||
? (record['api-key'] ?? record['apiKey'] ?? record.key ?? record.Key)
|
||||
: '';
|
||||
const trimmed = String(value ?? '').trim();
|
||||
if (!trimmed || seen.has(trimmed)) return;
|
||||
seen.add(trimmed);
|
||||
keys.push(trimmed);
|
||||
});
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
||||
/**
|
||||
* 解析用于 /models 探测的 API 密钥列表:
|
||||
* 优先取已缓存配置中的 api-keys,否则回退到 /api-keys 接口,结果按连接缓存。
|
||||
*/
|
||||
export function useApiKeysForModels() {
|
||||
const apiBase = useAuthStore((state) => state.apiBase);
|
||||
const configApiKeys = useConfigStore((state) => state.config?.apiKeys);
|
||||
const cacheRef = useRef<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
cacheRef.current = [];
|
||||
}, [apiBase, configApiKeys]);
|
||||
|
||||
return useCallback(
|
||||
async ({ force = false }: { force?: boolean } = {}) => {
|
||||
if (force) {
|
||||
cacheRef.current = [];
|
||||
}
|
||||
if (cacheRef.current.length) {
|
||||
return cacheRef.current;
|
||||
}
|
||||
|
||||
const configKeys = normalizeApiKeyList(configApiKeys);
|
||||
if (configKeys.length) {
|
||||
cacheRef.current = configKeys;
|
||||
return configKeys;
|
||||
}
|
||||
|
||||
try {
|
||||
const list = await apiKeysApi.list();
|
||||
const normalized = normalizeApiKeyList(list);
|
||||
if (normalized.length) {
|
||||
cacheRef.current = normalized;
|
||||
}
|
||||
return normalized;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
[configApiKeys]
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IconKey, IconBot, IconFileText, IconSatellite } from '@/components/ui/icons';
|
||||
import { useAuthStore, useConfigStore, useModelsStore } from '@/stores';
|
||||
import { apiKeysApi, providersApi, authFilesApi, ampcodeApi } from '@/services/api';
|
||||
import { useApiKeysForModels } from '@/hooks/useApiKeysForModels';
|
||||
import type { AmpcodeConfig } from '@/types';
|
||||
import { formatDateValue } from '@/utils/format';
|
||||
import styles from './DashboardPage.module.scss';
|
||||
@@ -81,12 +82,6 @@ export function DashboardPage() {
|
||||
const [timeOfDay, setTimeOfDay] = useState<TimeOfDay>(getTimeOfDay);
|
||||
const [currentTime, setCurrentTime] = useState(() => new Date());
|
||||
|
||||
const apiKeysCache = useRef<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
apiKeysCache.current = [];
|
||||
}, [apiBase, config?.apiKeys]);
|
||||
|
||||
// Update time every 60 seconds
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => {
|
||||
@@ -96,53 +91,7 @@ export function DashboardPage() {
|
||||
return () => clearInterval(id);
|
||||
}, []);
|
||||
|
||||
const normalizeApiKeyList = (input: unknown): string[] => {
|
||||
if (!Array.isArray(input)) return [];
|
||||
const seen = new Set<string>();
|
||||
const keys: string[] = [];
|
||||
|
||||
input.forEach((item) => {
|
||||
const record =
|
||||
item !== null && typeof item === 'object' && !Array.isArray(item)
|
||||
? (item as Record<string, unknown>)
|
||||
: null;
|
||||
const value =
|
||||
typeof item === 'string'
|
||||
? item
|
||||
: record
|
||||
? (record['api-key'] ?? record['apiKey'] ?? record.key ?? record.Key)
|
||||
: '';
|
||||
const trimmed = String(value ?? '').trim();
|
||||
if (!trimmed || seen.has(trimmed)) return;
|
||||
seen.add(trimmed);
|
||||
keys.push(trimmed);
|
||||
});
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
||||
const resolveApiKeysForModels = useCallback(async () => {
|
||||
if (apiKeysCache.current.length) {
|
||||
return apiKeysCache.current;
|
||||
}
|
||||
|
||||
const configKeys = normalizeApiKeyList(config?.apiKeys);
|
||||
if (configKeys.length) {
|
||||
apiKeysCache.current = configKeys;
|
||||
return configKeys;
|
||||
}
|
||||
|
||||
try {
|
||||
const list = await apiKeysApi.list();
|
||||
const normalized = normalizeApiKeyList(list);
|
||||
if (normalized.length) {
|
||||
apiKeysCache.current = normalized;
|
||||
}
|
||||
return normalized;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}, [config?.apiKeys]);
|
||||
const resolveApiKeysForModels = useApiKeysForModels();
|
||||
|
||||
const fetchModels = useCallback(async () => {
|
||||
if (connectionStatus !== 'connected' || !apiBase) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
useThemeStore,
|
||||
} from '@/stores';
|
||||
import { configApi, versionApi } from '@/services/api';
|
||||
import { apiKeysApi } from '@/services/api/apiKeys';
|
||||
import { useApiKeysForModels } from '@/hooks/useApiKeysForModels';
|
||||
import { formatDateTimeValue } from '@/utils/format';
|
||||
import { classifyModels } from '@/utils/models';
|
||||
import { STORAGE_KEY_AUTH } from '@/utils/constants';
|
||||
@@ -95,7 +95,6 @@ export function SystemPage() {
|
||||
const [requestLogSaving, setRequestLogSaving] = useState(false);
|
||||
const [checkingVersion, setCheckingVersion] = useState(false);
|
||||
|
||||
const apiKeysCache = useRef<string[]>([]);
|
||||
const versionTapCount = useRef(0);
|
||||
const versionTapTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
@@ -120,54 +119,7 @@ export function SystemPage() {
|
||||
return resolvedTheme === 'dark' ? iconEntry.dark : iconEntry.light;
|
||||
};
|
||||
|
||||
const normalizeApiKeyList = (input: unknown): string[] => {
|
||||
if (!Array.isArray(input)) return [];
|
||||
const seen = new Set<string>();
|
||||
const keys: string[] = [];
|
||||
|
||||
input.forEach((item) => {
|
||||
const record =
|
||||
item !== null && typeof item === 'object' && !Array.isArray(item)
|
||||
? (item as Record<string, unknown>)
|
||||
: null;
|
||||
const value =
|
||||
typeof item === 'string'
|
||||
? item
|
||||
: record
|
||||
? (record['api-key'] ?? record['apiKey'] ?? record.key ?? record.Key)
|
||||
: '';
|
||||
const trimmed = String(value ?? '').trim();
|
||||
if (!trimmed || seen.has(trimmed)) return;
|
||||
seen.add(trimmed);
|
||||
keys.push(trimmed);
|
||||
});
|
||||
|
||||
return keys;
|
||||
};
|
||||
|
||||
const resolveApiKeysForModels = useCallback(async () => {
|
||||
if (apiKeysCache.current.length) {
|
||||
return apiKeysCache.current;
|
||||
}
|
||||
|
||||
const configKeys = normalizeApiKeyList(config?.apiKeys);
|
||||
if (configKeys.length) {
|
||||
apiKeysCache.current = configKeys;
|
||||
return configKeys;
|
||||
}
|
||||
|
||||
try {
|
||||
const list = await apiKeysApi.list();
|
||||
const normalized = normalizeApiKeyList(list);
|
||||
if (normalized.length) {
|
||||
apiKeysCache.current = normalized;
|
||||
}
|
||||
return normalized;
|
||||
} catch (err) {
|
||||
console.warn('Auto loading API keys for models failed:', err);
|
||||
return [];
|
||||
}
|
||||
}, [config?.apiKeys]);
|
||||
const resolveApiKeysForModels = useApiKeysForModels();
|
||||
|
||||
const fetchModels = async ({ forceRefresh = false }: { forceRefresh?: boolean } = {}) => {
|
||||
if (auth.connectionStatus !== 'connected') {
|
||||
@@ -183,13 +135,9 @@ export function SystemPage() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (forceRefresh) {
|
||||
apiKeysCache.current = [];
|
||||
}
|
||||
|
||||
setModelStatus({ type: 'muted', message: t('system_info.models_loading') });
|
||||
try {
|
||||
const apiKeys = await resolveApiKeysForModels();
|
||||
const apiKeys = await resolveApiKeysForModels({ force: forceRefresh });
|
||||
const primaryKey = apiKeys[0];
|
||||
const list = await fetchModelsFromStore(auth.apiBase, primaryKey, forceRefresh);
|
||||
const hasModels = list.length > 0;
|
||||
|
||||
Reference in New Issue
Block a user