mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 18:50:56 +08:00
refactor: network search module, support quick menu (#5291)
* refactor: Reconstruct the network search module to support quick menu switching between different suppliers. * refactor(GeminiProvider): simplify web search enablement logic * refactor(settings): remove unnecessary SettingDivider for cleaner layout * refactor(SelectModelButton): remove unused setModel function for improved performance * refactor(ApiService): simplify web search condition by removing redundant check
This commit is contained in:
parent
7be6ddfb59
commit
0a7bf99f9c
@ -2393,7 +2393,7 @@ export function isGenerateImageModel(model: Model): boolean {
|
||||
}
|
||||
|
||||
export function getOpenAIWebSearchParams(assistant: Assistant, model: Model): Record<string, any> {
|
||||
if (WebSearchService.isWebSearchEnabled() && WebSearchService.isOverwriteEnabled()) {
|
||||
if (WebSearchService.isWebSearchEnabled()) {
|
||||
return {}
|
||||
}
|
||||
if (isWebSearchModel(model)) {
|
||||
|
||||
@ -136,7 +136,7 @@
|
||||
"input.translate": "Translate to {{target_language}}",
|
||||
"input.upload": "Upload image or document file",
|
||||
"input.upload.document": "Upload document file (model does not support images)",
|
||||
"input.web_search": "Enable web search",
|
||||
"input.web_search": "Web search",
|
||||
"input.web_search.button.ok": "Go to Settings",
|
||||
"input.web_search.enable": "Enable web search",
|
||||
"input.web_search.enable_content": "Need to check web search connectivity in settings first",
|
||||
@ -247,7 +247,10 @@
|
||||
"topics.export.title_naming_success": "Title generated successfully",
|
||||
"topics.export.title_naming_failed": "Failed to generate title, using default title",
|
||||
"input.translating": "Translating...",
|
||||
"input.upload.upload_from_local": "Upload local file..."
|
||||
"input.upload.upload_from_local": "Upload local file...",
|
||||
"input.web_search.builtin": "Model Built-in",
|
||||
"input.web_search.builtin.enabled_content": "Use the built-in web search function of the model",
|
||||
"input.web_search.builtin.disabled_content": "The current model does not support web search"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "Collapse",
|
||||
|
||||
@ -136,7 +136,7 @@
|
||||
"input.translate": "{{target_language}}に翻訳",
|
||||
"input.upload": "画像またはドキュメントをアップロード",
|
||||
"input.upload.document": "ドキュメントをアップロード(モデルは画像をサポートしません)",
|
||||
"input.web_search": "ウェブ検索を有効にする",
|
||||
"input.web_search": "ウェブ検索",
|
||||
"input.web_search.button.ok": "設定に移動",
|
||||
"input.web_search.enable": "ウェブ検索を有効にする",
|
||||
"input.web_search.enable_content": "ウェブ検索の接続性を先に設定で確認する必要があります",
|
||||
@ -247,7 +247,10 @@
|
||||
"topics.export.title_naming_success": "タイトルの生成に成功しました",
|
||||
"topics.export.title_naming_failed": "タイトルの生成に失敗しました。デフォルトのタイトルを使用します",
|
||||
"input.translating": "翻訳中...",
|
||||
"input.upload.upload_from_local": "ローカルファイルをアップロード..."
|
||||
"input.upload.upload_from_local": "ローカルファイルをアップロード...",
|
||||
"input.web_search.builtin": "モデル内蔵",
|
||||
"input.web_search.builtin.enabled_content": "モデル内蔵のウェブ検索機能を使用",
|
||||
"input.web_search.builtin.disabled_content": "現在のモデルはウェブ検索をサポートしていません"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "折りたたむ",
|
||||
|
||||
@ -136,7 +136,7 @@
|
||||
"input.translate": "Перевести на {{target_language}}",
|
||||
"input.upload": "Загрузить изображение или документ",
|
||||
"input.upload.document": "Загрузить документ (модель не поддерживает изображения)",
|
||||
"input.web_search": "Включить веб-поиск",
|
||||
"input.web_search": "Веб-поиск",
|
||||
"input.web_search.button.ok": "Перейти в Настройки",
|
||||
"input.web_search.enable": "Включить веб-поиск",
|
||||
"input.web_search.enable_content": "Необходимо предварительно проверить подключение к веб-поиску в настройках",
|
||||
@ -247,7 +247,10 @@
|
||||
"topics.export.title_naming_success": "Заголовок успешно создан",
|
||||
"topics.export.title_naming_failed": "Не удалось создать заголовок, используется заголовок по умолчанию",
|
||||
"input.translating": "Перевод...",
|
||||
"input.upload.upload_from_local": "Загрузить локальный файл..."
|
||||
"input.upload.upload_from_local": "Загрузить локальный файл...",
|
||||
"input.web_search.builtin": "Модель встроена",
|
||||
"input.web_search.builtin.enabled_content": "Используйте встроенную функцию веб-поиска модели",
|
||||
"input.web_search.builtin.disabled_content": "Текущая модель не поддерживает веб-поиск"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "Свернуть",
|
||||
|
||||
@ -138,10 +138,13 @@
|
||||
"input.upload": "上传图片或文档",
|
||||
"input.upload.upload_from_local": "上传本地文件...",
|
||||
"input.upload.document": "上传文档(模型不支持图片)",
|
||||
"input.web_search": "开启网络搜索",
|
||||
"input.web_search": "网络搜索",
|
||||
"input.web_search.button.ok": "去设置",
|
||||
"input.web_search.enable": "开启网络搜索",
|
||||
"input.web_search.enable_content": "需要先在设置中检查网络搜索连通性",
|
||||
"input.web_search.builtin": "模型内置",
|
||||
"input.web_search.builtin.enabled_content": "使用模型内置的网络搜索功能",
|
||||
"input.web_search.builtin.disabled_content": "当前模型不支持网络搜索功能",
|
||||
"message.new.branch": "分支",
|
||||
"message.new.branch.created": "新分支已创建",
|
||||
"message.new.context": "清除上下文",
|
||||
|
||||
@ -136,7 +136,7 @@
|
||||
"input.translate": "翻譯成{{target_language}}",
|
||||
"input.upload": "上傳圖片或文件",
|
||||
"input.upload.document": "上傳文件(模型不支援圖片)",
|
||||
"input.web_search": "開啟網路搜尋",
|
||||
"input.web_search": "網路搜尋",
|
||||
"input.web_search.button.ok": "去設定",
|
||||
"input.web_search.enable": "開啟網路搜尋",
|
||||
"input.web_search.enable_content": "需要先在設定中開啟網路搜尋",
|
||||
@ -247,7 +247,10 @@
|
||||
"topics.export.title_naming_success": "標題生成成功",
|
||||
"topics.export.title_naming_failed": "標題生成失敗,使用預設標題",
|
||||
"input.translating": "翻譯中...",
|
||||
"input.upload.upload_from_local": "上傳本地文件..."
|
||||
"input.upload.upload_from_local": "上傳本地文件...",
|
||||
"input.web_search.builtin": "模型內置",
|
||||
"input.web_search.builtin.enabled_content": "使用模型內置的網路搜尋功能",
|
||||
"input.web_search.builtin.disabled_content": "當前模型不支持網路搜尋功能"
|
||||
},
|
||||
"code_block": {
|
||||
"collapse": "折疊",
|
||||
|
||||
@ -51,7 +51,6 @@ import {
|
||||
// import { CompletionUsage } from 'openai/resources'
|
||||
import React, { CSSProperties, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import NarrowLayout from '../Messages/NarrowLayout'
|
||||
@ -67,6 +66,7 @@ import NewContextButton from './NewContextButton'
|
||||
import QuickPhrasesButton, { QuickPhrasesButtonRef } from './QuickPhrasesButton'
|
||||
import SendMessageButton from './SendMessageButton'
|
||||
import TokenCount from './TokenCount'
|
||||
import WebSearchButton, { WebSearchButtonRef } from './WebSearchButton'
|
||||
|
||||
interface Props {
|
||||
assistant: Assistant
|
||||
@ -116,7 +116,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
const currentMessageId = useRef<string>('')
|
||||
const isVision = useMemo(() => isVisionModel(model), [model])
|
||||
const supportExts = useMemo(() => [...textExts, ...documentExts, ...(isVision ? imageExts : [])], [isVision])
|
||||
const navigate = useNavigate()
|
||||
const { activedMcpServers } = useMCPServers()
|
||||
const { bases: knowledgeBases } = useKnowledgeBases()
|
||||
|
||||
@ -131,6 +130,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
const knowledgeBaseButtonRef = useRef<KnowledgeBaseButtonRef>(null)
|
||||
const mcpToolsButtonRef = useRef<MCPToolsButtonRef>(null)
|
||||
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
|
||||
const webSearchButtonRef = useRef<WebSearchButtonRef>(null)
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const debouncedEstimate = useCallback(
|
||||
@ -379,6 +379,15 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
mcpToolsButtonRef.current?.openResourcesList()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('chat.input.web_search'),
|
||||
description: '',
|
||||
icon: <Globe />,
|
||||
isMenu: true,
|
||||
action: () => {
|
||||
webSearchButtonRef.current?.openQuickPanel()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: isVisionModel(model) ? t('chat.input.upload') : t('chat.input.upload.document'),
|
||||
description: '',
|
||||
@ -770,46 +779,17 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
setSelectedKnowledgeBases(newKnowledgeBases ?? [])
|
||||
}
|
||||
|
||||
const showWebSearchEnableModal = () => {
|
||||
window.modal.confirm({
|
||||
title: t('chat.input.web_search.enable'),
|
||||
content: t('chat.input.web_search.enable_content'),
|
||||
centered: true,
|
||||
okText: t('chat.input.web_search.button.ok'),
|
||||
onOk: () => {
|
||||
navigate('/settings/web-search')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const shouldShowEnableModal = () => {
|
||||
// 网络搜索功能是否未启用
|
||||
const webSearchNotEnabled = !WebSearchService.isWebSearchEnabled()
|
||||
// 非网络搜索模型:仅当网络搜索功能未启用时显示启用提示
|
||||
if (!isWebSearchModel(model)) {
|
||||
return webSearchNotEnabled
|
||||
}
|
||||
// 网络搜索模型:当允许覆盖但网络搜索功能未启用时显示启用提示
|
||||
return WebSearchService.isOverwriteEnabled() && webSearchNotEnabled
|
||||
}
|
||||
|
||||
const onEnableWebSearch = () => {
|
||||
if (shouldShowEnableModal()) {
|
||||
showWebSearchEnableModal()
|
||||
return
|
||||
}
|
||||
|
||||
updateAssistant({ ...assistant, enableWebSearch: !assistant.enableWebSearch })
|
||||
}
|
||||
|
||||
const onEnableGenerateImage = () => {
|
||||
updateAssistant({ ...assistant, enableGenerateImage: !assistant.enableGenerateImage })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isWebSearchModel(model) && !WebSearchService.isWebSearchEnabled() && assistant.enableWebSearch) {
|
||||
if (!isWebSearchModel(model) && assistant.enableWebSearch) {
|
||||
updateAssistant({ ...assistant, enableWebSearch: false })
|
||||
}
|
||||
if (assistant.webSearchProviderId && !WebSearchService.isWebSearchEnabled(assistant.webSearchProviderId)) {
|
||||
updateAssistant({ ...assistant, webSearchProviderId: undefined })
|
||||
}
|
||||
if (!isGenerateImageModel(model) && assistant.enableGenerateImage) {
|
||||
updateAssistant({ ...assistant, enableGenerateImage: false })
|
||||
}
|
||||
@ -938,14 +918,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
|
||||
setFiles={setFiles}
|
||||
ToolbarButton={ToolbarButton}
|
||||
/>
|
||||
<Tooltip placement="top" title={t('chat.input.web_search')} arrow>
|
||||
<ToolbarButton type="text" onClick={onEnableWebSearch}>
|
||||
<Globe
|
||||
size={18}
|
||||
style={{ color: assistant.enableWebSearch ? 'var(--color-link)' : 'var(--color-icon)' }}
|
||||
/>
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
<WebSearchButton ref={webSearchButtonRef} assistant={assistant} ToolbarButton={ToolbarButton} />
|
||||
{showKnowledgeIcon && (
|
||||
<KnowledgeBaseButton
|
||||
ref={knowledgeBaseButtonRef}
|
||||
|
||||
129
src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx
Normal file
129
src/renderer/src/pages/home/Inputbar/WebSearchButton.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
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 } from '@renderer/types'
|
||||
import { hasObjectKey } from '@renderer/utils'
|
||||
import { Tooltip } from 'antd'
|
||||
import { Globe, Settings } from 'lucide-react'
|
||||
import { FC, useCallback, useImperativeHandle, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
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 navigate = useNavigate()
|
||||
const quickPanel = useQuickPanel()
|
||||
const { providers } = useWebSearchProviders()
|
||||
const { updateAssistant } = useAssistant(assistant.id)
|
||||
|
||||
const updateSelectedWebSearchProvider = useCallback(
|
||||
(providerId: WebSearchProvider['id']) => {
|
||||
// TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
|
||||
setTimeout(() => {
|
||||
const currentWebSearchProviderId = assistant.webSearchProviderId
|
||||
const newWebSearchProviderId = currentWebSearchProviderId === providerId ? undefined : providerId
|
||||
updateAssistant({ ...assistant, webSearchProviderId: newWebSearchProviderId, enableWebSearch: false })
|
||||
}, 200)
|
||||
},
|
||||
[assistant, updateAssistant]
|
||||
)
|
||||
|
||||
const updateSelectedWebSearchBuiltin = useCallback(() => {
|
||||
// TODO: updateAssistant有性能问题,会导致关闭快捷面板卡顿
|
||||
setTimeout(() => {
|
||||
updateAssistant({ ...assistant, webSearchProviderId: undefined, enableWebSearch: !assistant.enableWebSearch })
|
||||
}, 200)
|
||||
}, [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.websearch.apikey')
|
||||
: t('settings.websearch.free')
|
||||
: t('chat.input.web_search.enable_content'),
|
||||
icon: <Globe />,
|
||||
isSelected: p.id === assistant?.webSearchProviderId,
|
||||
disabled: !WebSearchService.isWebSearchEnabled(p.id),
|
||||
action: () => updateSelectedWebSearchProvider(p.id)
|
||||
}))
|
||||
|
||||
items.unshift({
|
||||
label: t('chat.input.web_search.builtin'),
|
||||
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()
|
||||
})
|
||||
items.push({
|
||||
label: '前往设置' + '...',
|
||||
icon: <Settings />,
|
||||
action: () => navigate('/settings/web-search')
|
||||
})
|
||||
|
||||
return items
|
||||
}, [
|
||||
assistant.model,
|
||||
assistant.enableWebSearch,
|
||||
assistant.webSearchProviderId,
|
||||
providers,
|
||||
t,
|
||||
updateSelectedWebSearchProvider,
|
||||
updateSelectedWebSearchBuiltin,
|
||||
navigate
|
||||
])
|
||||
|
||||
const openQuickPanel = useCallback(() => {
|
||||
quickPanel.open({
|
||||
title: t('chat.input.web_search'),
|
||||
list: providerItems,
|
||||
symbol: '?'
|
||||
})
|
||||
}, [quickPanel, providerItems, t])
|
||||
|
||||
const handleOpenQuickPanel = useCallback(() => {
|
||||
if (quickPanel.isVisible && quickPanel.symbol === '?') {
|
||||
quickPanel.close()
|
||||
} else {
|
||||
openQuickPanel()
|
||||
}
|
||||
}, [openQuickPanel, quickPanel])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
openQuickPanel
|
||||
}))
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" title={t('chat.input.web_search')} arrow>
|
||||
<ToolbarButton type="text" onClick={handleOpenQuickPanel}>
|
||||
<Globe
|
||||
size={18}
|
||||
style={{
|
||||
color:
|
||||
assistant?.webSearchProviderId || assistant.enableWebSearch ? 'var(--color-link)' : 'var(--color-icon)'
|
||||
}}
|
||||
/>
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default WebSearchButton
|
||||
@ -1,6 +1,7 @@
|
||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
|
||||
import { isLocalAi } from '@renderer/config/env'
|
||||
import { isWebSearchModel } from '@renderer/config/models'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { getProviderName } from '@renderer/services/ProviderService'
|
||||
import { Assistant } from '@renderer/types'
|
||||
@ -14,7 +15,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const SelectModelButton: FC<Props> = ({ assistant }) => {
|
||||
const { model, setModel } = useAssistant(assistant.id)
|
||||
const { model, updateAssistant } = useAssistant(assistant.id)
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (isLocalAi) {
|
||||
@ -25,7 +26,15 @@ const SelectModelButton: FC<Props> = ({ assistant }) => {
|
||||
event.currentTarget.blur()
|
||||
const selectedModel = await SelectModelPopup.show({ model })
|
||||
if (selectedModel) {
|
||||
setModel(selectedModel)
|
||||
// 避免更新数据造成关闭弹框的卡顿
|
||||
setTimeout(() => {
|
||||
const enabledWebSearch = isWebSearchModel(selectedModel)
|
||||
updateAssistant({
|
||||
...assistant,
|
||||
model: selectedModel,
|
||||
enableWebSearch: enabledWebSearch && assistant.enableWebSearch
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { setContentLimit, setMaxResult, setOverwrite, setSearchWithTime } from '@renderer/store/websearch'
|
||||
import { setContentLimit, setMaxResult, setSearchWithTime } from '@renderer/store/websearch'
|
||||
import { Input, Slider, Switch, Tooltip } from 'antd'
|
||||
import { t } from 'i18next'
|
||||
import { Info } from 'lucide-react'
|
||||
@ -11,7 +11,6 @@ import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle
|
||||
const BasicSettings: FC = () => {
|
||||
const { theme } = useTheme()
|
||||
const searchWithTime = useAppSelector((state) => state.websearch.searchWithTime)
|
||||
const overwrite = useAppSelector((state) => state.websearch.overwrite)
|
||||
const maxResults = useAppSelector((state) => state.websearch.maxResults)
|
||||
const contentLimit = useAppSelector((state) => state.websearch.contentLimit)
|
||||
|
||||
@ -26,16 +25,6 @@ const BasicSettings: FC = () => {
|
||||
<SettingRowTitle>{t('settings.websearch.search_with_time')}</SettingRowTitle>
|
||||
<Switch checked={searchWithTime} onChange={(checked) => dispatch(setSearchWithTime(checked))} />
|
||||
</SettingRow>
|
||||
<SettingDivider style={{ marginTop: 15, marginBottom: 12 }} />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>
|
||||
{t('settings.websearch.overwrite')}
|
||||
<Tooltip title={t('settings.websearch.overwrite_tooltip')} placement="right">
|
||||
<Info size={16} color="var(--color-icon)" style={{ marginLeft: 5, cursor: 'pointer' }} />
|
||||
</Tooltip>
|
||||
</SettingRowTitle>
|
||||
<Switch checked={overwrite} onChange={(checked) => dispatch(setOverwrite(checked))} />
|
||||
</SettingRow>
|
||||
<SettingDivider style={{ marginTop: 15, marginBottom: 10 }} />
|
||||
<SettingRow style={{ height: 40 }}>
|
||||
<SettingRowTitle>{t('settings.websearch.search_max_result')}</SettingRowTitle>
|
||||
|
||||
@ -185,7 +185,7 @@ const WebSearchProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
||||
<SettingSubtitle style={{ marginTop: 5, marginBottom: 10 }}>
|
||||
{t('settings.provider.api_host')}
|
||||
</SettingSubtitle>
|
||||
<Flex>
|
||||
<Flex gap={8}>
|
||||
<Input
|
||||
value={apiHost}
|
||||
placeholder={t('settings.provider.api_host')}
|
||||
|
||||
@ -29,7 +29,6 @@ import {
|
||||
filterEmptyMessages,
|
||||
filterUserRoleStartMessages
|
||||
} from '@renderer/services/MessagesService'
|
||||
import WebSearchService from '@renderer/services/WebSearchService'
|
||||
import {
|
||||
Assistant,
|
||||
FileType,
|
||||
@ -285,7 +284,7 @@ export default class GeminiProvider extends BaseProvider {
|
||||
const tools: ToolListUnion = []
|
||||
const toolResponses: MCPToolResponse[] = []
|
||||
|
||||
if (!WebSearchService.isOverwriteEnabled() && assistant.enableWebSearch && isWebSearchModel(model)) {
|
||||
if (assistant.enableWebSearch && isWebSearchModel(model)) {
|
||||
tools.push({
|
||||
// @ts-ignore googleSearch is not a valid tool for Gemini
|
||||
googleSearch: {}
|
||||
|
||||
@ -338,6 +338,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
const defaultModel = getDefaultModel()
|
||||
const model = assistant.model || defaultModel
|
||||
const { contextCount, maxTokens, streamOutput } = getAssistantSettings(assistant)
|
||||
const isEnabledWebSearch = assistant.enableWebSearch || !!assistant.webSearchProviderId
|
||||
messages = addImageFileToContents(messages)
|
||||
let systemMessage = { role: 'system', content: assistant.prompt || '' }
|
||||
if (isOpenAIoSeries(model) && !OPENAI_NO_SUPPORT_DEV_ROLE_MODELS.includes(model.id)) {
|
||||
@ -636,7 +637,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
} as LLMWebSearchCompleteChunk)
|
||||
}
|
||||
}
|
||||
if (assistant.enableWebSearch && isZhipuModel(model) && finishReason === 'stop' && chunk?.web_search) {
|
||||
if (isEnabledWebSearch && isZhipuModel(model) && finishReason === 'stop' && chunk?.web_search) {
|
||||
onChunk({
|
||||
type: ChunkType.LLM_WEB_SEARCH_COMPLETE,
|
||||
llm_web_search: {
|
||||
@ -645,7 +646,7 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
}
|
||||
} as LLMWebSearchCompleteChunk)
|
||||
}
|
||||
if (assistant.enableWebSearch && isHunyuanSearchModel(model) && chunk?.search_info?.search_results) {
|
||||
if (isEnabledWebSearch && isHunyuanSearchModel(model) && chunk?.search_info?.search_results) {
|
||||
onChunk({
|
||||
type: ChunkType.LLM_WEB_SEARCH_COMPLETE,
|
||||
llm_web_search: {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { getOpenAIWebSearchParams, isOpenAIWebSearch, isWebSearchModel } from '@renderer/config/models'
|
||||
import { getOpenAIWebSearchParams, isOpenAIWebSearch } from '@renderer/config/models'
|
||||
import { SEARCH_SUMMARY_PROMPT } from '@renderer/config/prompts'
|
||||
import i18n from '@renderer/i18n'
|
||||
import {
|
||||
@ -42,7 +42,7 @@ async function fetchExternalTool(
|
||||
// 可能会有重复?
|
||||
const knowledgeBaseIds = getKnowledgeBaseIds(lastUserMessage)
|
||||
const hasKnowledgeBase = !isEmpty(knowledgeBaseIds)
|
||||
const webSearchProvider = WebSearchService.getWebSearchProvider()
|
||||
const webSearchProvider = WebSearchService.getWebSearchProvider(assistant.webSearchProviderId)
|
||||
|
||||
// --- Keyword/Question Extraction Function ---
|
||||
const extract = async (): Promise<ExtractResults | undefined> => {
|
||||
@ -120,7 +120,7 @@ async function fetchExternalTool(
|
||||
// Use the consolidated processWebsearch function
|
||||
WebSearchService.createAbortSignal(lastUserMessage.id)
|
||||
return {
|
||||
results: await WebSearchService.processWebsearch(webSearchProvider, extractResults),
|
||||
results: await WebSearchService.processWebsearch(webSearchProvider!, extractResults),
|
||||
source: WebSearchSource.WEBSEARCH
|
||||
}
|
||||
} catch (error) {
|
||||
@ -157,8 +157,7 @@ async function fetchExternalTool(
|
||||
}
|
||||
}
|
||||
|
||||
const shouldWebSearch =
|
||||
assistant.enableWebSearch && (!isWebSearchModel(assistant.model!) || WebSearchService.isOverwriteEnabled())
|
||||
const shouldWebSearch = !!assistant.webSearchProviderId
|
||||
|
||||
// --- Execute Extraction and Searches ---
|
||||
const extractResults = await extract()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import WebSearchEngineProvider from '@renderer/providers/WebSearchProvider'
|
||||
import store from '@renderer/store'
|
||||
import { setDefaultProvider, WebSearchState } from '@renderer/store/websearch'
|
||||
import { WebSearchState } from '@renderer/store/websearch'
|
||||
import { WebSearchProvider, WebSearchProviderResponse } from '@renderer/types'
|
||||
import { hasObjectKey } from '@renderer/utils'
|
||||
import { addAbortController } from '@renderer/utils/abortController'
|
||||
@ -43,9 +43,9 @@ class WebSearchService {
|
||||
* @public
|
||||
* @returns 如果默认搜索提供商已启用则返回true,否则返回false
|
||||
*/
|
||||
public isWebSearchEnabled(): boolean {
|
||||
const { defaultProvider, providers } = this.getWebSearchState()
|
||||
const provider = providers.find((provider) => provider.id === defaultProvider)
|
||||
public isWebSearchEnabled(providerId?: WebSearchProvider['id']): boolean {
|
||||
const { providers } = this.getWebSearchState()
|
||||
const provider = providers.find((provider) => provider.id === providerId)
|
||||
|
||||
if (!provider) {
|
||||
return false
|
||||
@ -67,6 +67,8 @@ class WebSearchService {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 支持在快捷菜单中自选搜索供应商,所以这个不再适用
|
||||
*
|
||||
* 检查是否启用覆盖搜索
|
||||
* @public
|
||||
* @returns 如果启用覆盖搜索则返回true,否则返回false
|
||||
@ -80,21 +82,10 @@ class WebSearchService {
|
||||
* 获取当前默认的网络搜索提供商
|
||||
* @public
|
||||
* @returns 网络搜索提供商
|
||||
* @throws 如果找不到默认提供商则抛出错误
|
||||
*/
|
||||
public getWebSearchProvider(): WebSearchProvider {
|
||||
const { defaultProvider, providers } = this.getWebSearchState()
|
||||
let provider = providers.find((provider) => provider.id === defaultProvider)
|
||||
|
||||
if (!provider) {
|
||||
provider = providers[0]
|
||||
if (provider) {
|
||||
// 可选:自动更新默认提供商
|
||||
store.dispatch(setDefaultProvider(provider.id))
|
||||
} else {
|
||||
throw new Error(`No web search providers available`)
|
||||
}
|
||||
}
|
||||
public getWebSearchProvider(providerId?: string): WebSearchProvider | undefined {
|
||||
const { providers } = this.getWebSearchState()
|
||||
const provider = providers.find((provider) => provider.id === providerId)
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ export interface SubscribeSource {
|
||||
|
||||
export interface WebSearchState {
|
||||
// 默认搜索提供商的ID
|
||||
/** @deprecated 支持在快捷菜单中自选搜索供应商,所以这个不再适用 */
|
||||
defaultProvider: string
|
||||
// 所有可用的搜索提供商列表
|
||||
providers: WebSearchProvider[]
|
||||
@ -21,6 +22,7 @@ export interface WebSearchState {
|
||||
// 订阅源列表
|
||||
subscribeSources: SubscribeSource[]
|
||||
// 是否覆盖服务商搜索
|
||||
/** @deprecated 支持在快捷菜单中自选搜索供应商,所以这个不再适用 */
|
||||
overwrite: boolean
|
||||
contentLimit?: number
|
||||
}
|
||||
|
||||
@ -18,7 +18,9 @@ export type Assistant = {
|
||||
defaultModel?: Model
|
||||
settings?: Partial<AssistantSettings>
|
||||
messages?: AssistantMessage[]
|
||||
/** enableWebSearch 代表使用模型内置网络搜索功能 */
|
||||
enableWebSearch?: boolean
|
||||
webSearchProviderId?: WebSearchProvider['id']
|
||||
enableGenerateImage?: boolean
|
||||
mcpServers?: MCPServer[]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user