feat: add search enhance mode switch

This commit is contained in:
kangfenmao 2025-03-19 16:59:43 +08:00
parent b89213b1ab
commit 11620828ad
14 changed files with 102 additions and 50 deletions

View File

@ -80,8 +80,8 @@ afterPack: scripts/after-pack.js
afterSign: scripts/notarize.js afterSign: scripts/notarize.js
releaseInfo: releaseInfo:
releaseNotes: | releaseNotes: |
支持引文预览功能 知识库设置增加重排模型,提升知识库的准确性
界面显示优化 自定义服务商增加兼容模式
修复快捷弹窗无法对话问题 增加 Github Copilot 服务商
MCP修复豆包无法使用问题 PlantUML 预览支持放大和缩小
MCP修复 Linux 上无法安装问题 联网模式支持增强模式

View File

@ -1075,6 +1075,8 @@
"search_provider_placeholder": "Choose a search service provider.", "search_provider_placeholder": "Choose a search service provider.",
"search_result_default": "Default", "search_result_default": "Default",
"search_with_time": "Search with dates included", "search_with_time": "Search with dates included",
"enhance_mode": "Search enhance mode",
"enhance_mode_tooltip": "Use the default model to extract search keywords from the problem and search",
"tavily": { "tavily": {
"api_key": "Tavily API Key", "api_key": "Tavily API Key",
"api_key.placeholder": "Enter Tavily API Key", "api_key.placeholder": "Enter Tavily API Key",

View File

@ -1075,6 +1075,8 @@
"search_provider_placeholder": "検索サービスプロバイダーを選択する", "search_provider_placeholder": "検索サービスプロバイダーを選択する",
"search_result_default": "デフォルト", "search_result_default": "デフォルト",
"search_with_time": "日付を含む検索", "search_with_time": "日付を含む検索",
"enhance_mode": "検索強化モード",
"enhance_mode_tooltip": "デフォルトモデルを使用して問題から検索キーワードを抽出し、検索を実行します",
"tavily": { "tavily": {
"api_key": "Tavily API キー", "api_key": "Tavily API キー",
"api_key.placeholder": "Tavily API キーを入力してください", "api_key.placeholder": "Tavily API キーを入力してください",

View File

@ -1075,6 +1075,8 @@
"search_provider_placeholder": "Выберите поставщика поисковых услуг", "search_provider_placeholder": "Выберите поставщика поисковых услуг",
"search_result_default": "По умолчанию", "search_result_default": "По умолчанию",
"search_with_time": "Поиск, содержащий дату", "search_with_time": "Поиск, содержащий дату",
"enhance_mode": "Режим улучшения поиска",
"enhance_mode_tooltip": "Используйте модель по умолчанию для извлечения ключевых слов из проблемы и поиска",
"tavily": { "tavily": {
"api_key": "Ключ API Tavily", "api_key": "Ключ API Tavily",
"api_key.placeholder": "Введите ключ API Tavily", "api_key.placeholder": "Введите ключ API Tavily",

View File

@ -1075,6 +1075,8 @@
"search_provider_placeholder": "选择一个搜索服务商", "search_provider_placeholder": "选择一个搜索服务商",
"search_result_default": "默认", "search_result_default": "默认",
"search_with_time": "搜索包含日期", "search_with_time": "搜索包含日期",
"enhance_mode": "搜索增强模式",
"enhance_mode_tooltip": "使用默认模型提取关键词后搜索",
"tavily": { "tavily": {
"api_key": "Tavily API 密钥", "api_key": "Tavily API 密钥",
"api_key.placeholder": "请输入 Tavily API 密钥", "api_key.placeholder": "请输入 Tavily API 密钥",

View File

@ -1075,6 +1075,8 @@
"search_provider_placeholder": "選擇一個搜尋服務商", "search_provider_placeholder": "選擇一個搜尋服務商",
"search_result_default": "預設", "search_result_default": "預設",
"search_with_time": "搜尋包含日期", "search_with_time": "搜尋包含日期",
"enhance_mode": "搜索增強模式",
"enhance_mode_tooltip": "使用預設模型提取關鍵詞後搜索",
"tavily": { "tavily": {
"api_key": "Tavily API 金鑰", "api_key": "Tavily API 金鑰",
"api_key.placeholder": "請輸入 Tavily API 金鑰", "api_key.placeholder": "請輸入 Tavily API 金鑰",

View File

@ -80,6 +80,7 @@ const MCPSettings: FC = () => {
return ( return (
<Paragraph <Paragraph
className="selectable"
ellipsis={{ ellipsis={{
rows: 1, rows: 1,
expandable: 'collapsible', expandable: 'collapsible',

View File

@ -1,7 +1,8 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setMaxResult, setSearchWithTime } from '@renderer/store/websearch' import { setEnhanceMode, setMaxResult, setSearchWithTime } from '@renderer/store/websearch'
import { Slider, Switch } from 'antd' import { Slider, Switch, Tooltip } from 'antd'
import { t } from 'i18next' import { t } from 'i18next'
import { FC } from 'react' import { FC } from 'react'
@ -10,21 +11,32 @@ import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle
const BasicSettings: FC = () => { const BasicSettings: FC = () => {
const { theme } = useTheme() const { theme } = useTheme()
const searchWithTime = useAppSelector((state) => state.websearch.searchWithTime) const searchWithTime = useAppSelector((state) => state.websearch.searchWithTime)
const enhanceMode = useAppSelector((state) => state.websearch.enhanceMode)
const maxResults = useAppSelector((state) => state.websearch.maxResults) const maxResults = useAppSelector((state) => state.websearch.maxResults)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
return ( return (
<> <>
<SettingGroup theme={theme}> <SettingGroup theme={theme} style={{ paddingBottom: 8 }}>
<SettingTitle>{t('settings.general.title')}</SettingTitle> <SettingTitle>{t('settings.general.title')}</SettingTitle>
<SettingDivider /> <SettingDivider />
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.websearch.search_with_time')}</SettingRowTitle> <SettingRowTitle>{t('settings.websearch.search_with_time')}</SettingRowTitle>
<Switch checked={searchWithTime} onChange={(checked) => dispatch(setSearchWithTime(checked))} /> <Switch checked={searchWithTime} onChange={(checked) => dispatch(setSearchWithTime(checked))} />
</SettingRow> </SettingRow>
<SettingDivider style={{ marginTop: 15, marginBottom: 5 }} /> <SettingDivider style={{ marginTop: 15, marginBottom: 12 }} />
<SettingRow style={{ marginBottom: -10 }}> <SettingRow>
<SettingRowTitle>
{t('settings.websearch.enhance_mode')}
<Tooltip title={t('settings.websearch.enhance_mode_tooltip')} placement="right">
<InfoCircleOutlined style={{ marginLeft: 5, color: 'var(--color-icon)', cursor: 'pointer' }} />
</Tooltip>
</SettingRowTitle>
<Switch checked={enhanceMode} onChange={(checked) => dispatch(setEnhanceMode(checked))} />
</SettingRow>
<SettingDivider style={{ marginTop: 15, marginBottom: 12 }} />
<SettingRow>
<SettingRowTitle>{t('settings.websearch.search_max_result')}</SettingRowTitle> <SettingRowTitle>{t('settings.websearch.search_max_result')}</SettingRowTitle>
<Slider <Slider
defaultValue={maxResults} defaultValue={maxResults}

View File

@ -476,13 +476,18 @@ export default class AnthropicProvider extends BaseProvider {
content: messages.map((m) => m.content).join('\n') content: messages.map((m) => m.content).join('\n')
} }
const response = await this.sdk.messages.create({ const response = await this.sdk.messages.create(
{
messages: [userMessage] as Anthropic.Messages.MessageParam[], messages: [userMessage] as Anthropic.Messages.MessageParam[],
model: model.id, model: model.id,
system: systemMessage.content, system: systemMessage.content,
stream: false, stream: false,
max_tokens: 4096 max_tokens: 4096
}) },
{
timeout: 20 * 1000
}
)
const content = response.content[0].type === 'text' ? response.content[0].text : '' const content = response.content[0].type === 'text' ? response.content[0].text : ''

View File

@ -518,7 +518,10 @@ export default class GeminiProvider extends BaseProvider {
temperature: assistant?.settings?.temperature temperature: assistant?.settings?.temperature
} }
}, },
this.requestOptions {
...this.requestOptions,
timeout: 20 * 1000
}
) )
const chat = await geminiModel.startChat() const chat = await geminiModel.startChat()

View File

@ -780,13 +780,18 @@ export default class OpenAIProvider extends BaseProvider {
content: messages.map((m) => m.content).join('\n') content: messages.map((m) => m.content).join('\n')
} }
// @ts-ignore key is not typed // @ts-ignore key is not typed
const response = await this.sdk.chat.completions.create({ const response = await this.sdk.chat.completions.create(
{
model: model.id, model: model.id,
messages: [systemMessage, userMessage] as ChatCompletionMessageParam[], messages: [systemMessage, userMessage] as ChatCompletionMessageParam[],
stream: false, stream: false,
keep_alive: this.keepAliveTime, keep_alive: this.keepAliveTime,
max_tokens: 1000 max_tokens: 1000
}) },
{
timeout: 20 * 1000
}
)
// 针对思考类模型的返回,总结仅截取</think>之后的内容 // 针对思考类模型的返回,总结仅截取</think>之后的内容
let content = response.choices[0].message?.content || '' let content = response.choices[0].message?.content || ''

View File

@ -49,6 +49,7 @@ export async function fetchChatCompletion({
const lastMessage = findLast(messages, (m) => m.role === 'user') const lastMessage = findLast(messages, (m) => m.role === 'user')
const lastAnswer = findLast(messages, (m) => m.role === 'assistant') const lastAnswer = findLast(messages, (m) => m.role === 'assistant')
const hasKnowledgeBase = !isEmpty(lastMessage?.knowledgeBaseIds) const hasKnowledgeBase = !isEmpty(lastMessage?.knowledgeBaseIds)
if (lastMessage) { if (lastMessage) {
if (hasKnowledgeBase) { if (hasKnowledgeBase) {
window.message.info({ window.message.info({
@ -57,25 +58,28 @@ export async function fetchChatCompletion({
}) })
} }
// 更新消息状态为搜索中
onResponse({ ...message, status: 'searching' })
try { try {
// 等待关键词生成完成 // 等待关键词生成完成
const searchSummaryAssistant = getDefaultAssistant() const searchSummaryAssistant = getDefaultAssistant()
searchSummaryAssistant.model = assistant.model || getDefaultModel() searchSummaryAssistant.model = assistant.model || getDefaultModel()
searchSummaryAssistant.prompt = SEARCH_SUMMARY_PROMPT searchSummaryAssistant.prompt = SEARCH_SUMMARY_PROMPT
// 如果启用搜索增强模式,则使用搜索增强模式
if (WebSearchService.isEnhanceModeEnabled()) {
const keywords = await fetchSearchSummary({ const keywords = await fetchSearchSummary({
messages: lastAnswer ? [lastAnswer, lastMessage] : [lastMessage], messages: lastAnswer ? [lastAnswer, lastMessage] : [lastMessage],
assistant: searchSummaryAssistant assistant: searchSummaryAssistant
}) })
if (keywords) { if (keywords) {
query = keywords query = keywords
}
} else { } else {
query = lastMessage.content query = lastMessage.content
} }
// 更新消息状态为搜索中
onResponse({ ...message, status: 'searching' })
// 等待搜索完成 // 等待搜索完成
const webSearch = await WebSearchService.search(webSearchProvider, query) const webSearch = await WebSearchService.search(webSearchProvider, query)
@ -84,6 +88,7 @@ export async function fetchChatCompletion({
...message.metadata, ...message.metadata,
webSearch: webSearch webSearch: webSearch
} }
window.keyv.set(`web-search-${lastMessage?.id}`, webSearch) window.keyv.set(`web-search-${lastMessage?.id}`, webSearch)
} catch (error) { } catch (error) {
console.error('Web search failed:', error) console.error('Web search failed:', error)
@ -93,6 +98,7 @@ export async function fetchChatCompletion({
} }
const allMCPTools = await window.api.mcp.listTools() const allMCPTools = await window.api.mcp.listTools()
await AI.completions({ await AI.completions({
messages: filterUsefulMessages(messages), messages: filterUsefulMessages(messages),
assistant, assistant,

View File

@ -1,23 +1,10 @@
import store from '@renderer/store' import store from '@renderer/store'
import { setDefaultProvider } from '@renderer/store/websearch' import { setDefaultProvider, WebSearchState } from '@renderer/store/websearch'
import { WebSearchProvider, WebSearchResponse } from '@renderer/types' import { WebSearchProvider, WebSearchResponse } from '@renderer/types'
import { hasObjectKey } from '@renderer/utils' import { hasObjectKey } from '@renderer/utils'
import WebSearchEngineProvider from '@renderer/webSearchProvider/WebSearchEngineProvider' import WebSearchEngineProvider from '@renderer/webSearchProvider/WebSearchEngineProvider'
import dayjs from 'dayjs' import dayjs from 'dayjs'
interface WebSearchState {
// 默认搜索提供商的ID
defaultProvider: string
// 所有可用的搜索提供商列表
providers: WebSearchProvider[]
// 是否在搜索查询中添加当前日期
searchWithTime: boolean
// 搜索结果的最大数量
maxResults: number
// 要排除的域名列表
excludeDomains: string[]
}
/** /**
* *
*/ */
@ -55,6 +42,16 @@ class WebSearchService {
return false return false
} }
/**
*
* @public
* @returns truefalse
*/
public isEnhanceModeEnabled(): boolean {
const { enhanceMode } = this.getWebSearchState()
return enhanceMode
}
/** /**
* *
* @public * @public

View File

@ -1,11 +1,19 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import type { WebSearchProvider } from '@renderer/types' import type { WebSearchProvider } from '@renderer/types'
export interface WebSearchState { export interface WebSearchState {
// 默认搜索提供商的ID
defaultProvider: string defaultProvider: string
// 所有可用的搜索提供商列表
providers: WebSearchProvider[] providers: WebSearchProvider[]
// 是否在搜索查询中添加当前日期
searchWithTime: boolean searchWithTime: boolean
// 搜索结果的最大数量
maxResults: number maxResults: number
// 要排除的域名列表
excludeDomains: string[] excludeDomains: string[]
// 是否启用搜索增强模式
enhanceMode: boolean
} }
const initialState: WebSearchState = { const initialState: WebSearchState = {
@ -29,7 +37,8 @@ const initialState: WebSearchState = {
], ],
searchWithTime: true, searchWithTime: true,
maxResults: 5, maxResults: 5,
excludeDomains: [] excludeDomains: [],
enhanceMode: false
} }
const websearchSlice = createSlice({ const websearchSlice = createSlice({
@ -59,6 +68,9 @@ const websearchSlice = createSlice({
}, },
setExcludeDomains: (state, action: PayloadAction<string[]>) => { setExcludeDomains: (state, action: PayloadAction<string[]>) => {
state.excludeDomains = action.payload state.excludeDomains = action.payload
},
setEnhanceMode: (state, action: PayloadAction<boolean>) => {
state.enhanceMode = action.payload
} }
} }
}) })
@ -70,7 +82,8 @@ export const {
setDefaultProvider, setDefaultProvider,
setSearchWithTime, setSearchWithTime,
setExcludeDomains, setExcludeDomains,
setMaxResult setMaxResult,
setEnhanceMode
} = websearchSlice.actions } = websearchSlice.actions
export default websearchSlice.reducer export default websearchSlice.reducer