mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 23:12:38 +08:00
Feat: url context for Gemini models (#7931)
* feat: Add URL Context ability for Gemini Models * feat: Adding URL Context Button to tool bar and make it visible only when gemini models selected. It is not working (adding urlContext tools) for now. * fix: trying to force enable UrlContext function * fix: enableUrlContext indication reverted * feat: migration script for refreshing tool order to add URL Context button. * fix: optimize migrate.ts * fix: upgrade version --------- Co-authored-by: suyao <sy20010504@gmail.com>
This commit is contained in:
parent
1b129636ed
commit
71917eb0ec
@ -443,7 +443,7 @@ export class GeminiAPIClient extends BaseApiClient<
|
||||
messages: GeminiSdkMessageParam[]
|
||||
metadata: Record<string, any>
|
||||
}> => {
|
||||
const { messages, mcpTools, maxTokens, enableWebSearch, enableGenerateImage } = coreRequest
|
||||
const { messages, mcpTools, maxTokens, enableWebSearch, enableUrlContext, enableGenerateImage } = coreRequest
|
||||
// 1. 处理系统消息
|
||||
let systemInstruction = assistant.prompt
|
||||
|
||||
@ -483,6 +483,12 @@ export class GeminiAPIClient extends BaseApiClient<
|
||||
})
|
||||
}
|
||||
|
||||
if (enableUrlContext) {
|
||||
tools.push({
|
||||
urlContext: {}
|
||||
})
|
||||
}
|
||||
|
||||
if (isGemmaModel(model) && assistant.prompt) {
|
||||
const isFirstMessage = history.length === 0
|
||||
if (isFirstMessage && messageContents) {
|
||||
|
||||
@ -84,6 +84,7 @@ export interface ResponseChunkTransformerContext {
|
||||
isStreaming: boolean
|
||||
isEnabledToolCalling: boolean
|
||||
isEnabledWebSearch: boolean
|
||||
isEnabledUrlContext: boolean
|
||||
isEnabledReasoning: boolean
|
||||
mcpTools: MCPTool[]
|
||||
provider: Provider
|
||||
|
||||
@ -55,6 +55,7 @@ export const ResponseTransformMiddleware: CompletionsMiddleware =
|
||||
isStreaming: params.streamOutput || false,
|
||||
isEnabledToolCalling: (params.mcpTools && params.mcpTools.length > 0) || false,
|
||||
isEnabledWebSearch: params.enableWebSearch || false,
|
||||
isEnabledUrlContext: params.enableUrlContext || false,
|
||||
isEnabledReasoning: params.enableReasoning || false,
|
||||
mcpTools: params.mcpTools || [],
|
||||
provider: ctx.apiClientInstance?.provider
|
||||
|
||||
@ -49,6 +49,7 @@ export interface CompletionsParams {
|
||||
// 功能开关
|
||||
streamOutput: boolean
|
||||
enableWebSearch?: boolean
|
||||
enableUrlContext?: boolean
|
||||
enableReasoning?: boolean
|
||||
enableGenerateImage?: boolean
|
||||
|
||||
|
||||
@ -39,3 +39,18 @@ export function getWebSearchTools(model: Model): ChatCompletionTool[] {
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export function getUrlContextTools(model: Model): ChatCompletionTool[] {
|
||||
if (model.id.includes('gemini')) {
|
||||
return [
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'urlContext'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
@ -211,6 +211,7 @@
|
||||
"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",
|
||||
"input.url_context": "URL Context",
|
||||
"input.web_search.no_web_search": "Disable Web Search",
|
||||
"input.web_search.no_web_search.description": "Do not enable web search",
|
||||
"input.web_search.settings": "Web Search Settings",
|
||||
|
||||
@ -214,6 +214,7 @@
|
||||
"input.web_search.no_web_search": "ウェブ検索を無効にする",
|
||||
"input.web_search.no_web_search.description": "ウェブ検索を無効にする",
|
||||
"input.web_search.settings": "ウェブ検索設定",
|
||||
"input.url_context": "URLコンテキスト",
|
||||
"message.new.branch": "新しいブランチ",
|
||||
"message.new.branch.created": "新しいブランチが作成されました",
|
||||
"message.new.context": "新しいコンテキスト",
|
||||
|
||||
@ -214,6 +214,7 @@
|
||||
"input.web_search.no_web_search": "Отключить веб-поиск",
|
||||
"input.web_search.no_web_search.description": "Отключить веб-поиск",
|
||||
"input.web_search.settings": "Настройки веб-поиска",
|
||||
"input.url_context": "Контекст страницы",
|
||||
"message.new.branch": "Новая ветка",
|
||||
"message.new.branch.created": "Новая ветка создана",
|
||||
"message.new.context": "Новый контекст",
|
||||
|
||||
@ -214,6 +214,7 @@
|
||||
"input.web_search.no_web_search": "不使用网络",
|
||||
"input.web_search.no_web_search.description": "不启用网络搜索功能",
|
||||
"input.web_search.settings": "网络搜索设置",
|
||||
"input.url_context": "网页上下文",
|
||||
"message.new.branch": "分支",
|
||||
"message.new.branch.created": "新分支已创建",
|
||||
"message.new.context": "清除上下文",
|
||||
|
||||
@ -214,6 +214,7 @@
|
||||
"input.web_search.no_web_search": "關閉網路搜尋",
|
||||
"input.web_search.no_web_search.description": "關閉網路搜尋",
|
||||
"input.web_search.settings": "網路搜尋設定",
|
||||
"input.url_context": "網頁上下文",
|
||||
"message.new.branch": "分支",
|
||||
"message.new.branch.created": "新分支已建立",
|
||||
"message.new.context": "新上下文",
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
FileSearch,
|
||||
Globe,
|
||||
Languages,
|
||||
Link,
|
||||
LucideSquareTerminal,
|
||||
Maximize,
|
||||
MessageSquareDiff,
|
||||
@ -36,6 +37,7 @@ import MentionModelsButton, { MentionModelsButtonRef } from './MentionModelsButt
|
||||
import NewContextButton from './NewContextButton'
|
||||
import QuickPhrasesButton, { QuickPhrasesButtonRef } from './QuickPhrasesButton'
|
||||
import ThinkingButton, { ThinkingButtonRef } from './ThinkingButton'
|
||||
import UrlContextButton, { UrlContextButtonRef } from './UrlContextbutton'
|
||||
import WebSearchButton, { WebSearchButtonRef } from './WebSearchButton'
|
||||
|
||||
export interface InputbarToolsRef {
|
||||
@ -128,6 +130,7 @@ const InputbarTools = ({
|
||||
const attachmentButtonRef = useRef<AttachmentButtonRef>(null)
|
||||
const webSearchButtonRef = useRef<WebSearchButtonRef | null>(null)
|
||||
const thinkingButtonRef = useRef<ThinkingButtonRef | null>(null)
|
||||
const urlContextButtonRef = useRef<UrlContextButtonRef | null>(null)
|
||||
|
||||
const toolOrder = useAppSelector((state) => state.inputTools.toolOrder)
|
||||
const isCollapse = useAppSelector((state) => state.inputTools.isCollapsed)
|
||||
@ -230,6 +233,15 @@ const InputbarTools = ({
|
||||
webSearchButtonRef.current?.openQuickPanel()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('chat.input.url_context'),
|
||||
description: '',
|
||||
icon: <Link />,
|
||||
isMenu: true,
|
||||
action: () => {
|
||||
urlContextButtonRef.current?.openQuickPanel()
|
||||
}
|
||||
},
|
||||
{
|
||||
label: couldAddImageFile ? t('chat.input.upload') : t('chat.input.upload.document'),
|
||||
description: '',
|
||||
@ -328,6 +340,12 @@ const InputbarTools = ({
|
||||
label: t('chat.input.web_search'),
|
||||
component: <WebSearchButton ref={webSearchButtonRef} assistant={assistant} ToolbarButton={ToolbarButton} />
|
||||
},
|
||||
{
|
||||
key: 'url_context',
|
||||
label: t('chat.input.url_context'),
|
||||
component: <UrlContextButton ref={urlContextButtonRef} assistant={assistant} ToolbarButton={ToolbarButton} />,
|
||||
condition: model.id.toLowerCase().includes('gemini')
|
||||
},
|
||||
{
|
||||
key: 'knowledge_base',
|
||||
label: t('chat.input.knowledge_base'),
|
||||
|
||||
44
src/renderer/src/pages/home/Inputbar/UrlContextbutton.tsx
Normal file
44
src/renderer/src/pages/home/Inputbar/UrlContextbutton.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { Assistant } from '@renderer/types'
|
||||
import { Tooltip } from 'antd'
|
||||
import { Link } from 'lucide-react'
|
||||
import { FC, memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export interface UrlContextButtonRef {
|
||||
openQuickPanel: () => void
|
||||
}
|
||||
|
||||
interface Props {
|
||||
ref?: React.RefObject<UrlContextButtonRef | null>
|
||||
assistant: Assistant
|
||||
ToolbarButton: any
|
||||
}
|
||||
|
||||
const UrlContextButton: FC<Props> = ({ assistant, ToolbarButton }) => {
|
||||
const { t } = useTranslation()
|
||||
const { updateAssistant } = useAssistant(assistant.id)
|
||||
|
||||
const urlContentNewState = !assistant.enableUrlContext
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
setTimeout(() => {
|
||||
updateAssistant({ ...assistant, enableUrlContext: urlContentNewState })
|
||||
}, 100)
|
||||
}, [assistant, urlContentNewState, updateAssistant])
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" title={t('chat.input.url_context')} arrow>
|
||||
<ToolbarButton type="text" onClick={handleToggle}>
|
||||
<Link
|
||||
size={18}
|
||||
style={{
|
||||
color: assistant.enableUrlContext ? 'var(--color-link)' : 'var(--color-icon)'
|
||||
}}
|
||||
/>
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(UrlContextButton)
|
||||
@ -354,6 +354,8 @@ export async function fetchChatCompletion({
|
||||
model.id.includes('sonar') ||
|
||||
false
|
||||
|
||||
const enableUrlContext = assistant.enableUrlContext || false
|
||||
|
||||
const enableGenerateImage =
|
||||
isGenerateImageModel(model) && (isSupportedDisableGenerationModel(model) ? assistant.enableGenerateImage : true)
|
||||
|
||||
@ -370,6 +372,7 @@ export async function fetchChatCompletion({
|
||||
streamOutput: assistant.settings?.streamOutput || false,
|
||||
enableReasoning,
|
||||
enableWebSearch,
|
||||
enableUrlContext,
|
||||
enableGenerateImage
|
||||
},
|
||||
{
|
||||
|
||||
@ -54,7 +54,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 120,
|
||||
version: 121,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -11,6 +11,7 @@ export const DEFAULT_TOOL_ORDER: ToolOrder = {
|
||||
'attachment',
|
||||
'thinking',
|
||||
'web_search',
|
||||
'url_context',
|
||||
'knowledge_base',
|
||||
'mcp_tools',
|
||||
'generate_image',
|
||||
|
||||
@ -1763,6 +1763,24 @@ const migrateConfig = {
|
||||
} catch (error) {
|
||||
return state
|
||||
}
|
||||
},
|
||||
'121': (state: RootState) => {
|
||||
try {
|
||||
const { toolOrder } = state.inputTools
|
||||
const urlContextKey = 'url_context'
|
||||
const webSearchIndex = toolOrder.visible.indexOf('web_search')
|
||||
const knowledgeBaseIndex = toolOrder.visible.indexOf('knowledge_base')
|
||||
if (webSearchIndex !== -1) {
|
||||
toolOrder.visible.splice(webSearchIndex, 0, urlContextKey)
|
||||
} else if (knowledgeBaseIndex !== -1) {
|
||||
toolOrder.visible.splice(knowledgeBaseIndex, 0, urlContextKey)
|
||||
} else {
|
||||
toolOrder.visible.push(urlContextKey)
|
||||
}
|
||||
return state
|
||||
} catch (error) {
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,6 +23,8 @@ export type Assistant = {
|
||||
/** enableWebSearch 代表使用模型内置网络搜索功能 */
|
||||
enableWebSearch?: boolean
|
||||
webSearchProviderId?: WebSearchProvider['id']
|
||||
// enableUrlContext 是 Gemini 的特有功能
|
||||
enableUrlContext?: boolean
|
||||
enableGenerateImage?: boolean
|
||||
mcpServers?: MCPServer[]
|
||||
knowledgeRecognition?: 'off' | 'on'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user