diff --git a/src/renderer/src/pages/settings/WebSearchSettings/WebSearchProviderSetting.tsx b/src/renderer/src/pages/settings/WebSearchSettings/WebSearchProviderSetting.tsx index b0a2cb43..331cc645 100644 --- a/src/renderer/src/pages/settings/WebSearchSettings/WebSearchProviderSetting.tsx +++ b/src/renderer/src/pages/settings/WebSearchSettings/WebSearchProviderSetting.tsx @@ -1,8 +1,10 @@ -import { ExportOutlined } from '@ant-design/icons' +import { CheckOutlined, ExportOutlined, InfoCircleOutlined, LoadingOutlined } from '@ant-design/icons' import { WEB_SEARCH_PROVIDER_CONFIG } from '@renderer/config/webSearchProviders' import { useWebSearchProvider } from '@renderer/hooks/useWebSearchProviders' +import WebSearchService from '@renderer/services/WebSearchService' import { WebSearchProvider } from '@renderer/types' -import { Divider, Flex, Input } from 'antd' +import { hasObjectKey } from '@renderer/utils' +import { Button, Divider, Flex, Input } from 'antd' import Link from 'antd/es/typography/Link' import { FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -13,15 +15,19 @@ import { SettingHelpLink, SettingHelpTextRow, SettingSubtitle, SettingTitle } fr interface Props { provider: WebSearchProvider } + const WebSearchProviderSetting: FC = ({ provider: _provider }) => { const { provider, updateProvider } = useWebSearchProvider(_provider.id) const { t } = useTranslation() const [apiKey, setApiKey] = useState(provider.apiKey) const [apiHost, setApiHost] = useState(provider.apiHost) + const [apiChecking, setApiChecking] = useState(false) + const [apiValid, setApiValid] = useState(false) const webSearchProviderConfig = WEB_SEARCH_PROVIDER_CONFIG[provider.id] const apiKeyWebsite = webSearchProviderConfig?.websites?.apiKey const officialWebsite = webSearchProviderConfig?.websites?.official + const onUpdateApiKey = () => { if (apiKey !== provider.apiKey) { updateProvider({ ...provider, apiKey }) @@ -37,6 +43,46 @@ const WebSearchProviderSetting: FC = ({ provider: _provider }) => { } } + async function checkSearch() { + if (!provider) { + window.message.error({ + content: t('settings.websearch.no_provider_selected'), + duration: 3, + icon: , + key: 'no-provider-selected' + }) + return + } + + try { + setApiChecking(true) + const { valid, error } = await WebSearchService.checkSearch(provider) + + setApiValid(valid) + + if (!valid && error) { + const errorMessage = error.message ? ' ' + error.message : '' + window.message.error({ + content: errorMessage, + duration: 4, + key: 'search-check-error' + }) + } + updateProvider({ ...provider, enabled: true }) + } catch (err) { + console.error('Check search error:', err) + setApiValid(false) + window.message.error({ + content: t('settings.websearch.check_failed'), + duration: 3, + key: 'check-search-error' + }) + } finally { + setApiChecking(false) + setTimeout(() => setApiValid(false), 2500) + } + } + useEffect(() => { setApiKey(provider.apiKey ?? '') setApiHost(provider.apiHost ?? '') @@ -55,18 +101,27 @@ const WebSearchProviderSetting: FC = ({ provider: _provider }) => { - {provider.apiKey !== undefined && ( + {hasObjectKey(provider, 'apiKey') && ( <> - {t('settings.provider.api_key')} - setApiKey(e.target.value)} - onBlur={onUpdateApiKey} - spellCheck={false} - type="password" - autoFocus={apiKey === ''} - /> + {t('settings.provider.api_key')} + + setApiKey(e.target.value)} + onBlur={onUpdateApiKey} + spellCheck={false} + type="password" + autoFocus={apiKey === ''} + /> + + {t('settings.websearch.get_api_key')} @@ -74,9 +129,12 @@ const WebSearchProviderSetting: FC = ({ provider: _provider }) => { )} - {provider.apiHost !== undefined && ( + + {hasObjectKey(provider, 'apiHost') && ( <> - {t('settings.provider.api_host')} + + {t('settings.provider.api_host')} + { const { providers } = useWebSearchProviders() - const { provider: defaultProvider, setDefaultProvider, updateDefaultProvider } = useDefaultWebSearchProvider() - + const { provider: defaultProvider, setDefaultProvider } = useDefaultWebSearchProvider() const { t } = useTranslation() const [selectedProvider, setSelectedProvider] = useState(defaultProvider) const { theme: themeMode } = useTheme() - const [apiChecking, setApiChecking] = useState(false) - const [apiValid, setApiValid] = useState(false) - function updateSelectedWebSearchProvider(providerId: string) { const provider = providers.find((p) => p.id === providerId) if (!provider) { return } - setApiValid(false) setSelectedProvider(provider) setDefaultProvider(provider) } - async function checkSearch() { - // 检查是否选择了提供商 - if (!selectedProvider || !selectedProvider.id) { - window.message.error({ - content: t('settings.websearch.no_provider_selected'), - duration: 3, - icon: , - key: 'no-provider-selected' - }) - return - } - - try { - setApiChecking(true) - const { valid, error } = await WebSearchService.checkSearch(selectedProvider) - - setApiValid(valid) - - // 如果验证失败且有错误信息,显示错误 - if (!valid && error) { - const errorMessage = error.message ? ' ' + error.message : '' - window.message.error({ - content: errorMessage, - duration: 4, - key: 'search-check-error' - }) - } - updateDefaultProvider({ ...selectedProvider, enabled: true }) - } catch (err) { - console.error('Check search error:', err) - setApiValid(false) - window.message.error({ - content: t('settings.websearch.check_failed'), - duration: 3, - key: 'check-search-error' - }) - } finally { - setApiChecking(false) - } - } return ( @@ -88,16 +41,10 @@ const WebSearchSettings: FC = () => { placeholder={t('settings.websearch.search_provider_placeholder')} options={providers.map((p) => ({ value: p.id, label: p.name }))} /> - - + + {selectedProvider && } diff --git a/src/renderer/src/services/WebSearchService.ts b/src/renderer/src/services/WebSearchService.ts index 6f6ee1b0..cb152fc8 100644 --- a/src/renderer/src/services/WebSearchService.ts +++ b/src/renderer/src/services/WebSearchService.ts @@ -1,6 +1,7 @@ import store from '@renderer/store' import { setDefaultProvider } from '@renderer/store/websearch' import { WebSearchProvider, WebSearchResponse } from '@renderer/types' +import { hasObjectKey } from '@renderer/utils' import WebSearchEngineProvider from '@renderer/webSearchProvider/WebSearchEngineProvider' import dayjs from 'dayjs' @@ -38,7 +39,20 @@ class WebSearchService { public isWebSearchEnabled(): boolean { const { defaultProvider, providers } = this.getWebSearchState() const provider = providers.find((provider) => provider.id === defaultProvider) - return provider?.enabled ?? false + + if (!provider) { + return false + } + + if (hasObjectKey(provider, 'apiKey')) { + return provider.apiKey !== '' + } + + if (hasObjectKey(provider, 'apiHost')) { + return provider.apiHost !== '' + } + + return false } /** diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index f1db1506..acbf6046 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1214,16 +1214,22 @@ const migrateConfig = { isSystem: true, enabled: false }) - const existWebsearchProvider = state.websearch.providers.find((p) => p.id === 'tavily') - if (existWebsearchProvider && existWebsearchProvider.apiKey !== '') { - existWebsearchProvider.enabled = true + return state + }, + '77': (state: RootState) => { + if (state.websearch) { + if (!state.websearch.providers.find((p) => p.id === 'searxng')) { + state.websearch.providers.push({ + id: 'searxng', + name: 'Searxng', + apiHost: '' + }) + } + state.websearch.providers.forEach((p) => { + // @ts-ignore eslint-disable-next-line + delete p.enabled + }) } - state.websearch.providers.push({ - id: 'searxng', - name: 'Searxng', - enabled: false, - apiHost: '' - }) return state } } diff --git a/src/renderer/src/store/websearch.ts b/src/renderer/src/store/websearch.ts index f6d688a7..05f76fc2 100644 --- a/src/renderer/src/store/websearch.ts +++ b/src/renderer/src/store/websearch.ts @@ -42,7 +42,6 @@ const websearchSlice = createSlice({ updateWebSearchProviders: (state, action: PayloadAction) => { state.providers = action.payload }, - updateWebSearchProvider: (state, action: PayloadAction) => { const index = state.providers.findIndex((provider) => provider.id === action.payload.id) if (index !== -1) { diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index f1322010..7b39fd46 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -290,7 +290,6 @@ export type SidebarIcon = 'assistants' | 'agents' | 'paintings' | 'translate' | export type WebSearchProvider = { id: string name: string - enabled: boolean apiKey?: string apiHost?: string engines?: string[] diff --git a/src/renderer/src/utils/index.ts b/src/renderer/src/utils/index.ts index 7958918d..ce553a6b 100644 --- a/src/renderer/src/utils/index.ts +++ b/src/renderer/src/utils/index.ts @@ -474,4 +474,12 @@ export function getTitleFromString(str: string, length: number = 80) { return title } +export function hasObjectKey(obj: any, key: string) { + if (typeof obj !== 'object' || obj === null) { + return false + } + + return Object.keys(obj).includes(key) +} + export { classNames }