diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index 777bc61984..91c65a7e3e 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -5,7 +5,7 @@ import { HStack } from '@renderer/components/Layout' import { ApiKeyListPopup } from '@renderer/components/Popups/ApiKeyListPopup' import Selector from '@renderer/components/Selector' import { HelpTooltip } from '@renderer/components/TooltipIcons' -import { isEmbeddingModel, isRerankModel } from '@renderer/config/models' +import { isRerankModel } from '@renderer/config/models' import { PROVIDER_URLS } from '@renderer/config/providers' import { useTheme } from '@renderer/context/ThemeProvider' import { useAllProviders, useProvider, useProviders } from '@renderer/hooks/useProvider' @@ -129,17 +129,20 @@ const ProviderSetting: FC = ({ providerId }) => { checking: false }) - const updateWebSearchProviderKey = ({ apiKey }: { apiKey: string }) => { - provider.id === 'zhipu' && dispatch(updateWebSearchProvider({ id: 'zhipu', apiKey: apiKey.split(',')[0] })) - } + const updateWebSearchProviderKey = useCallback( + ({ apiKey }: { apiKey: string }) => { + provider.id === 'zhipu' && dispatch(updateWebSearchProvider({ id: 'zhipu', apiKey: apiKey.split(',')[0] })) + }, + [dispatch, provider.id] + ) - // eslint-disable-next-line react-hooks/exhaustive-deps - const debouncedUpdateApiKey = useCallback( - debounce((value) => { - updateProvider({ apiKey: formatApiKeys(value) }) - updateWebSearchProviderKey({ apiKey: formatApiKeys(value) }) - }, 150), - [] + const debouncedUpdateApiKey = useMemo( + () => + debounce((value: string) => { + updateProvider({ apiKey: formatApiKeys(value) }) + updateWebSearchProviderKey({ apiKey: formatApiKeys(value) }) + }, 150), + [updateProvider, updateWebSearchProviderKey] ) // 同步 provider.apiKey 到 localApiKey @@ -225,7 +228,7 @@ const ProviderSetting: FC = ({ providerId }) => { return } - const modelsToCheck = models.filter((model) => !isEmbeddingModel(model) && !isRerankModel(model)) + const modelsToCheck = models.filter((model) => !isRerankModel(model)) if (isEmpty(modelsToCheck)) { window.toast.error({ diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts index 0cd57a353a..89ebe6ecd3 100644 --- a/src/renderer/src/services/ApiService.ts +++ b/src/renderer/src/services/ApiService.ts @@ -601,6 +601,13 @@ export function checkApiProvider(provider: Provider): void { } } +/** + * Validates that a provider/model pair is working by sending a minimal request. + * @param provider - The provider configuration to test. + * @param model - The model to use for the validation request (chat or embeddings). + * @param timeout - Maximum time (ms) to wait for the request to complete. Defaults to 15000 ms. + * @throws {Error} If the request fails or times out, indicating the API is not usable. + */ export async function checkApi(provider: Provider, model: Model, timeout = 15000): Promise { checkApiProvider(provider) @@ -611,7 +618,6 @@ export async function checkApi(provider: Provider, model: Model, timeout = 15000 assistant.prompt = 'test' // 避免部分 provider 空系统提示词会报错 if (isEmbeddingModel(model)) { - // race 超时 15s logger.silly("it's a embedding model") const timerPromise = new Promise((_, reject) => setTimeout(() => reject('Timeout'), timeout)) await Promise.race([ai.getEmbeddingDimensions(model), timerPromise])