feat: Add tool calling support for models
- Introduce ToolsCallingIcon component for tool calling models - Add isToolCallingModel function in models config - Update ModelTags to support showing tool calling icon - Add tooltips to model icons for better UX - Update Chinese localization with tool calling translation - Modify Inputbar and SelectModelButton to accommodate new icon
This commit is contained in:
parent
3f82a692a2
commit
49d29d78da
@ -1,10 +1,16 @@
|
||||
import { Tooltip } from 'antd'
|
||||
import React, { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const ReasoningIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Icon className="iconfont icon-thinking" {...(props as any)} />
|
||||
<Tooltip title={t('models.reasoning')} placement="top">
|
||||
<Icon className="iconfont icon-thinking" {...(props as any)} />
|
||||
</Tooltip>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
31
src/renderer/src/components/Icons/ToolsCallingIcon.tsx
Normal file
31
src/renderer/src/components/Icons/ToolsCallingIcon.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { ToolOutlined } from '@ant-design/icons'
|
||||
import { Tooltip } from 'antd'
|
||||
import React, { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const ToolsCallingIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Tooltip title={t('models.tool_calling')} placement="top">
|
||||
<Icon {...(props as any)} />
|
||||
</Tooltip>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Icon = styled(ToolOutlined)`
|
||||
color: #d97757;
|
||||
font-size: 15px;
|
||||
margin-right: 6px;
|
||||
`
|
||||
|
||||
export default ToolsCallingIcon
|
||||
@ -1,11 +1,17 @@
|
||||
import { EyeOutlined } from '@ant-design/icons'
|
||||
import { Tooltip } from 'antd'
|
||||
import React, { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const VisionIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Icon {...(props as any)} />
|
||||
<Tooltip title={t('models.vision')} placement="top">
|
||||
<Icon {...(props as any)} />
|
||||
</Tooltip>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
import { GlobalOutlined } from '@ant-design/icons'
|
||||
import { Tooltip } from 'antd'
|
||||
import React, { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const WebSearchIcon: FC<React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Icon {...(props as any)} />
|
||||
<Tooltip title={t('models.websearch')} placement="top">
|
||||
<Icon {...(props as any)} />
|
||||
</Tooltip>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,4 +1,10 @@
|
||||
import { isEmbeddingModel, isReasoningModel, isVisionModel, isWebSearchModel } from '@renderer/config/models'
|
||||
import {
|
||||
isEmbeddingModel,
|
||||
isReasoningModel,
|
||||
isToolCallingModel,
|
||||
isVisionModel,
|
||||
isWebSearchModel
|
||||
} from '@renderer/config/models'
|
||||
import { Model } from '@renderer/types'
|
||||
import { isFreeModel } from '@renderer/utils'
|
||||
import { Tag } from 'antd'
|
||||
@ -7,6 +13,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import ReasoningIcon from './Icons/ReasoningIcon'
|
||||
import ToolsCallingIcon from './Icons/ToolsCallingIcon'
|
||||
import VisionIcon from './Icons/VisionIcon'
|
||||
import WebSearchIcon from './Icons/WebSearchIcon'
|
||||
|
||||
@ -14,15 +21,17 @@ interface ModelTagsProps {
|
||||
model: Model
|
||||
showFree?: boolean
|
||||
showReasoning?: boolean
|
||||
showToolsCalling?: boolean
|
||||
}
|
||||
|
||||
const ModelTags: FC<ModelTagsProps> = ({ model, showFree = true, showReasoning = true }) => {
|
||||
const ModelTags: FC<ModelTagsProps> = ({ model, showFree = true, showReasoning = true, showToolsCalling = true }) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Container>
|
||||
{isVisionModel(model) && <VisionIcon />}
|
||||
{isWebSearchModel(model) && <WebSearchIcon />}
|
||||
{showReasoning && isReasoningModel(model) && <ReasoningIcon />}
|
||||
{showToolsCalling && isToolCallingModel(model) && <ToolsCallingIcon />}
|
||||
{isEmbeddingModel(model) && <Tag color="orange">{t('models.embedding')}</Tag>}
|
||||
{showFree && isFreeModel(model) && <Tag color="green">{t('models.free')}</Tag>}
|
||||
</Container>
|
||||
|
||||
@ -134,6 +134,7 @@ import OpenAI from 'openai'
|
||||
|
||||
import { getWebSearchTools } from './tools'
|
||||
|
||||
// Vision models
|
||||
const visionAllowedModels = [
|
||||
'llava',
|
||||
'moondream',
|
||||
@ -159,19 +160,33 @@ const visionAllowedModels = [
|
||||
]
|
||||
|
||||
const visionExcludedModels = ['gpt-4-\\d+-preview', 'gpt-4-turbo-preview', 'gpt-4-32k', 'gpt-4-\\d+']
|
||||
|
||||
export const VISION_REGEX = new RegExp(
|
||||
`\\b(?!(?:${visionExcludedModels.join('|')})\\b)(${visionAllowedModels.join('|')})\\b`,
|
||||
'i'
|
||||
)
|
||||
|
||||
// Text to image models
|
||||
export const TEXT_TO_IMAGE_REGEX = /flux|diffusion|stabilityai|sd-|dall|cogview|janus/i
|
||||
|
||||
// Reasoning models
|
||||
export const REASONING_REGEX =
|
||||
/^(o\d+(?:-[\w-]+)?|.*\b(?:reasoner|thinking)\b.*|.*-[rR]\d+.*|.*\bqwq(?:-[\w-]+)?\b.*)$/i
|
||||
|
||||
// Embedding models
|
||||
export const EMBEDDING_REGEX = /(?:^text-|embed|bge-|e5-|LLM2Vec|retrieval|uae-|gte-|jina-clip|jina-embeddings)/i
|
||||
export const NOT_SUPPORTED_REGEX = /(?:^tts|rerank|whisper|speech)/i
|
||||
|
||||
// Tool calling models
|
||||
export const TOOL_CALLING_MODELS = ['gpt-4o', 'gpt-4o-mini', 'gpt-4', 'gpt-4.5', 'claude']
|
||||
export const TOOL_CALLING_REGEX = new RegExp(`\\b(?:${TOOL_CALLING_MODELS.join('|')})\\b`, 'i')
|
||||
export function isToolCallingModel(model: Model): boolean {
|
||||
if (['gemini', 'deepseek', 'anthropic'].includes(model.provider)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return TOOL_CALLING_REGEX.test(model.id)
|
||||
}
|
||||
|
||||
export function getModelLogo(modelId: string) {
|
||||
const isLight = true
|
||||
|
||||
@ -990,10 +1005,6 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
],
|
||||
yi: [
|
||||
{ id: 'yi-lightning', name: 'Yi Lightning', provider: 'yi', group: 'yi-lightning', owned_by: '01.ai' },
|
||||
// yi-medium, yi-large, yi-vision 已被 yi-lightning 替代 (详见 https://archive.ph/0Idg3)
|
||||
// { id: 'yi-medium', name: 'yi-medium', provider: 'yi', group: 'yi-medium', owned_by: '01.ai' },
|
||||
// { id: 'yi-large', name: 'yi-large', provider: 'yi', group: 'yi-large', owned_by: '01.ai' },
|
||||
// { id: 'yi-vision', name: 'yi-vision', provider: 'yi', group: 'yi-vision', owned_by: '01.ai' }
|
||||
{ id: 'yi-vision-v2', name: 'Yi Vision v2', provider: 'yi', group: 'yi-vision', owned_by: '01.ai' }
|
||||
],
|
||||
zhipu: [
|
||||
|
||||
@ -479,8 +479,9 @@
|
||||
},
|
||||
"vision": "视觉",
|
||||
"websearch": "联网",
|
||||
"edit": "编辑模型",
|
||||
"no_matches": "无可用模型"
|
||||
"edit": "编辑模型",
|
||||
"no_matches": "无可用模型",
|
||||
"tool_calling": "工具调用"
|
||||
},
|
||||
"navbar": {
|
||||
"expand": "伸缩对话框",
|
||||
|
||||
@ -685,7 +685,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
||||
onMentionModel={(model) => onMentionModel(model, mentionFromKeyboard)}
|
||||
ToolbarButton={ToolbarButton}
|
||||
/>
|
||||
<MCPToolsButton enabledMCPs={enabledMCPs} onEnableMCP={toggelEnableMCP} ToolbarButton={ToolbarButton} />
|
||||
<Tooltip placement="top" title={t('chat.input.web_search')} arrow>
|
||||
<ToolbarButton type="text" onClick={onEnableWebSearch}>
|
||||
<GlobalOutlined
|
||||
@ -693,6 +692,16 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
||||
/>
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
{showKnowledgeIcon && (
|
||||
<KnowledgeBaseButton
|
||||
selectedBases={selectedKnowledgeBases}
|
||||
onSelect={handleKnowledgeBaseSelect}
|
||||
ToolbarButton={ToolbarButton}
|
||||
disabled={files.length > 0}
|
||||
/>
|
||||
)}
|
||||
<MCPToolsButton enabledMCPs={enabledMCPs} onEnableMCP={toggelEnableMCP} ToolbarButton={ToolbarButton} />
|
||||
<AttachmentButton model={model} files={files} setFiles={setFiles} ToolbarButton={ToolbarButton} />
|
||||
<Tooltip placement="top" title={t('chat.input.clear', { Command: cleanTopicShortcut })} arrow>
|
||||
<Popconfirm
|
||||
title={t('chat.input.clear.content')}
|
||||
@ -706,15 +715,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic }) => {
|
||||
</ToolbarButton>
|
||||
</Popconfirm>
|
||||
</Tooltip>
|
||||
{showKnowledgeIcon && (
|
||||
<KnowledgeBaseButton
|
||||
selectedBases={selectedKnowledgeBases}
|
||||
onSelect={handleKnowledgeBaseSelect}
|
||||
ToolbarButton={ToolbarButton}
|
||||
disabled={files.length > 0}
|
||||
/>
|
||||
)}
|
||||
<AttachmentButton model={model} files={files} setFiles={setFiles} ToolbarButton={ToolbarButton} />
|
||||
<Tooltip placement="top" title={t('chat.input.new.context', { Command: newContextShortcut })} arrow>
|
||||
<ToolbarButton type="text" onClick={onNewContext}>
|
||||
<PicCenterOutlined />
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ToolOutlined } from '@ant-design/icons'
|
||||
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||
import { MCPServer } from '@renderer/types'
|
||||
import { Dropdown, Switch, Tooltip } from 'antd'
|
||||
@ -39,7 +40,7 @@ const MCPToolsButton: FC<Props> = ({ enabledMCPs, onEnableMCP, ToolbarButton })
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [enableAll])
|
||||
}, [activeServers, enableAll, enabledMCPs, onEnableMCP])
|
||||
|
||||
const menu = (
|
||||
<div ref={menuRef} className="ant-dropdown-menu">
|
||||
@ -84,9 +85,9 @@ const MCPToolsButton: FC<Props> = ({ enabledMCPs, onEnableMCP, ToolbarButton })
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
overlayClassName="mention-models-dropdown">
|
||||
<Tooltip placement="top" title="MCP Servers" arrow>
|
||||
<Tooltip placement="top" title={t('settings.mcp.title')} arrow>
|
||||
<ToolbarButton type="text" ref={dropdownRef}>
|
||||
<i className="iconfont icon-mcp" style={{ fontSize: 18 }}></i>
|
||||
<ToolOutlined style={{ color: enabledMCPs.length > 0 ? '#d97757' : 'var(--color-icon)' }} />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
</Dropdown>
|
||||
|
||||
@ -39,7 +39,7 @@ const SelectModelButton: FC<Props> = ({ assistant }) => {
|
||||
<ModelName>
|
||||
{model ? model.name : t('button.select_model')} {providerName ? '| ' + providerName : ''}
|
||||
</ModelName>
|
||||
<ModelTags model={model} showFree={false} showReasoning={false} />
|
||||
<ModelTags model={model} showFree={false} showReasoning={false} showToolsCalling={false} />
|
||||
</ButtonContent>
|
||||
</DropdownButton>
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user