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:
Teo 2025-04-29 16:31:41 +08:00 committed by GitHub
parent 7be6ddfb59
commit 0a7bf99f9c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 204 additions and 95 deletions

View File

@ -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)) {

View File

@ -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",

View File

@ -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": "折りたたむ",

View File

@ -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": "Свернуть",

View File

@ -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": "清除上下文",

View File

@ -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": "折疊",

View File

@ -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}

View 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

View File

@ -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)
}
}

View File

@ -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>

View File

@ -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')}

View File

@ -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: {}

View File

@ -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: {

View File

@ -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()

View File

@ -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 truefalse
*/
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 truefalse
@ -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
}

View File

@ -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
}

View File

@ -18,7 +18,9 @@ export type Assistant = {
defaultModel?: Model
settings?: Partial<AssistantSettings>
messages?: AssistantMessage[]
/** enableWebSearch 代表使用模型内置网络搜索功能 */
enableWebSearch?: boolean
webSearchProviderId?: WebSearchProvider['id']
enableGenerateImage?: boolean
mcpServers?: MCPServer[]
}