feat: add web search for google gemini modal gemini-2.0-flash-exp

This commit is contained in:
kangfenmao 2024-12-12 14:23:59 +08:00
parent f312c5fc40
commit 2fae6e4a3e
14 changed files with 74 additions and 35 deletions

View File

@ -62,12 +62,7 @@ electronDownload:
afterSign: scripts/notarize.js afterSign: scripts/notarize.js
releaseInfo: releaseInfo:
releaseNotes: | releaseNotes: |
增加快捷键切换助手和话题显示 修复对话消息无法删除问题
历史消息懒加载 by @1355873789 支持 Gemini 模型联网搜索
更快的应用更新下载速度 by @1355873789 发送消息增加 Command + Enter 快捷键 by @duanyongcheng
更加清晰的模型分组 Windows 版本样式优化
修复部分代码块无法正常显示问题
增加应用更新内容显示
消息发送增加 Ctrl + Enter 快捷键
清除上下文消息点击可以撤销
增加 Top-P 设置选项

View File

@ -57,7 +57,7 @@
"@electron-toolkit/eslint-config-prettier": "^2.0.0", "@electron-toolkit/eslint-config-prettier": "^2.0.0",
"@electron-toolkit/eslint-config-ts": "^1.0.1", "@electron-toolkit/eslint-config-ts": "^1.0.1",
"@electron-toolkit/tsconfig": "^1.0.1", "@electron-toolkit/tsconfig": "^1.0.1",
"@google/generative-ai": "^0.16.0", "@google/generative-ai": "^0.21.0",
"@hello-pangea/dnd": "^16.6.0", "@hello-pangea/dnd": "^16.6.0",
"@kangfenmao/keyv-storage": "^0.1.0", "@kangfenmao/keyv-storage": "^0.1.0",
"@reduxjs/toolkit": "^2.2.5", "@reduxjs/toolkit": "^2.2.5",

View File

@ -0,0 +1,15 @@
import { GlobalOutlined } from '@ant-design/icons'
import React, { FC } from 'react'
import styled from 'styled-components'
const WebSearchIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>> = (props) => {
return <Icon {...(props as any)} />
}
const Icon = styled(GlobalOutlined)`
color: var(--color-link);
font-size: 12px;
margin-left: 4px;
`
export default WebSearchIcon

View File

@ -1,7 +1,7 @@
import { PushpinOutlined, SearchOutlined } from '@ant-design/icons' import { PushpinOutlined, SearchOutlined } from '@ant-design/icons'
import VisionIcon from '@renderer/components/Icons/VisionIcon' import VisionIcon from '@renderer/components/Icons/VisionIcon'
import { TopView } from '@renderer/components/TopView' import { TopView } from '@renderer/components/TopView'
import { getModelLogo, isVisionModel } from '@renderer/config/models' import { getModelLogo, isVisionModel, isWebSearchModel } from '@renderer/config/models'
import db from '@renderer/databases' import db from '@renderer/databases'
import { useProviders } from '@renderer/hooks/useProvider' import { useProviders } from '@renderer/hooks/useProvider'
import { getModelUniqId } from '@renderer/services/ModelService' import { getModelUniqId } from '@renderer/services/ModelService'
@ -12,6 +12,7 @@ import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import WebSearchIcon from '../Icons/WebSearchIcon'
import { HStack } from '../Layout' import { HStack } from '../Layout'
import Scrollbar from '../Scrollbar' import Scrollbar from '../Scrollbar'
@ -73,7 +74,7 @@ const PopupContainer: React.FC<PopupContainerProps> = ({ model, resolve }) => {
label: ( label: (
<ModelItem> <ModelItem>
<span> <span>
{m?.name} {isVisionModel(m) && <VisionIcon />} {m?.name} {isVisionModel(m) && <VisionIcon />} {isWebSearchModel(m) && <WebSearchIcon />}
</span> </span>
<PinIcon <PinIcon
onClick={(e) => { onClick={(e) => {

View File

@ -1056,3 +1056,7 @@ export function isVisionModel(model: Model): boolean {
export function isSupportedModel(model: OpenAI.Models.Model): boolean { export function isSupportedModel(model: OpenAI.Models.Model): boolean {
return !NOT_SUPPORTED_REGEX.test(model.id) return !NOT_SUPPORTED_REGEX.test(model.id)
} }
export function isWebSearchModel(model: Model): boolean {
return model?.provider === 'gemini' && model?.id === 'gemini-2.0-flash-exp'
}

View File

@ -80,6 +80,7 @@
"input.topics": " Topics ", "input.topics": " Topics ",
"input.translate": "Translate to English", "input.translate": "Translate to English",
"input.upload": "Upload image or document file", "input.upload": "Upload image or document file",
"input.web_search": "Enable web search",
"message.new.branch": "New Branch", "message.new.branch": "New Branch",
"message.new.branch.created": "New Branch Created", "message.new.branch.created": "New Branch Created",
"message.new.context": "New Context", "message.new.context": "New Context",

View File

@ -80,6 +80,7 @@
"input.topics": " Топики ", "input.topics": " Топики ",
"input.translate": "Перевести на английский", "input.translate": "Перевести на английский",
"input.upload": "Загрузить изображение или документ", "input.upload": "Загрузить изображение или документ",
"input.web_search": "Включить веб-поиск",
"message.new.branch": "Новая ветка", "message.new.branch": "Новая ветка",
"message.new.branch.created": "Новая ветка создана", "message.new.branch.created": "Новая ветка создана",
"message.new.context": "Новый контекст", "message.new.context": "Новый контекст",

View File

@ -80,6 +80,7 @@
"input.topics": " 话题 ", "input.topics": " 话题 ",
"input.translate": "翻译成英文", "input.translate": "翻译成英文",
"input.upload": "上传图片或文档", "input.upload": "上传图片或文档",
"input.web_search": "开启网络搜索",
"message.new.branch": "新分支", "message.new.branch": "新分支",
"message.new.branch.created": "新分支已创建", "message.new.branch.created": "新分支已创建",
"message.new.context": "清除上下文", "message.new.context": "清除上下文",

View File

@ -80,6 +80,7 @@
"input.topics": " 話題 ", "input.topics": " 話題 ",
"input.translate": "翻譯成英文", "input.translate": "翻譯成英文",
"input.upload": "上傳圖片或文檔", "input.upload": "上傳圖片或文檔",
"input.web_search": "開啟網路搜索",
"message.new.branch": "新分支", "message.new.branch": "新分支",
"message.new.branch.created": "新分支已建立", "message.new.branch.created": "新分支已建立",
"message.new.context": "新上下文", "message.new.context": "新上下文",

View File

@ -4,13 +4,14 @@ import {
FormOutlined, FormOutlined,
FullscreenExitOutlined, FullscreenExitOutlined,
FullscreenOutlined, FullscreenOutlined,
GlobalOutlined,
PauseCircleOutlined, PauseCircleOutlined,
QuestionCircleOutlined QuestionCircleOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import { PicCenterOutlined } from '@ant-design/icons' import { PicCenterOutlined } from '@ant-design/icons'
import TranslateButton from '@renderer/components/TranslateButton' import TranslateButton from '@renderer/components/TranslateButton'
import { isMac } from '@renderer/config/constant' import { isMac } from '@renderer/config/constant'
import { isVisionModel } from '@renderer/config/models' import { isVisionModel, isWebSearchModel } from '@renderer/config/models'
import db from '@renderer/databases' import db from '@renderer/databases'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { useRuntime } from '@renderer/hooks/useRuntime' import { useRuntime } from '@renderer/hooks/useRuntime'
@ -48,10 +49,10 @@ interface Props {
let _text = '' let _text = ''
let _files: FileType[] = [] let _files: FileType[] = []
const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => { const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
const [text, setText] = useState(_text) const [text, setText] = useState(_text)
const [inputFocus, setInputFocus] = useState(false) const [inputFocus, setInputFocus] = useState(false)
const { addTopic, model, setModel } = useAssistant(assistant.id) const { assistant, addTopic, model, setModel, updateAssistant } = useAssistant(_assistant.id)
const { const {
sendMessageShortcut, sendMessageShortcut,
fontSize, fontSize,
@ -400,6 +401,17 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
<FormOutlined /> <FormOutlined />
</ToolbarButton> </ToolbarButton>
</Tooltip> </Tooltip>
{isWebSearchModel(model) && (
<Tooltip placement="top" title={t('chat.input.web_search')} arrow>
<ToolbarButton
type="text"
onClick={() => updateAssistant({ ...assistant, enableWebSearch: !assistant.enableWebSearch })}>
<GlobalOutlined
style={{ color: assistant.enableWebSearch ? 'var(--color-link)' : 'var(--color-icon)' }}
/>
</ToolbarButton>
</Tooltip>
)}
<Tooltip placement="top" title={t('chat.input.clear')} arrow> <Tooltip placement="top" title={t('chat.input.clear')} arrow>
<Popconfirm <Popconfirm
title={t('chat.input.clear.content')} title={t('chat.input.clear.content')}

View File

@ -8,7 +8,8 @@ import {
SettingOutlined SettingOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import VisionIcon from '@renderer/components/Icons/VisionIcon' import VisionIcon from '@renderer/components/Icons/VisionIcon'
import { getModelLogo, isVisionModel, VISION_REGEX } from '@renderer/config/models' import WebSearchIcon from '@renderer/components/Icons/WebSearchIcon'
import { getModelLogo, isVisionModel, isWebSearchModel, VISION_REGEX } from '@renderer/config/models'
import { PROVIDER_CONFIG } from '@renderer/config/providers' import { PROVIDER_CONFIG } from '@renderer/config/providers'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useAssistants, useDefaultModel } from '@renderer/hooks/useAssistant' import { useAssistants, useDefaultModel } from '@renderer/hooks/useAssistant'
@ -261,20 +262,24 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
title={group} title={group}
style={{ marginBottom: '10px', border: '0.5px solid var(--color-border)' }} style={{ marginBottom: '10px', border: '0.5px solid var(--color-border)' }}
size="small"> size="small">
{modelGroups[group].map((model) => ( {modelGroups[group].map((model) => {
<ModelListItem key={model.id}> console.debug(model)
<ModelListHeader> return (
<Avatar src={getModelLogo(model.id)} size={22} style={{ marginRight: '8px' }}> <ModelListItem key={model.id}>
{model.name[0].toUpperCase()} <ModelListHeader>
</Avatar> <Avatar src={getModelLogo(model.id)} size={22} style={{ marginRight: '8px' }}>
{model.name} {isVisionModel(model) && <VisionIcon />} {model.name[0].toUpperCase()}
<Popover content={modelTypeContent(model)} title={t('model.type.select')} trigger="click"> </Avatar>
<SettingIcon /> {model.name} {isVisionModel(model) && <VisionIcon />}
</Popover> {isWebSearchModel(model) && <WebSearchIcon />}
</ModelListHeader> <Popover content={modelTypeContent(model)} title={t('model.type.select')} trigger="click">
<RemoveIcon onClick={() => removeModel(model)} /> <SettingIcon />
</ModelListItem> </Popover>
))} </ModelListHeader>
<RemoveIcon onClick={() => removeModel(model)} />
</ModelListItem>
)
})}
</Card> </Card>
))} ))}
{docsWebsite && ( {docsWebsite && (

View File

@ -84,6 +84,8 @@ export default class GeminiProvider extends BaseProvider {
{ {
model: model.id, model: model.id,
systemInstruction: assistant.prompt, systemInstruction: assistant.prompt,
// @ts-ignore googleSearch is not a valid tool for Gemini
tools: assistant.enableWebSearch ? [{ googleSearch: {} }] : [],
generationConfig: { generationConfig: {
maxOutputTokens: maxTokens, maxOutputTokens: maxTokens,
temperature: assistant?.settings?.temperature, temperature: assistant?.settings?.temperature,

View File

@ -13,6 +13,7 @@ export type Assistant = {
defaultModel?: Model defaultModel?: Model
settings?: Partial<AssistantSettings> settings?: Partial<AssistantSettings>
messages?: AssistantMessage[] messages?: AssistantMessage[]
enableWebSearch?: boolean
} }
export type AssistantMessage = { export type AssistantMessage = {

View File

@ -798,10 +798,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@google/generative-ai@npm:^0.16.0": "@google/generative-ai@npm:^0.21.0":
version: 0.16.1 version: 0.21.0
resolution: "@google/generative-ai@npm:0.16.1" resolution: "@google/generative-ai@npm:0.21.0"
checksum: 10c0/bad13a040e210a48a44272b81ef78213d23c17491ff3cb0a937952518c79e96005b78d8c3324342b5b8613b6b13f56fe9bc53447c5d2c7da4a9cd26c6a8d7de4 checksum: 10c0/cff5946c5964f2380e5097d82bd563d79be27a1a5ac604aaaad3f9ba3382992e4f0a371bd255baabfba4e5bdf296d8ce1410cbd65424afa98e64b2590fe49f3b
languageName: node languageName: node
linkType: hard linkType: hard
@ -2341,7 +2341,7 @@ __metadata:
"@electron-toolkit/preload": "npm:^3.0.0" "@electron-toolkit/preload": "npm:^3.0.0"
"@electron-toolkit/tsconfig": "npm:^1.0.1" "@electron-toolkit/tsconfig": "npm:^1.0.1"
"@electron-toolkit/utils": "npm:^3.0.0" "@electron-toolkit/utils": "npm:^3.0.0"
"@google/generative-ai": "npm:^0.16.0" "@google/generative-ai": "npm:^0.21.0"
"@hello-pangea/dnd": "npm:^16.6.0" "@hello-pangea/dnd": "npm:^16.6.0"
"@kangfenmao/keyv-storage": "npm:^0.1.0" "@kangfenmao/keyv-storage": "npm:^0.1.0"
"@reduxjs/toolkit": "npm:^2.2.5" "@reduxjs/toolkit": "npm:^2.2.5"