cherry-studio/src/renderer/src/pages/settings/WebSearchSettings.tsx
Chen Tao 09652b2e31 feat: add web search settings (#2314)
* fix: add time when using web search

* feat: add optional

* chore

* chore

* chore

* clean code

* feat: set search max results

* feat: add manual blacklist

* clean code

* chore

* chore

* clean
2025-02-25 23:46:51 +08:00

132 lines
4.9 KiB
TypeScript

import tavilyLogo from '@renderer/assets/images/search/tavily.svg'
import tavilyLogoDark from '@renderer/assets/images/search/tavily-dark.svg'
import { HStack } from '@renderer/components/Layout'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useWebSearchProvider } from '@renderer/hooks/useWebSearchProviders'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setExcludeDomains, setMaxResult, setSearchWithTime } from '@renderer/store/websearch'
import { formatDomains } from '@renderer/utils/blacklist'
import { Alert, Input, Slider, Switch, Typography } from 'antd'
import TextArea from 'antd/es/input/TextArea'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import {
SettingContainer,
SettingDivider,
SettingGroup,
SettingHelpLink,
SettingHelpTextRow,
SettingRow,
SettingRowTitle,
SettingTitle
} from '.'
const WebSearchSettings: FC = () => {
const { t } = useTranslation()
const { Paragraph } = Typography
const { theme } = useTheme()
const { provider, updateProvider } = useWebSearchProvider('tavily')
const [apiKey, setApiKey] = useState(provider.apiKey)
const logo = theme === 'dark' ? tavilyLogoDark : tavilyLogo
const searchWithTime = useAppSelector((state) => state.websearch.searchWithTime)
const maxResults = useAppSelector((state) => state.websearch.maxResults)
const excludeDomains = useAppSelector((state) => state.websearch.excludeDomains)
const [errFormat, setErrFormat] = useState(false)
const [blacklistInput, setBlacklistInput] = useState('')
const dispatch = useAppDispatch()
useEffect(() => {
return () => {
if (apiKey && apiKey !== provider.apiKey) {
updateProvider({ ...provider, apiKey })
}
}
}, [apiKey, provider, updateProvider])
useEffect(() => {
if (excludeDomains) {
setBlacklistInput(excludeDomains.join('\n'))
}
}, [excludeDomains])
function updateManualBlacklist(blacklist: string) {
const blacklistDomains = blacklist.split('\n').filter((url) => url.trim() !== '')
const { formattedDomains, hasError } = formatDomains(blacklistDomains)
setErrFormat(hasError)
if (hasError) return
dispatch(setExcludeDomains(formattedDomains))
}
return (
<SettingContainer theme={theme}>
<SettingGroup theme={theme}>
<HStack alignItems="center" gap={10}>
<TavilyLogo src={logo} alt="web-search" style={{ width: '60px' }} />
</HStack>
<SettingDivider />
<Paragraph type="secondary" style={{ margin: '10px 0' }}>
{t('settings.websearch.tavily.description')}
</Paragraph>
<Input.Password
style={{ width: '100%' }}
placeholder={t('settings.websearch.tavily.api_key.placeholder')}
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
onBlur={() => updateProvider({ ...provider, apiKey })}
/>
<SettingHelpTextRow style={{ justifyContent: 'space-between', marginTop: 5 }}>
<SettingHelpLink target="_blank" href="https://app.tavily.com/home">
{t('settings.websearch.get_api_key')}
</SettingHelpLink>
</SettingHelpTextRow>
</SettingGroup>
<SettingGroup theme={theme}>
<SettingTitle>{t('settings.general.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.websearch.search_with_time')}</SettingRowTitle>
<Switch checked={searchWithTime} onChange={(checked) => dispatch(setSearchWithTime(checked))} />
</SettingRow>
<SettingRow>
<SettingRowTitle>{t('settings.websearch.search_max_result')}</SettingRowTitle>
<Slider
defaultValue={maxResults}
style={{ width: '200px' }}
min={1}
max={20}
step={1}
marks={{ 1: '1', 5: t('settings.websearch.search_result_default'), 20: '20' }}
onChangeComplete={(value) => dispatch(setMaxResult(value))}
/>
</SettingRow>
</SettingGroup>
<SettingGroup theme={theme}>
<SettingTitle>{t('settings.websearch.blacklist')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.websearch.blacklist_description')}</SettingRowTitle>
</SettingRow>
<TextArea
value={blacklistInput}
onChange={(e) => setBlacklistInput(e.target.value)}
onBlur={() => updateManualBlacklist(blacklistInput)}
placeholder={t('settings.websearch.blacklist_tooltip')}
autoSize={{ minRows: 2, maxRows: 6 }}
rows={4}
/>
{errFormat && <Alert message={t('settings.websearch.blacklist_tooltip')} type="error" />}
</SettingGroup>
</SettingContainer>
)
}
const TavilyLogo = styled.img`
width: 80px;
`
export default WebSearchSettings