mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
refactor(utils): share isRecord and getErrorMessage helpers
isRecord was declared locally in 15 modules (with two divergent shapes) and getErrorMessage in 7. Move a single canonical pair into utils/helpers and import it everywhere. The shared isRecord excludes arrays; the call sites that previously allowed them only read named properties, so behavior is unchanged.
This commit is contained in:
@@ -5,6 +5,7 @@ import { EmptyState } from '@/components/ui/EmptyState';
|
||||
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
|
||||
import { pluginsApi } from '@/services/api';
|
||||
import { useAuthStore } from '@/stores';
|
||||
import { getErrorMessage, isRecord } from '@/utils/helpers';
|
||||
import type { PluginListResponse } from '@/types';
|
||||
import {
|
||||
collectPluginResourceEntries,
|
||||
@@ -12,15 +13,9 @@ import {
|
||||
} from './pluginResources';
|
||||
import styles from './PluginResourcePage.module.scss';
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
const hasStatus = (error: unknown, status: number) =>
|
||||
isRecord(error) && error.status === status;
|
||||
|
||||
const getErrorMessage = (error: unknown, fallback: string) =>
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : fallback;
|
||||
|
||||
const safeDecodeURIComponent = (value = '') => {
|
||||
try {
|
||||
return decodeURIComponent(value);
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
|
||||
import { pluginStoreApi } from '@/services/api';
|
||||
import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
|
||||
import { getErrorMessage, isRecord } from '@/utils/helpers';
|
||||
import type { PluginStoreEntry, PluginStoreResponse } from '@/types';
|
||||
import { buildRepositoryURL, resolvePluginAssetURL } from './pluginResources';
|
||||
import styles from './PluginStorePage.module.scss';
|
||||
@@ -27,12 +28,6 @@ interface StoreLoadError {
|
||||
message: string;
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
const getErrorMessage = (error: unknown, fallback: string) =>
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : fallback;
|
||||
|
||||
const getErrorStatus = (error: unknown): number | undefined =>
|
||||
isRecord(error) && typeof error.status === 'number' ? error.status : undefined;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import { useHeaderRefresh } from '@/hooks/useHeaderRefresh';
|
||||
import { pluginsApi } from '@/services/api';
|
||||
import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
|
||||
import { getErrorMessage, isRecord } from '@/utils/helpers';
|
||||
import type { PluginConfigField, PluginListEntry, PluginListResponse } from '@/types';
|
||||
import { getPluginTitle, resolvePluginAssetURL } from './pluginResources';
|
||||
import styles from './PluginsPage.module.scss';
|
||||
@@ -44,15 +45,9 @@ function PluginCardLogo({ src }: { src: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
const cloneRecord = (value: unknown): Record<string, unknown> =>
|
||||
isRecord(value) ? { ...value } : {};
|
||||
|
||||
const getErrorMessage = (error: unknown, fallback: string) =>
|
||||
error instanceof Error ? error.message : typeof error === 'string' ? error : fallback;
|
||||
|
||||
const hasStatus = (error: unknown, status: number) =>
|
||||
isRecord(error) && error.status === status;
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type ProviderRecentUsageMap,
|
||||
} from '@/components/providers/utils';
|
||||
import type { OpenAIProviderConfig } from '@/types';
|
||||
import { isRecord } from '@/utils/helpers';
|
||||
import { ProviderHeaderCard } from './components/ProviderHeaderCard';
|
||||
import { ProviderCategoryList } from './components/ProviderCategoryList';
|
||||
import { ProviderResourcePanel } from './components/ProviderResourcePanel';
|
||||
@@ -66,9 +67,6 @@ const matchesFilter = (r: ProviderResource, normalized: string): boolean => {
|
||||
return haystack.some((v) => v.includes(normalized));
|
||||
};
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
||||
|
||||
const getResourceModels = (resource: ProviderResource): string[] => {
|
||||
if (!isRecord(resource.raw)) return [];
|
||||
if (resource.brand === 'ampcode') {
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
buildOpenAIChatCompletionsEndpoint,
|
||||
} from '@/components/providers/utils';
|
||||
import { buildHeaderObject, hasHeader } from '@/utils/headers';
|
||||
import { getErrorMessage } from '@/utils/helpers';
|
||||
import type { ApiKeyEntryInput, ModelEntryInput, ProviderBrand } from '../../types';
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 30_000;
|
||||
@@ -20,14 +21,8 @@ export interface ConnectivityStatus {
|
||||
|
||||
const IDLE: ConnectivityStatus = { state: 'idle', message: '' };
|
||||
|
||||
const errorMessage = (err: unknown): string => {
|
||||
if (err instanceof Error) return err.message;
|
||||
if (typeof err === 'string') return err;
|
||||
return '';
|
||||
};
|
||||
|
||||
const requestFailureMessage = (err: unknown, messages: ConnectivityErrorMessages): string => {
|
||||
const raw = errorMessage(err);
|
||||
const raw = getErrorMessage(err);
|
||||
const isTimeout =
|
||||
(typeof err === 'object' &&
|
||||
err !== null &&
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { modelsApi } from '@/services/api';
|
||||
import { buildHeaderObject } from '@/utils/headers';
|
||||
import { getErrorMessage } from '@/utils/helpers';
|
||||
import type { ModelInfo } from '@/utils/models';
|
||||
import type { ApiKeyEntryInput, ProviderBrand } from '../../types';
|
||||
|
||||
@@ -14,12 +15,6 @@ export const MODEL_DISCOVERY_BRANDS: ReadonlyArray<ProviderBrand> = [
|
||||
export const isModelDiscoveryBrand = (brand: ProviderBrand): boolean =>
|
||||
MODEL_DISCOVERY_BRANDS.includes(brand);
|
||||
|
||||
const toErrorMessage = (err: unknown): string => {
|
||||
if (err instanceof Error) return err.message;
|
||||
if (typeof err === 'string') return err;
|
||||
return '';
|
||||
};
|
||||
|
||||
export interface UseModelDiscoveryArgs {
|
||||
brand: ProviderBrand;
|
||||
baseUrl: string;
|
||||
@@ -111,7 +106,7 @@ export function useModelDiscovery(args: UseModelDiscoveryArgs): UseModelDiscover
|
||||
setHasFetched(true);
|
||||
} catch (err) {
|
||||
setModels([]);
|
||||
setError(toErrorMessage(err) || 'Failed to fetch models');
|
||||
setError(getErrorMessage(err) || 'Failed to fetch models');
|
||||
setHasFetched(true);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isRecord } from '@/utils/helpers';
|
||||
import { PROVIDER_BRAND_ORDER } from './descriptors';
|
||||
import {
|
||||
PROVIDER_SORT_BY_VALUES,
|
||||
@@ -32,9 +33,6 @@ export interface ProvidersWorkbenchUiState {
|
||||
filtersByBrand: Partial<Record<ProviderBrand, ProviderFilterState>>;
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
||||
|
||||
const isProviderBrand = (value: unknown): value is ProviderBrand =>
|
||||
typeof value === 'string' && PROVIDER_BRAND_SET.has(value as ProviderBrand);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { ampcodeApi, providersApi } from '@/services/api';
|
||||
import { getErrorMessage } from '@/utils/helpers';
|
||||
import { useAuthStore, useConfigStore } from '@/stores';
|
||||
import {
|
||||
withDisableAllModelsRule,
|
||||
@@ -28,12 +29,6 @@ import type {
|
||||
ProviderSnapshot,
|
||||
} from './types';
|
||||
|
||||
const getErrorMessage = (err: unknown): string => {
|
||||
if (err instanceof Error) return err.message;
|
||||
if (typeof err === 'string') return err;
|
||||
return '';
|
||||
};
|
||||
|
||||
export interface UseProviderWorkbenchResult {
|
||||
connected: boolean;
|
||||
isPending: boolean;
|
||||
|
||||
+1
-10
@@ -30,6 +30,7 @@ import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores';
|
||||
import { logsApi, type LogsQuery } from '@/services/api/logs';
|
||||
import { versionApi } from '@/services/api/version';
|
||||
import { copyToClipboard } from '@/utils/clipboard';
|
||||
import { getErrorMessage } from '@/utils/helpers';
|
||||
import { downloadBlob } from '@/utils/download';
|
||||
import { MANAGEMENT_API_PREFIX } from '@/utils/constants';
|
||||
import { formatUnixTimestamp } from '@/utils/format';
|
||||
@@ -82,16 +83,6 @@ const mergeIncrementalLines = (currentLines: string[], incomingLines: string[]):
|
||||
return [...currentLines, ...incomingLines.slice(overlap)];
|
||||
};
|
||||
|
||||
const getErrorMessage = (err: unknown): string => {
|
||||
if (err instanceof Error) return err.message;
|
||||
if (typeof err === 'string') return err;
|
||||
if (typeof err !== 'object' || err === null) return '';
|
||||
if (!('message' in err)) return '';
|
||||
|
||||
const message = (err as { message?: unknown }).message;
|
||||
return typeof message === 'string' ? message : '';
|
||||
};
|
||||
|
||||
const getErrorPayloadText = (err: unknown): string => {
|
||||
if (typeof err !== 'object' || err === null) return '';
|
||||
const payloads = [
|
||||
|
||||
+1
-10
@@ -8,6 +8,7 @@ import { useNotificationStore, useThemeStore } from '@/stores';
|
||||
import { oauthApi, type OAuthProvider } from '@/services/api/oauth';
|
||||
import { vertexApi, type VertexImportResponse } from '@/services/api/vertex';
|
||||
import { copyToClipboard } from '@/utils/clipboard';
|
||||
import { getErrorMessage, isRecord } from '@/utils/helpers';
|
||||
import styles from './OAuthPage.module.scss';
|
||||
import iconCodex from '@/assets/icons/codex.svg';
|
||||
import iconClaude from '@/assets/icons/claude.svg';
|
||||
@@ -49,16 +50,6 @@ interface VertexImportState {
|
||||
result?: VertexImportResult;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === 'object';
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error) return error.message;
|
||||
if (isRecord(error) && typeof error.message === 'string') return error.message;
|
||||
return typeof error === 'string' ? error : '';
|
||||
}
|
||||
|
||||
function getErrorStatus(error: unknown): number | undefined {
|
||||
if (!isRecord(error)) return undefined;
|
||||
return typeof error.status === 'number' ? error.status : undefined;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
import { apiClient } from './client';
|
||||
import { isRecord } from '@/utils/helpers';
|
||||
|
||||
export interface ApiCallRequest {
|
||||
authIndex?: string;
|
||||
@@ -46,9 +47,6 @@ const normalizeBody = (input: unknown): { bodyText: string; body: unknown | null
|
||||
};
|
||||
|
||||
export const getApiCallErrorMessage = (result: ApiCallResult): string => {
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object';
|
||||
|
||||
const status = result.statusCode;
|
||||
const body = result.body;
|
||||
const bodyText = result.bodyText;
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
VERSION_HEADER_KEYS
|
||||
} from '@/utils/constants';
|
||||
import { computeApiUrl } from '@/utils/connection';
|
||||
import { isRecord } from '@/utils/helpers';
|
||||
import type { ServerRuntimeKind } from '@/types';
|
||||
|
||||
class ApiClient {
|
||||
@@ -143,9 +144,6 @@ class ApiClient {
|
||||
* 错误处理
|
||||
*/
|
||||
private handleError(error: unknown): ApiError {
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object';
|
||||
|
||||
if (axios.isAxiosError(error)) {
|
||||
const responseData: unknown = error.response?.data;
|
||||
const responseRecord = isRecord(responseData) ? responseData : null;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import { apiClient } from './client';
|
||||
import { LOGS_TIMEOUT_MS } from '@/utils/constants';
|
||||
import { isRecord } from '@/utils/helpers';
|
||||
|
||||
export type LogCursor = number | string;
|
||||
export type LogBackendKind = 'unknown' | 'file' | 'home-db';
|
||||
@@ -59,9 +60,6 @@ export interface ErrorLogsResponse {
|
||||
files?: ErrorLogFile[];
|
||||
}
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object';
|
||||
|
||||
const stringValue = (value: unknown): string => (typeof value === 'string' ? value.trim() : '');
|
||||
|
||||
const unixSecondsFromValue = (value: unknown): number => {
|
||||
|
||||
@@ -6,6 +6,7 @@ import axios from 'axios';
|
||||
import { normalizeModelList } from '@/utils/models';
|
||||
import { normalizeApiBase } from '@/utils/connection';
|
||||
import { apiCallApi, getApiCallErrorMessage } from './apiCall';
|
||||
import { isRecord } from '@/utils/helpers';
|
||||
|
||||
const DEFAULT_CLAUDE_BASE_URL = 'https://api.anthropic.com';
|
||||
const DEFAULT_GEMINI_BASE_URL = 'https://generativelanguage.googleapis.com';
|
||||
@@ -13,9 +14,6 @@ const DEFAULT_ANTHROPIC_VERSION = '2023-06-01';
|
||||
const CLAUDE_MODELS_IN_FLIGHT = new Map<string, Promise<ReturnType<typeof normalizeModelList>>>();
|
||||
const GEMINI_MODELS_IN_FLIGHT = new Map<string, Promise<ReturnType<typeof normalizeModelList>>>();
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
const buildRequestSignature = (
|
||||
url: string,
|
||||
headers: Record<string, string>,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { apiClient } from './client';
|
||||
import { isRecord } from '@/utils/helpers';
|
||||
import type {
|
||||
PluginConfigField,
|
||||
PluginListEntry,
|
||||
@@ -10,9 +11,6 @@ import type {
|
||||
PluginStoreResponse,
|
||||
} from '@/types';
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
const asString = (value: unknown): string => {
|
||||
if (value === undefined || value === null) return '';
|
||||
return String(value);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
import { apiClient } from './client';
|
||||
import { isRecord } from '@/utils/helpers';
|
||||
import {
|
||||
normalizeGeminiKeyConfig,
|
||||
normalizeOpenAIProvider,
|
||||
@@ -19,9 +20,6 @@ import type {
|
||||
const serializeHeaders = (headers?: Record<string, string>) =>
|
||||
headers && Object.keys(headers).length ? headers : undefined;
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
const RESPONSE_ONLY_FIELDS = ['auth-index'] as const;
|
||||
|
||||
const PROVIDER_KEY_FIELDS = [
|
||||
|
||||
@@ -11,9 +11,7 @@ import type {
|
||||
} from '@/types';
|
||||
import type { Config } from '@/types/config';
|
||||
import { buildHeaderObject } from '@/utils/headers';
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
import { isRecord } from '@/utils/helpers';
|
||||
|
||||
const normalizeBoolean = (value: unknown): boolean | undefined =>
|
||||
typeof value === 'boolean' ? value : undefined;
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
|
||||
import { apiClient } from './client';
|
||||
import type { ServerRuntimeKind } from '@/types';
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object';
|
||||
import { isRecord } from '@/utils/helpers';
|
||||
|
||||
export const versionApi = {
|
||||
checkLatest: () => apiClient.get<Record<string, unknown>>('/latest-version'),
|
||||
|
||||
@@ -9,3 +9,19 @@
|
||||
export function generateId(): string {
|
||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为普通对象(排除 null 与数组)
|
||||
*/
|
||||
export const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
/**
|
||||
* 从 unknown 错误中提取可读消息
|
||||
*/
|
||||
export const getErrorMessage = (error: unknown, fallback = ''): string => {
|
||||
if (error instanceof Error) return error.message || fallback;
|
||||
if (typeof error === 'string') return error || fallback;
|
||||
if (isRecord(error) && typeof error.message === 'string') return error.message || fallback;
|
||||
return fallback;
|
||||
};
|
||||
|
||||
+2
-3
@@ -3,6 +3,8 @@
|
||||
* 迁移自基线 utils/models.js
|
||||
*/
|
||||
|
||||
import { isRecord } from './helpers';
|
||||
|
||||
export interface ModelInfo {
|
||||
name: string;
|
||||
alias?: string;
|
||||
@@ -30,9 +32,6 @@ const matchCategory = (text: string) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
value !== null && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
export function normalizeModelList(payload: unknown, { dedupe = false } = {}): ModelInfo[] {
|
||||
const toModel = (entry: unknown): ModelInfo | null => {
|
||||
if (typeof entry === 'string') {
|
||||
|
||||
Reference in New Issue
Block a user