bef0180e4c
* feat(类型): 添加WebSearchProviderIds常量并更新WebSearchProvider类型 * refactor(web-search): 重构网络搜索提供商配置和logo获取逻辑 将webSearchProviders.ts中的提供商logo获取函数移动到使用组件中 并优化提供商配置的类型定义 * feat(WebSearchButton): 添加不同搜索引擎的图标支持 为WebSearchButton组件添加多个搜索引擎的图标支持,包括Baidu、Google、Bing等 * feat(types): 添加预处理和网页搜索提供者的类型校验函数 添加 PreprocessProviderId 和 WebSearchProviderId 的类型校验函数 isPreprocessProviderId 和 isWebSearchProviderId,用于验证字符串是否为有效的提供者 ID * refactor(types): 重命名ApiProviderUnion并添加更新函数类型 添加用于更新不同类型API提供者的函数类型,提高类型安全性 * refactor(websearch): 将搜索提供商配置提取到单独文件 将websearch store中的搜索提供商配置提取到单独的配置文件,提高代码可维护性 * refactor(PreprocessSettings): 移除未使用的 system 选项禁用逻辑 由于 system 字段实际未使用,移除相关代码以简化逻辑 * refactor(api-key-popup): 移除providerKind参数,改用providerId判断类型 * refactor(preprocessProviders): 使用类型定义优化预处理提供者配置 将 providerId 参数类型从 string 改为 PreprocessProviderId 为 PREPROCESS_PROVIDER_CONFIG 添加类型定义 * refactor(hooks): 使用PreprocessProviderId类型替换字符串类型参数 * refactor(hooks): 使用 WebSearchProviderId 类型替换字符串类型参数 将 useWebSearchProvider 钩子的 id 参数类型从 string 改为 WebSearchProviderId,提高类型安全性 * refactor(knowledge): 将providerId类型改为PreprocessProviderId * refactor(PreprocessSettings): 移除未使用的options相关代码 清理PreprocessSettings组件中已被注释掉的options状态和相关逻辑,简化代码结构 * refactor(WebSearchProviderSetting): 将providerId类型从string改为WebSearchProviderId * refactor(websearch): 移除WebSearchProvider类型中不必要的id字段约束 * style(WebSearchButton): 调整图标大小和样式以保持视觉一致性 * fix(ApiKeyListPopup): 修正LLM提供者判断逻辑 使用'models'属性检查替代原有逻辑,更准确地判断是否为LLM provider * fix(ApiKeyListPopup): 修复预处理provider判断逻辑 处理mistral同时提供预处理和llm服务的情况,避免误判
175 lines
6.0 KiB
TypeScript
175 lines
6.0 KiB
TypeScript
import { BaiduOutlined, GoogleOutlined } from '@ant-design/icons'
|
|
import { BingLogo, BochaLogo, ExaLogo, SearXNGLogo, TavilyLogo } from '@renderer/components/Icons'
|
|
import { QuickPanelListItem, useQuickPanel } from '@renderer/components/QuickPanel'
|
|
import { isWebSearchModel } from '@renderer/config/models'
|
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
|
import { useWebSearchProviders } from '@renderer/hooks/useWebSearchProviders'
|
|
import WebSearchService from '@renderer/services/WebSearchService'
|
|
import { Assistant, WebSearchProvider, WebSearchProviderId } from '@renderer/types'
|
|
import { hasObjectKey } from '@renderer/utils'
|
|
import { Tooltip } from 'antd'
|
|
import { Globe } from 'lucide-react'
|
|
import { FC, memo, startTransition, useCallback, useImperativeHandle, useMemo } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
export interface WebSearchButtonRef {
|
|
openQuickPanel: () => void
|
|
}
|
|
|
|
interface Props {
|
|
ref?: React.RefObject<WebSearchButtonRef | null>
|
|
assistant: Assistant
|
|
ToolbarButton: any
|
|
}
|
|
|
|
const WebSearchButton: FC<Props> = ({ ref, assistant, ToolbarButton }) => {
|
|
const { t } = useTranslation()
|
|
const quickPanel = useQuickPanel()
|
|
const { providers } = useWebSearchProviders()
|
|
const { updateAssistant } = useAssistant(assistant.id)
|
|
|
|
const enableWebSearch = assistant?.webSearchProviderId || assistant.enableWebSearch
|
|
|
|
const WebSearchIcon = useCallback(
|
|
({ pid, size = 18 }: { pid?: WebSearchProviderId; size?: number }) => {
|
|
const iconColor = enableWebSearch ? 'var(--color-primary)' : 'var(--color-icon)'
|
|
|
|
switch (pid) {
|
|
case 'bocha':
|
|
return <BochaLogo width={size} height={size} color={iconColor} />
|
|
case 'exa':
|
|
// size微调,视觉上和其他图标平衡一些
|
|
return <ExaLogo width={size - 2} height={size} color={iconColor} />
|
|
case 'tavily':
|
|
return <TavilyLogo width={size} height={size} color={iconColor} />
|
|
case 'searxng':
|
|
return <SearXNGLogo width={size} height={size} color={iconColor} />
|
|
case 'local-baidu':
|
|
return <BaiduOutlined size={size} style={{ color: iconColor, fontSize: size }} />
|
|
case 'local-bing':
|
|
return <BingLogo width={size} height={size} color={iconColor} />
|
|
case 'local-google':
|
|
return <GoogleOutlined size={size} style={{ color: iconColor, fontSize: size }} />
|
|
default:
|
|
return <Globe size={size} style={{ color: iconColor, fontSize: size }} />
|
|
}
|
|
},
|
|
[enableWebSearch]
|
|
)
|
|
|
|
const updateSelectedWebSearchProvider = useCallback(
|
|
async (providerId?: WebSearchProvider['id']) => {
|
|
// TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
|
|
const currentWebSearchProviderId = assistant.webSearchProviderId
|
|
const newWebSearchProviderId = currentWebSearchProviderId === providerId ? undefined : providerId
|
|
startTransition(() => {
|
|
updateAssistant({ ...assistant, webSearchProviderId: newWebSearchProviderId, enableWebSearch: false })
|
|
})
|
|
},
|
|
[assistant, updateAssistant]
|
|
)
|
|
|
|
const updateSelectedWebSearchBuiltin = useCallback(async () => {
|
|
// TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
|
|
startTransition(() => {
|
|
updateAssistant({ ...assistant, webSearchProviderId: undefined, enableWebSearch: !assistant.enableWebSearch })
|
|
})
|
|
}, [assistant, updateAssistant])
|
|
|
|
const providerItems = useMemo<QuickPanelListItem[]>(() => {
|
|
const isWebSearchModelEnabled = assistant.model && isWebSearchModel(assistant.model)
|
|
|
|
const items: QuickPanelListItem[] = providers
|
|
.map((p) => ({
|
|
label: p.name,
|
|
description: WebSearchService.isWebSearchEnabled(p.id)
|
|
? hasObjectKey(p, 'apiKey')
|
|
? t('settings.tool.websearch.apikey')
|
|
: t('settings.tool.websearch.free')
|
|
: t('chat.input.web_search.enable_content'),
|
|
icon: <WebSearchIcon size={13} pid={p.id} />,
|
|
isSelected: p.id === assistant?.webSearchProviderId,
|
|
disabled: !WebSearchService.isWebSearchEnabled(p.id),
|
|
action: () => updateSelectedWebSearchProvider(p.id)
|
|
}))
|
|
.filter((o) => !o.disabled)
|
|
|
|
if (isWebSearchModelEnabled) {
|
|
items.unshift({
|
|
label: t('chat.input.web_search.builtin.label'),
|
|
description: isWebSearchModelEnabled
|
|
? t('chat.input.web_search.builtin.enabled_content')
|
|
: t('chat.input.web_search.builtin.disabled_content'),
|
|
icon: <Globe />,
|
|
isSelected: assistant.enableWebSearch,
|
|
disabled: !isWebSearchModelEnabled,
|
|
action: () => updateSelectedWebSearchBuiltin()
|
|
})
|
|
}
|
|
|
|
return items
|
|
}, [
|
|
WebSearchIcon,
|
|
assistant.enableWebSearch,
|
|
assistant.model,
|
|
assistant?.webSearchProviderId,
|
|
providers,
|
|
t,
|
|
updateSelectedWebSearchBuiltin,
|
|
updateSelectedWebSearchProvider
|
|
])
|
|
|
|
const openQuickPanel = useCallback(() => {
|
|
if (assistant.webSearchProviderId) {
|
|
updateSelectedWebSearchProvider(undefined)
|
|
return
|
|
}
|
|
|
|
if (assistant.enableWebSearch) {
|
|
updateSelectedWebSearchBuiltin()
|
|
return
|
|
}
|
|
|
|
quickPanel.open({
|
|
title: t('chat.input.web_search.label'),
|
|
list: providerItems,
|
|
symbol: '?',
|
|
pageSize: 9
|
|
})
|
|
}, [
|
|
assistant.webSearchProviderId,
|
|
assistant.enableWebSearch,
|
|
quickPanel,
|
|
t,
|
|
providerItems,
|
|
updateSelectedWebSearchProvider,
|
|
updateSelectedWebSearchBuiltin
|
|
])
|
|
|
|
const handleOpenQuickPanel = useCallback(() => {
|
|
if (quickPanel.isVisible && quickPanel.symbol === '?') {
|
|
quickPanel.close()
|
|
} else {
|
|
openQuickPanel()
|
|
}
|
|
}, [openQuickPanel, quickPanel])
|
|
|
|
useImperativeHandle(ref, () => ({
|
|
openQuickPanel
|
|
}))
|
|
|
|
return (
|
|
<Tooltip
|
|
placement="top"
|
|
title={enableWebSearch ? t('common.close') : t('chat.input.web_search.label')}
|
|
mouseLeaveDelay={0}
|
|
arrow>
|
|
<ToolbarButton type="text" onClick={handleOpenQuickPanel}>
|
|
<WebSearchIcon pid={assistant.webSearchProviderId} />
|
|
</ToolbarButton>
|
|
</Tooltip>
|
|
)
|
|
}
|
|
|
|
export default memo(WebSearchButton)
|