refactor(ModelEditContent): improve experience when choosing model types (#8847)

* feat(标签组件): 新增多种模型标签组件并重构标签引用路径

新增RerankerTag、EmbeddingTag、ReasoningTag、VisionTag和ToolsCallingTag组件
将CustomTag移动至Tags目录并更新所有引用路径
重构ModelTagsWithLabel组件使用新的标签组件

* feat(标签组件): 导出CustomTagProps并增强所有标签组件的props传递

- 导出CustomTagProps接口供其他组件使用
- 在所有标签组件中添加...restProps以支持更多自定义属性
- 新增WebSearchTag组件
- 统一各标签组件的props类型定义方式

* refactor(组件): 统一标签组件的showLabel属性命名

将shouldShowLabel重命名为showLabel以保持命名一致性

* feat(Tags): 为 CustomTag 组件添加 disabled 状态支持

当 disabled 为 true 时,标签颜色将变为灰色

* feat(Tags): 为 CustomTag 组件添加 onClick 事件支持并修复关闭事件冒泡

添加 onClick 属性以支持标签点击事件
修复关闭按钮点击事件冒泡问题

* fix(Tags): 修复CustomTag组件点击状态样式问题

添加$clickable属性以控制鼠标指针样式
确保当onClick存在时显示手型指针

* refactor(ProviderSettings): 替换复选框为标签组件展示模型能力

移除旧的复选框实现,改用专用标签组件展示模型能力类型
简化相关逻辑代码,提升可维护性
调整模态框宽度为自适应内容

* refactor(ProviderSettings): 重构模型编辑弹窗的布局和样式

将模型能力选择部分移动到顶部并优化布局
移除重复的类型标题并调整按钮位置
统一模态框宽度为固定值

* fix(ProviderSettings): 将 Space.Compact 替换为 Space 以修复布局问题

* feat(模型设置): 添加模型类型选择警告提示并优化交互

新增 WarnTooltip 组件用于显示模型类型选择的警告信息
修改模型类型选择交互逻辑,允许用户切换 vision 类型
更新中文翻译文本,使警告信息更准确

* refactor(components): 重构模型能力标签组件并集中管理

将分散的模型能力标签组件移动到统一的 ModelCapabilities 目录
新增 WebSearchTag 组件并优化现有标签组件结构

* feat(组件): 新增带有警告图标的Tooltip组件

* refactor(ProviderSettings): 优化模型能力标签的交互逻辑和性能

使用useMemo和useCallback优化模型类型选择和计算逻辑
重构标签组件导入路径和交互方式

* feat(Tags): 为 CustomTag 组件添加 style 属性支持

允许通过 style 属性自定义标签的样式,提供更灵活的样式控制

* refactor(ProviderSettings): 优化模型类型选择逻辑和UI交互

- 移除冗余代码并简化模型能力选择逻辑
- 添加互斥类型检查防止同时选择不兼容的模型类型
- 为重置按钮添加图标和工具提示提升用户体验
- 统一所有类型标签的禁用状态样式

* fix(ProviderSettings): 为重置按钮添加type="text"属性以修复样式问题

* refactor(组件): 移除GlobalOutlined图标并使用WebSearchTag组件替代

简化WebSearch模型的标签显示逻辑,使用统一的WebSearchTag组件替代手动创建的CustomTag,提高代码复用性和可维护性

* fix(组件): 更换deprecated属性

* feat(组件): 为CustomTag添加inactive状态并优化禁用逻辑

为CustomTag组件新增inactive属性,用于控制标签的视觉禁用状态
将disabled属性与点击事件解耦,优化禁用状态下的交互行为
更新相关调用代码以适配新的属性结构

* style(ProviderSettings): 调整 ModelEditContent 组件中 Flex 布局的换行属性

* fix(ProviderSettings): 移除Modal组件中固定的width属性

* fix(components): 为WebSearchTag组件添加size属性以保持一致性

* fix(ProviderSettings): 使用uniqueObjectArray防止模型能力重复

确保模型能力列表中的项唯一,避免重复添加相同类型的模型能力
This commit is contained in:
Phantom 2025-08-07 20:41:21 +08:00 committed by GitHub
parent ad0c2a11f3
commit 201fcf9f45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 316 additions and 212 deletions

View File

@ -78,7 +78,7 @@ const CustomCollapse: FC<CustomCollapseProps> = ({
style={collapseStyle}
defaultActiveKey={defaultActiveKey}
activeKey={activeKey}
destroyInactivePanel={destroyInactivePanel}
destroyOnHidden={destroyInactivePanel}
collapsible={collapsible}
onChange={(keys) => {
setActiveKeys(keys)

View File

@ -26,7 +26,7 @@ const ModelIdWithTags = ({
maxWidth: '500px'
}
}}
destroyTooltipOnHide
destroyOnHidden
title={
<Typography.Text style={{ color: 'white' }} copyable={{ text: model.id }}>
{model.id}

View File

@ -1,4 +1,3 @@
import { EyeOutlined, GlobalOutlined, ToolOutlined } from '@ant-design/icons'
import {
isEmbeddingModel,
isFunctionCallingModel,
@ -14,7 +13,15 @@ import { FC, memo, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import CustomTag from './CustomTag'
import CustomTag from './Tags/CustomTag'
import {
EmbeddingTag,
ReasoningTag,
RerankerTag,
ToolsCallingTag,
VisionTag,
WebSearchTag
} from './Tags/ModelCapabilities'
interface ModelTagsProps {
model: Model
@ -70,45 +77,17 @@ const ModelTagsWithLabel: FC<ModelTagsProps> = ({
return (
<Container ref={containerRef} style={style}>
{isVisionModel(model) && (
<CustomTag
size={size}
color="#00b96b"
icon={<EyeOutlined style={{ fontSize: size }} />}
tooltip={showTooltip ? t('models.type.vision') : undefined}>
{shouldShowLabel ? t('models.type.vision') : ''}
</CustomTag>
)}
{isWebSearchModel(model) && (
<CustomTag
size={size}
color="#1677ff"
icon={<GlobalOutlined style={{ fontSize: size }} />}
tooltip={showTooltip ? t('models.type.websearch') : undefined}>
{shouldShowLabel ? t('models.type.websearch') : ''}
</CustomTag>
)}
{isVisionModel(model) && <VisionTag size={size} showTooltip={showTooltip} showLabel={shouldShowLabel} />}
{isWebSearchModel(model) && <WebSearchTag size={size} showTooltip={showTooltip} showLabel={shouldShowLabel} />}
{showReasoning && isReasoningModel(model) && (
<CustomTag
size={size}
color="#6372bd"
icon={<i className="iconfont icon-thinking" />}
tooltip={showTooltip ? t('models.type.reasoning') : undefined}>
{shouldShowLabel ? t('models.type.reasoning') : ''}
</CustomTag>
<ReasoningTag size={size} showTooltip={showTooltip} showLabel={shouldShowLabel} />
)}
{showToolsCalling && isFunctionCallingModel(model) && (
<CustomTag
size={size}
color="#f18737"
icon={<ToolOutlined style={{ fontSize: size }} />}
tooltip={showTooltip ? t('models.type.function_calling') : undefined}>
{shouldShowLabel ? t('models.type.function_calling') : ''}
</CustomTag>
<ToolsCallingTag size={size} showTooltip={showTooltip} showLabel={shouldShowLabel} />
)}
{isEmbeddingModel(model) && <CustomTag size={size} color="#FFA500" icon={t('models.type.embedding')} />}
{isEmbeddingModel(model) && <EmbeddingTag size={size} />}
{showFree && isFreeModel(model) && <CustomTag size={size} color="#7cb305" icon={t('models.type.free')} />}
{isRerankModel(model) && <CustomTag size={size} color="#6495ED" icon={t('models.type.rerank')} />}
{isRerankModel(model) && <RerankerTag size={size} />}
</Container>
)
}

View File

@ -1,5 +1,5 @@
import { loggerService } from '@logger'
import CustomTag from '@renderer/components/CustomTag'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { TopView } from '@renderer/components/TopView'
import { useKnowledge, useKnowledgeBases } from '@renderer/hooks/useKnowledge'
import { Message } from '@renderer/types/newMessage'

View File

@ -1,27 +1,58 @@
import { CloseOutlined } from '@ant-design/icons'
import { Tooltip } from 'antd'
import { FC, memo, useMemo } from 'react'
import { CSSProperties, FC, memo, useMemo } from 'react'
import styled from 'styled-components'
interface CustomTagProps {
export interface CustomTagProps {
icon?: React.ReactNode
children?: React.ReactNode | string
color: string
size?: number
style?: CSSProperties
tooltip?: string
closable?: boolean
onClose?: () => void
onClick?: () => void
disabled?: boolean
inactive?: boolean
}
const CustomTag: FC<CustomTagProps> = ({ children, icon, color, size = 12, tooltip, closable = false, onClose }) => {
const CustomTag: FC<CustomTagProps> = ({
children,
icon,
color,
size = 12,
style,
tooltip,
closable = false,
onClose,
onClick,
disabled,
inactive
}) => {
const actualColor = inactive ? '#aaaaaa' : color
const tagContent = useMemo(
() => (
<Tag $color={color} $size={size} $closable={closable}>
<Tag
$color={actualColor}
$size={size}
$closable={closable}
onClick={disabled ? undefined : onClick}
style={{ cursor: disabled ? 'not-allowed' : onClick ? 'pointer' : 'auto', ...style }}>
{icon && icon} {children}
{closable && <CloseIcon $size={size} $color={color} onClick={onClose} />}
{closable && (
<CloseIcon
$size={size}
$color={actualColor}
onClick={(e) => {
e.stopPropagation()
onClose?.()
}}
/>
)}
</Tag>
),
[children, closable, color, icon, onClose, size]
[actualColor, children, closable, disabled, icon, onClick, onClose, size, style]
)
return tooltip ? (

View File

@ -0,0 +1,12 @@
import { useTranslation } from 'react-i18next'
import CustomTag, { CustomTagProps } from '../CustomTag'
type Props = {
size?: number
} & Omit<CustomTagProps, 'size' | 'tooltip' | 'icon' | 'color' | 'children'>
export const EmbeddingTag = ({ size, ...restProps }: Props) => {
const { t } = useTranslation()
return <CustomTag size={size} color="#FFA500" icon={t('models.type.embedding')} {...restProps} />
}

View File

@ -0,0 +1,23 @@
import { useTranslation } from 'react-i18next'
import CustomTag, { CustomTagProps } from '../CustomTag'
type Props = {
size?: number
showTooltip?: boolean
showLabel?: boolean
} & Omit<CustomTagProps, 'size' | 'tooltip' | 'icon' | 'color' | 'children'>
export const ReasoningTag = ({ size, showTooltip, showLabel, ...restProps }: Props) => {
const { t } = useTranslation()
return (
<CustomTag
size={size}
color="#6372bd"
icon={<i className="iconfont icon-thinking" />}
tooltip={showTooltip ? t('models.type.reasoning') : undefined}
{...restProps}>
{showLabel ? t('models.type.reasoning') : ''}
</CustomTag>
)
}

View File

@ -0,0 +1,12 @@
import { useTranslation } from 'react-i18next'
import CustomTag, { CustomTagProps } from '../CustomTag'
type Props = {
size?: number
} & Omit<CustomTagProps, 'size' | 'tooltip' | 'icon' | 'color' | 'children'>
export const RerankerTag = ({ size, ...restProps }: Props) => {
const { t } = useTranslation()
return <CustomTag size={size} color="#6495ED" icon={t('models.type.rerank')} {...restProps} />
}

View File

@ -0,0 +1,24 @@
import { ToolOutlined } from '@ant-design/icons'
import { useTranslation } from 'react-i18next'
import CustomTag, { CustomTagProps } from '../CustomTag'
type Props = {
size?: number
showTooltip?: boolean
showLabel?: boolean
} & Omit<CustomTagProps, 'size' | 'tooltip' | 'icon' | 'color' | 'children'>
export const ToolsCallingTag = ({ size, showTooltip, showLabel, ...restProps }: Props) => {
const { t } = useTranslation()
return (
<CustomTag
size={size}
color="#f18737"
icon={<ToolOutlined style={{ fontSize: size }} />}
tooltip={showTooltip ? t('models.type.function_calling') : undefined}
{...restProps}>
{showLabel ? t('models.type.function_calling') : ''}
</CustomTag>
)
}

View File

@ -0,0 +1,25 @@
import { EyeOutlined } from '@ant-design/icons'
import { useTranslation } from 'react-i18next'
import CustomTag, { CustomTagProps } from '../CustomTag'
type Props = {
size?: number
showTooltip?: boolean
showLabel?: boolean
} & Omit<CustomTagProps, 'size' | 'tooltip' | 'icon' | 'color' | 'children'>
export const VisionTag = ({ size, showTooltip, showLabel, ...restProps }: Props) => {
const { t } = useTranslation()
return (
<CustomTag
size={size}
color="#00b96b"
icon={<EyeOutlined style={{ fontSize: size }} />}
tooltip={showTooltip ? t('models.type.vision') : undefined}
{...restProps}>
{showLabel ? t('models.type.vision') : ''}
</CustomTag>
)
}

View File

@ -0,0 +1,25 @@
import { GlobalOutlined } from '@ant-design/icons'
import { useTranslation } from 'react-i18next'
import CustomTag, { CustomTagProps } from '../CustomTag'
type Props = {
size?: number
showTooltip?: boolean
showLabel?: boolean
} & Omit<CustomTagProps, 'size' | 'tooltip' | 'icon' | 'color' | 'children'>
export const WebSearchTag = ({ size, showTooltip, showLabel, ...restProps }: Props) => {
const { t } = useTranslation()
return (
<CustomTag
size={size}
color="#1677ff"
icon={<GlobalOutlined style={{ fontSize: size }} />}
tooltip={showTooltip ? t('models.type.websearch') : undefined}
{...restProps}>
{showLabel ? t('models.type.websearch') : ''}
</CustomTag>
)
}

View File

@ -0,0 +1,8 @@
import { EmbeddingTag } from './EmbeddingTag'
import { ReasoningTag } from './ReasoningTag'
import { RerankerTag } from './RerankerTag'
import { ToolsCallingTag } from './ToolsCallingTag'
import { VisionTag } from './VisionTag'
import { WebSearchTag } from './WebSearchTag'
export { EmbeddingTag, ReasoningTag, RerankerTag, ToolsCallingTag, VisionTag, WebSearchTag }

View File

@ -0,0 +1,25 @@
import { Tooltip, TooltipProps } from 'antd'
import { AlertTriangle } from 'lucide-react'
type InheritedTooltipProps = Omit<TooltipProps, 'children'>
interface WarnTooltipProps extends InheritedTooltipProps {
iconColor?: string
iconSize?: string | number
iconStyle?: React.CSSProperties
}
const WarnTooltip = ({
iconColor = 'var(--color-status-warning)',
iconSize = 14,
iconStyle,
...rest
}: WarnTooltipProps) => {
return (
<Tooltip {...rest}>
<AlertTriangle size={iconSize} color={iconColor} style={{ ...iconStyle }} role="img" aria-label="Information" />
</Tooltip>
)
}
export default WarnTooltip

View File

@ -2,7 +2,7 @@ import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it } from 'vitest'
import CustomTag from '../CustomTag'
import CustomTag from '../Tags/CustomTag'
const COLOR = '#ff0000'

View File

@ -3052,7 +3052,7 @@
"moresetting": {
"check": {
"confirm": "确认勾选",
"warn": "请慎重勾选此选项,勾选错误会导致模型无法正常使用!!"
"warn": "请慎重更改模型类型,选择错误的类型会导致模型无法正常使用"
},
"label": "更多设置",
"warn": "风险警告"

View File

@ -1,9 +1,9 @@
import { ImportOutlined, PlusOutlined } from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import CustomTag from '@renderer/components/CustomTag'
import { HStack } from '@renderer/components/Layout'
import ListItem from '@renderer/components/ListItem'
import Scrollbar from '@renderer/components/Scrollbar'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { useAgents } from '@renderer/hooks/useAgents'
import { useNavbarPosition } from '@renderer/hooks/useSettings'
import { createAssistantFromAgent } from '@renderer/services/AssistantService'

View File

@ -1,5 +1,5 @@
import CustomTag from '@renderer/components/CustomTag'
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { useAgents } from '@renderer/hooks/useAgents'
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
import { createAssistantFromAgent } from '@renderer/services/AssistantService'

View File

@ -12,7 +12,7 @@ import {
GlobalOutlined,
LinkOutlined
} from '@ant-design/icons'
import CustomTag from '@renderer/components/CustomTag'
import CustomTag from '@renderer/components/Tags/CustomTag'
import FileManager from '@renderer/services/FileManager'
import { FileMetadata } from '@renderer/types'
import { formatFileSize } from '@renderer/utils'

View File

@ -1,5 +1,5 @@
import { FileSearchOutlined } from '@ant-design/icons'
import CustomTag from '@renderer/components/CustomTag'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { KnowledgeBase } from '@renderer/types'
import { FC } from 'react'
import styled from 'styled-components'

View File

@ -1,4 +1,4 @@
import CustomTag from '@renderer/components/CustomTag'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { useProviders } from '@renderer/hooks/useProvider'
import { getModelUniqId } from '@renderer/services/ModelService'
import { Model } from '@renderer/types'

View File

@ -1,5 +1,5 @@
import { loggerService } from '@logger'
import CustomTag from '@renderer/components/CustomTag'
import CustomTag from '@renderer/components/Tags/CustomTag'
import TranslateButton from '@renderer/components/TranslateButton'
import { isGenerateImageModel, isVisionModel } from '@renderer/config/models'
import { useAssistant } from '@renderer/hooks/useAssistant'

View File

@ -1,7 +1,7 @@
import { RedoOutlined } from '@ant-design/icons'
import { loggerService } from '@logger'
import CustomTag from '@renderer/components/CustomTag'
import { HStack } from '@renderer/components/Layout'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { useKnowledge } from '@renderer/hooks/useKnowledge'
import { NavbarIcon } from '@renderer/pages/home/ChatNavbar'
import { getProviderName } from '@renderer/services/ProviderService'

View File

@ -1,4 +1,13 @@
import CopyIcon from '@renderer/components/Icons/CopyIcon'
import {
EmbeddingTag,
ReasoningTag,
RerankerTag,
ToolsCallingTag,
VisionTag,
WebSearchTag
} from '@renderer/components/Tags/ModelCapabilities'
import WarnTooltip from '@renderer/components/WarnTooltip'
import { endpointTypeOptions } from '@renderer/config/endpointTypes'
import {
isEmbeddingModel,
@ -13,7 +22,6 @@ import { Model, ModelCapability, ModelType, Provider } from '@renderer/types'
import { getDefaultGroupName, getDifference, getUnion, uniqueObjectArray } from '@renderer/utils'
import {
Button,
Checkbox,
Divider,
Flex,
Form,
@ -23,11 +31,12 @@ import {
Modal,
ModalProps,
Select,
Switch
Switch,
Tooltip
} from 'antd'
import { cloneDeep } from 'lodash'
import { ChevronDown, ChevronUp, SaveIcon } from 'lucide-react'
import { FC, useEffect, useRef, useState } from 'react'
import { ChevronDown, ChevronUp, RotateCcw, SaveIcon } from 'lucide-react'
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -107,22 +116,32 @@ const ModelEditContent: FC<ModelEditContentProps & ModalProps> = ({ provider, mo
{ label: t('models.price.custom'), value: 'custom' }
]
const defaultTypes = [
...(isVisionModel(model) ? ['vision'] : []),
...(isReasoningModel(model) ? ['reasoning'] : []),
...(isFunctionCallingModel(model) ? ['function_calling'] : []),
...(isWebSearchModel(model) ? ['web_search'] : []),
...(isEmbeddingModel(model) ? ['embedding'] : []),
...(isRerankModel(model) ? ['rerank'] : [])
]
const defaultTypes: ModelType[] = useMemo(
() => [
...(isVisionModel(model) ? (['vision'] as const) : []),
...(isReasoningModel(model) ? (['reasoning'] as const) : []),
...(isFunctionCallingModel(model) ? (['function_calling'] as const) : []),
...(isWebSearchModel(model) ? (['web_search'] as const) : []),
...(isEmbeddingModel(model) ? (['embedding'] as const) : []),
...(isRerankModel(model) ? (['rerank'] as const) : [])
],
[model]
)
const selectedTypes: string[] = getUnion(
modelCapabilities?.filter((t) => t.isUserSelected).map((t) => t.type) || [],
getDifference(defaultTypes, modelCapabilities?.filter((t) => t.isUserSelected === false).map((t) => t.type) || [])
const selectedTypes: ModelType[] = useMemo(
() =>
getUnion(
modelCapabilities?.filter((t) => t.isUserSelected).map((t) => t.type) || [],
getDifference(
defaultTypes,
modelCapabilities?.filter((t) => t.isUserSelected === false).map((t) => t.type) || []
)
),
[defaultTypes, modelCapabilities]
)
// 被rerank/embedding改变的类型
const changedTypesRef = useRef<string[]>([])
// const changedTypesRef = useRef<string[]>([])
useEffect(() => {
if (showMoreSettings) {
@ -151,157 +170,76 @@ const ModelEditContent: FC<ModelEditContentProps & ModalProps> = ({ provider, mo
}, [modelCapabilities])
const ModelCapability = () => {
const isDisabled = selectedTypes.includes('rerank') || selectedTypes.includes('embedding')
const isRerankDisabled = selectedTypes.includes('embedding')
const isEmbeddingDisabled = selectedTypes.includes('rerank')
const showTypeConfirmModal = (newCapability: ModelCapability) => {
const onUpdateType = selectedTypes?.find((t) => t === newCapability.type)
window.modal.confirm({
title: t('settings.moresetting.warn'),
content: t('settings.moresetting.check.warn'),
okText: t('settings.moresetting.check.confirm'),
cancelText: t('common.cancel'),
okButtonProps: { danger: true },
cancelButtonProps: { type: 'primary' },
onOk: () => {
if (onUpdateType) {
const updatedModelCapabilities = modelCapabilities?.map((t) => {
if (t.type === newCapability.type) {
return { ...t, isUserSelected: true }
}
if (
((onUpdateType !== t.type && onUpdateType === 'rerank') ||
(onUpdateType === 'embedding' && onUpdateType !== t.type)) &&
t.isUserSelected !== false
) {
changedTypesRef.current.push(t.type)
return { ...t, isUserSelected: false }
}
return t
})
setModelCapabilities(uniqueObjectArray(updatedModelCapabilities as ModelCapability[]))
} else {
const updatedModelCapabilities = modelCapabilities?.map((t) => {
if (
((newCapability.type !== t.type && newCapability.type === 'rerank') ||
(newCapability.type === 'embedding' && newCapability.type !== t.type)) &&
t.isUserSelected !== false
) {
changedTypesRef.current.push(t.type)
return { ...t, isUserSelected: false }
}
if (newCapability.type === t.type) {
return { ...t, isUserSelected: true }
}
return t
})
updatedModelCapabilities.push(newCapability as any)
setModelCapabilities(uniqueObjectArray(updatedModelCapabilities as ModelCapability[]))
}
},
onCancel: () => {},
centered: true
})
}
const handleTypeChange = (types: string[]) => {
setHasUserModified(true) // 标记用户已进行修改
const diff = types.length > selectedTypes.length
if (diff) {
const newCapability = getDifference(types, selectedTypes) // checkbox的特性确保了newCapability只有一个元素
showTypeConfirmModal({
type: newCapability[0] as ModelType,
isUserSelected: true
})
} else {
const disabledTypes = getDifference(selectedTypes, types)
const onUpdateType = modelCapabilities?.find((t) => t.type === disabledTypes[0])
if (onUpdateType) {
const updatedTypes = modelCapabilities?.map((t) => {
if (t.type === disabledTypes[0]) {
return { ...t, isUserSelected: false }
}
if (
((onUpdateType !== t && onUpdateType.type === 'rerank') ||
(onUpdateType.type === 'embedding' && onUpdateType !== t)) &&
t.isUserSelected === false
) {
if (changedTypesRef.current.includes(t.type)) {
return { ...t, isUserSelected: true }
}
}
return t
})
setModelCapabilities(uniqueObjectArray(updatedTypes as ModelCapability[]))
} else {
const updatedModelCapabilities = modelCapabilities?.map((t) => {
if (
(disabledTypes[0] === 'rerank' && t.type !== 'rerank') ||
(disabledTypes[0] === 'embedding' && t.type !== 'embedding' && t.isUserSelected === false)
) {
return { ...t, isUserSelected: true }
}
return t
})
updatedModelCapabilities.push({ type: disabledTypes[0] as ModelType, isUserSelected: false })
setModelCapabilities(uniqueObjectArray(updatedModelCapabilities as ModelCapability[]))
}
changedTypesRef.current.length = 0
}
}
const isOtherDisabled = selectedTypes.includes('rerank') || selectedTypes.includes('embedding')
const handleResetTypes = () => {
setModelCapabilities(originalModelCapabilities)
setHasUserModified(false) // 重置后清除修改标志
}
const updateType = useCallback((type: ModelType) => {
setHasUserModified(true)
setModelCapabilities((prev) =>
uniqueObjectArray([
...prev.filter((t) => t.type !== type),
{ type, isUserSelected: !selectedTypes.includes(type) }
])
)
}, [])
return (
<div>
<Flex justify="space-between" align="center" style={{ marginBottom: 8 }}>
<Checkbox.Group
value={selectedTypes}
onChange={handleTypeChange}
options={[
{
label: t('models.type.vision'),
value: 'vision',
disabled: isDisabled
},
{
label: t('models.type.websearch'),
value: 'web_search',
disabled: isDisabled
},
{
label: t('models.type.rerank'),
value: 'rerank',
disabled: isRerankDisabled
},
{
label: t('models.type.embedding'),
value: 'embedding',
disabled: isEmbeddingDisabled
},
{
label: t('models.type.reasoning'),
value: 'reasoning',
disabled: isDisabled
},
{
label: t('models.type.function_calling'),
value: 'function_calling',
disabled: isDisabled
}
]}
/>
<>
<TypeTitle>
<Flex align="center" gap={4} style={{ height: 24 }}>
{t('models.type.select')}
<WarnTooltip title={t('settings.moresetting.check.warn')} />
</Flex>
{hasUserModified && (
<Button size="small" onClick={handleResetTypes}>
{t('common.reset')}
</Button>
<Tooltip title={t('common.reset')}>
<Button size="small" icon={<RotateCcw size={14} />} onClick={handleResetTypes} type="text" />
</Tooltip>
)}
</TypeTitle>
<Flex justify="flex-start" align="center" gap={4} wrap={'wrap'} style={{ marginBottom: 8 }}>
<VisionTag
showLabel
inactive={isOtherDisabled || !selectedTypes.includes('vision')}
disabled={isOtherDisabled}
onClick={() => updateType('vision')}
/>
<WebSearchTag
showLabel
inactive={isOtherDisabled || !selectedTypes.includes('web_search')}
disabled={isOtherDisabled}
onClick={() => updateType('web_search')}
/>
<ReasoningTag
showLabel
inactive={isOtherDisabled || !selectedTypes.includes('reasoning')}
disabled={isOtherDisabled}
onClick={() => updateType('reasoning')}
/>
<ToolsCallingTag
showLabel
inactive={isOtherDisabled || !selectedTypes.includes('function_calling')}
disabled={isOtherDisabled}
onClick={() => updateType('function_calling')}
/>
<RerankerTag
disabled={isRerankDisabled}
inactive={isRerankDisabled || !selectedTypes.includes('rerank')}
onClick={() => updateType('rerank')}
/>
<EmbeddingTag
inactive={isEmbeddingDisabled || !selectedTypes.includes('embedding')}
disabled={isEmbeddingDisabled}
onClick={() => updateType('embedding')}
/>
</Flex>
</div>
</>
)
}
@ -405,7 +343,6 @@ const ModelEditContent: FC<ModelEditContentProps & ModalProps> = ({ provider, mo
{showMoreSettings && (
<div style={{ marginBottom: 8 }}>
<Divider style={{ margin: '16px 0 16px 0' }} />
<TypeTitle>{t('models.type.select')}</TypeTitle>
<ModelCapability />
<Divider style={{ margin: '16px 0 12px 0' }} />
<Form.Item
@ -516,6 +453,9 @@ const ModelEditContent: FC<ModelEditContentProps & ModalProps> = ({ provider, mo
}
const TypeTitle = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
margin: 12px 0;
font-size: 14px;
font-weight: 600;

View File

@ -1,6 +1,6 @@
import CustomTag from '@renderer/components/CustomTag'
import ExpandableText from '@renderer/components/ExpandableText'
import ModelIdWithTags from '@renderer/components/ModelIdWithTags'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { DynamicVirtualList } from '@renderer/components/VirtualList'
import { getModelLogo } from '@renderer/config/models'
import FileItem from '@renderer/pages/files/FileItem'

View File

@ -1,7 +1,7 @@
import CollapsibleSearchBar from '@renderer/components/CollapsibleSearchBar'
import CustomTag from '@renderer/components/CustomTag'
import { LoadingIcon, StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons'
import { HStack } from '@renderer/components/Layout'
import CustomTag from '@renderer/components/Tags/CustomTag'
import { PROVIDER_URLS } from '@renderer/config/providers'
import { useProvider } from '@renderer/hooks/useProvider'
import { getProviderLabel } from '@renderer/i18n/label'