From 201fcf9f45d4a5a3384d8743e2d06c0d03438fe0 Mon Sep 17 00:00:00 2001 From: Phantom <59059173+EurFelux@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:41:21 +0800 Subject: [PATCH] refactor(ModelEditContent): improve experience when choosing model types (#8847) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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防止模型能力重复 确保模型能力列表中的项唯一,避免重复添加相同类型的模型能力 --- .../src/components/CustomCollapse.tsx | 2 +- .../src/components/ModelIdWithTags.tsx | 2 +- .../src/components/ModelTagsWithLabel.tsx | 51 ++-- .../Popups/SaveToKnowledgePopup.tsx | 2 +- .../src/components/{ => Tags}/CustomTag.tsx | 43 ++- .../Tags/ModelCapabilities/EmbeddingTag.tsx | 12 + .../Tags/ModelCapabilities/ReasoningTag.tsx | 23 ++ .../Tags/ModelCapabilities/RerankerTag.tsx | 12 + .../ModelCapabilities/ToolsCallingTag.tsx | 24 ++ .../Tags/ModelCapabilities/VisionTag.tsx | 25 ++ .../Tags/ModelCapabilities/WebSearchTag.tsx | 25 ++ .../Tags/ModelCapabilities/index.ts | 8 + src/renderer/src/components/WarnTooltip.tsx | 25 ++ .../components/__tests__/CustomTag.test.tsx | 2 +- src/renderer/src/i18n/locales/zh-cn.json | 2 +- src/renderer/src/pages/agents/AgentsPage.tsx | 2 +- .../src/pages/agents/components/AgentCard.tsx | 2 +- .../pages/home/Inputbar/AttachmentPreview.tsx | 2 +- .../home/Inputbar/KnowledgeBaseInput.tsx | 2 +- .../home/Inputbar/MentionModelsInput.tsx | 2 +- .../src/pages/home/Messages/MessageEditor.tsx | 2 +- .../src/pages/knowledge/KnowledgeContent.tsx | 2 +- .../EditModelPopup/ModelEditContent.tsx | 252 +++++++----------- .../ModelList/ManageModelsList.tsx | 2 +- .../ProviderSettings/ModelList/ModelList.tsx | 2 +- 25 files changed, 316 insertions(+), 212 deletions(-) rename src/renderer/src/components/{ => Tags}/CustomTag.tsx (64%) create mode 100644 src/renderer/src/components/Tags/ModelCapabilities/EmbeddingTag.tsx create mode 100644 src/renderer/src/components/Tags/ModelCapabilities/ReasoningTag.tsx create mode 100644 src/renderer/src/components/Tags/ModelCapabilities/RerankerTag.tsx create mode 100644 src/renderer/src/components/Tags/ModelCapabilities/ToolsCallingTag.tsx create mode 100644 src/renderer/src/components/Tags/ModelCapabilities/VisionTag.tsx create mode 100644 src/renderer/src/components/Tags/ModelCapabilities/WebSearchTag.tsx create mode 100644 src/renderer/src/components/Tags/ModelCapabilities/index.ts create mode 100644 src/renderer/src/components/WarnTooltip.tsx diff --git a/src/renderer/src/components/CustomCollapse.tsx b/src/renderer/src/components/CustomCollapse.tsx index d41a9ffd60..8362d8a479 100644 --- a/src/renderer/src/components/CustomCollapse.tsx +++ b/src/renderer/src/components/CustomCollapse.tsx @@ -78,7 +78,7 @@ const CustomCollapse: FC = ({ style={collapseStyle} defaultActiveKey={defaultActiveKey} activeKey={activeKey} - destroyInactivePanel={destroyInactivePanel} + destroyOnHidden={destroyInactivePanel} collapsible={collapsible} onChange={(keys) => { setActiveKeys(keys) diff --git a/src/renderer/src/components/ModelIdWithTags.tsx b/src/renderer/src/components/ModelIdWithTags.tsx index 4b8ca86123..bf902ae1c4 100644 --- a/src/renderer/src/components/ModelIdWithTags.tsx +++ b/src/renderer/src/components/ModelIdWithTags.tsx @@ -26,7 +26,7 @@ const ModelIdWithTags = ({ maxWidth: '500px' } }} - destroyTooltipOnHide + destroyOnHidden title={ {model.id} diff --git a/src/renderer/src/components/ModelTagsWithLabel.tsx b/src/renderer/src/components/ModelTagsWithLabel.tsx index 86a04dd454..3da6ccfc8d 100644 --- a/src/renderer/src/components/ModelTagsWithLabel.tsx +++ b/src/renderer/src/components/ModelTagsWithLabel.tsx @@ -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 = ({ return ( - {isVisionModel(model) && ( - } - tooltip={showTooltip ? t('models.type.vision') : undefined}> - {shouldShowLabel ? t('models.type.vision') : ''} - - )} - {isWebSearchModel(model) && ( - } - tooltip={showTooltip ? t('models.type.websearch') : undefined}> - {shouldShowLabel ? t('models.type.websearch') : ''} - - )} + {isVisionModel(model) && } + {isWebSearchModel(model) && } {showReasoning && isReasoningModel(model) && ( - } - tooltip={showTooltip ? t('models.type.reasoning') : undefined}> - {shouldShowLabel ? t('models.type.reasoning') : ''} - + )} {showToolsCalling && isFunctionCallingModel(model) && ( - } - tooltip={showTooltip ? t('models.type.function_calling') : undefined}> - {shouldShowLabel ? t('models.type.function_calling') : ''} - + )} - {isEmbeddingModel(model) && } + {isEmbeddingModel(model) && } {showFree && isFreeModel(model) && } - {isRerankModel(model) && } + {isRerankModel(model) && } ) } diff --git a/src/renderer/src/components/Popups/SaveToKnowledgePopup.tsx b/src/renderer/src/components/Popups/SaveToKnowledgePopup.tsx index 3997135f72..1b92b97828 100644 --- a/src/renderer/src/components/Popups/SaveToKnowledgePopup.tsx +++ b/src/renderer/src/components/Popups/SaveToKnowledgePopup.tsx @@ -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' diff --git a/src/renderer/src/components/CustomTag.tsx b/src/renderer/src/components/Tags/CustomTag.tsx similarity index 64% rename from src/renderer/src/components/CustomTag.tsx rename to src/renderer/src/components/Tags/CustomTag.tsx index c875ba01a4..c7abd15a62 100644 --- a/src/renderer/src/components/CustomTag.tsx +++ b/src/renderer/src/components/Tags/CustomTag.tsx @@ -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 = ({ children, icon, color, size = 12, tooltip, closable = false, onClose }) => { +const CustomTag: FC = ({ + children, + icon, + color, + size = 12, + style, + tooltip, + closable = false, + onClose, + onClick, + disabled, + inactive +}) => { + const actualColor = inactive ? '#aaaaaa' : color const tagContent = useMemo( () => ( - + {icon && icon} {children} - {closable && } + {closable && ( + { + e.stopPropagation() + onClose?.() + }} + /> + )} ), - [children, closable, color, icon, onClose, size] + [actualColor, children, closable, disabled, icon, onClick, onClose, size, style] ) return tooltip ? ( diff --git a/src/renderer/src/components/Tags/ModelCapabilities/EmbeddingTag.tsx b/src/renderer/src/components/Tags/ModelCapabilities/EmbeddingTag.tsx new file mode 100644 index 0000000000..8a2e5e2a33 --- /dev/null +++ b/src/renderer/src/components/Tags/ModelCapabilities/EmbeddingTag.tsx @@ -0,0 +1,12 @@ +import { useTranslation } from 'react-i18next' + +import CustomTag, { CustomTagProps } from '../CustomTag' + +type Props = { + size?: number +} & Omit + +export const EmbeddingTag = ({ size, ...restProps }: Props) => { + const { t } = useTranslation() + return +} diff --git a/src/renderer/src/components/Tags/ModelCapabilities/ReasoningTag.tsx b/src/renderer/src/components/Tags/ModelCapabilities/ReasoningTag.tsx new file mode 100644 index 0000000000..8e6d94bea7 --- /dev/null +++ b/src/renderer/src/components/Tags/ModelCapabilities/ReasoningTag.tsx @@ -0,0 +1,23 @@ +import { useTranslation } from 'react-i18next' + +import CustomTag, { CustomTagProps } from '../CustomTag' + +type Props = { + size?: number + showTooltip?: boolean + showLabel?: boolean +} & Omit + +export const ReasoningTag = ({ size, showTooltip, showLabel, ...restProps }: Props) => { + const { t } = useTranslation() + return ( + } + tooltip={showTooltip ? t('models.type.reasoning') : undefined} + {...restProps}> + {showLabel ? t('models.type.reasoning') : ''} + + ) +} diff --git a/src/renderer/src/components/Tags/ModelCapabilities/RerankerTag.tsx b/src/renderer/src/components/Tags/ModelCapabilities/RerankerTag.tsx new file mode 100644 index 0000000000..6d6811f77f --- /dev/null +++ b/src/renderer/src/components/Tags/ModelCapabilities/RerankerTag.tsx @@ -0,0 +1,12 @@ +import { useTranslation } from 'react-i18next' + +import CustomTag, { CustomTagProps } from '../CustomTag' + +type Props = { + size?: number +} & Omit + +export const RerankerTag = ({ size, ...restProps }: Props) => { + const { t } = useTranslation() + return +} diff --git a/src/renderer/src/components/Tags/ModelCapabilities/ToolsCallingTag.tsx b/src/renderer/src/components/Tags/ModelCapabilities/ToolsCallingTag.tsx new file mode 100644 index 0000000000..1532564be1 --- /dev/null +++ b/src/renderer/src/components/Tags/ModelCapabilities/ToolsCallingTag.tsx @@ -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 + +export const ToolsCallingTag = ({ size, showTooltip, showLabel, ...restProps }: Props) => { + const { t } = useTranslation() + return ( + } + tooltip={showTooltip ? t('models.type.function_calling') : undefined} + {...restProps}> + {showLabel ? t('models.type.function_calling') : ''} + + ) +} diff --git a/src/renderer/src/components/Tags/ModelCapabilities/VisionTag.tsx b/src/renderer/src/components/Tags/ModelCapabilities/VisionTag.tsx new file mode 100644 index 0000000000..4ff219cfc5 --- /dev/null +++ b/src/renderer/src/components/Tags/ModelCapabilities/VisionTag.tsx @@ -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 + +export const VisionTag = ({ size, showTooltip, showLabel, ...restProps }: Props) => { + const { t } = useTranslation() + + return ( + } + tooltip={showTooltip ? t('models.type.vision') : undefined} + {...restProps}> + {showLabel ? t('models.type.vision') : ''} + + ) +} diff --git a/src/renderer/src/components/Tags/ModelCapabilities/WebSearchTag.tsx b/src/renderer/src/components/Tags/ModelCapabilities/WebSearchTag.tsx new file mode 100644 index 0000000000..099c9b3839 --- /dev/null +++ b/src/renderer/src/components/Tags/ModelCapabilities/WebSearchTag.tsx @@ -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 + +export const WebSearchTag = ({ size, showTooltip, showLabel, ...restProps }: Props) => { + const { t } = useTranslation() + + return ( + } + tooltip={showTooltip ? t('models.type.websearch') : undefined} + {...restProps}> + {showLabel ? t('models.type.websearch') : ''} + + ) +} diff --git a/src/renderer/src/components/Tags/ModelCapabilities/index.ts b/src/renderer/src/components/Tags/ModelCapabilities/index.ts new file mode 100644 index 0000000000..990f66c580 --- /dev/null +++ b/src/renderer/src/components/Tags/ModelCapabilities/index.ts @@ -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 } diff --git a/src/renderer/src/components/WarnTooltip.tsx b/src/renderer/src/components/WarnTooltip.tsx new file mode 100644 index 0000000000..b5292365db --- /dev/null +++ b/src/renderer/src/components/WarnTooltip.tsx @@ -0,0 +1,25 @@ +import { Tooltip, TooltipProps } from 'antd' +import { AlertTriangle } from 'lucide-react' + +type InheritedTooltipProps = Omit + +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 ( + + + + ) +} + +export default WarnTooltip diff --git a/src/renderer/src/components/__tests__/CustomTag.test.tsx b/src/renderer/src/components/__tests__/CustomTag.test.tsx index a306fa6494..12a4620b3d 100644 --- a/src/renderer/src/components/__tests__/CustomTag.test.tsx +++ b/src/renderer/src/components/__tests__/CustomTag.test.tsx @@ -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' diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 0e432c84c6..49bed6aff4 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -3052,7 +3052,7 @@ "moresetting": { "check": { "confirm": "确认勾选", - "warn": "请慎重勾选此选项,勾选错误会导致模型无法正常使用!!!" + "warn": "请慎重更改模型类型,选择错误的类型会导致模型无法正常使用!" }, "label": "更多设置", "warn": "风险警告" diff --git a/src/renderer/src/pages/agents/AgentsPage.tsx b/src/renderer/src/pages/agents/AgentsPage.tsx index 727e065662..c0c9093e7c 100644 --- a/src/renderer/src/pages/agents/AgentsPage.tsx +++ b/src/renderer/src/pages/agents/AgentsPage.tsx @@ -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' diff --git a/src/renderer/src/pages/agents/components/AgentCard.tsx b/src/renderer/src/pages/agents/components/AgentCard.tsx index 223987a06c..020bd35c73 100644 --- a/src/renderer/src/pages/agents/components/AgentCard.tsx +++ b/src/renderer/src/pages/agents/components/AgentCard.tsx @@ -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' diff --git a/src/renderer/src/pages/home/Inputbar/AttachmentPreview.tsx b/src/renderer/src/pages/home/Inputbar/AttachmentPreview.tsx index 678140c78f..fcf9b083fb 100644 --- a/src/renderer/src/pages/home/Inputbar/AttachmentPreview.tsx +++ b/src/renderer/src/pages/home/Inputbar/AttachmentPreview.tsx @@ -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' diff --git a/src/renderer/src/pages/home/Inputbar/KnowledgeBaseInput.tsx b/src/renderer/src/pages/home/Inputbar/KnowledgeBaseInput.tsx index 9918290e51..fdcde05aa3 100644 --- a/src/renderer/src/pages/home/Inputbar/KnowledgeBaseInput.tsx +++ b/src/renderer/src/pages/home/Inputbar/KnowledgeBaseInput.tsx @@ -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' diff --git a/src/renderer/src/pages/home/Inputbar/MentionModelsInput.tsx b/src/renderer/src/pages/home/Inputbar/MentionModelsInput.tsx index 1e62b15f85..025c0bb2b3 100644 --- a/src/renderer/src/pages/home/Inputbar/MentionModelsInput.tsx +++ b/src/renderer/src/pages/home/Inputbar/MentionModelsInput.tsx @@ -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' diff --git a/src/renderer/src/pages/home/Messages/MessageEditor.tsx b/src/renderer/src/pages/home/Messages/MessageEditor.tsx index 2b6dd59d1e..09f52a1a3d 100644 --- a/src/renderer/src/pages/home/Messages/MessageEditor.tsx +++ b/src/renderer/src/pages/home/Messages/MessageEditor.tsx @@ -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' diff --git a/src/renderer/src/pages/knowledge/KnowledgeContent.tsx b/src/renderer/src/pages/knowledge/KnowledgeContent.tsx index cba5513082..6d0e350ac0 100644 --- a/src/renderer/src/pages/knowledge/KnowledgeContent.tsx +++ b/src/renderer/src/pages/knowledge/KnowledgeContent.tsx @@ -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' diff --git a/src/renderer/src/pages/settings/ProviderSettings/EditModelPopup/ModelEditContent.tsx b/src/renderer/src/pages/settings/ProviderSettings/EditModelPopup/ModelEditContent.tsx index 8f0053ecab..7dfa6070e1 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/EditModelPopup/ModelEditContent.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/EditModelPopup/ModelEditContent.tsx @@ -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 = ({ 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([]) + // const changedTypesRef = useRef([]) useEffect(() => { if (showMoreSettings) { @@ -151,157 +170,76 @@ const ModelEditContent: FC = ({ 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 ( -
- - + <> + + + {t('models.type.select')} + + + {hasUserModified && ( - + +
+ ) } @@ -405,7 +343,6 @@ const ModelEditContent: FC = ({ provider, mo {showMoreSettings && (
- {t('models.type.select')} = ({ provider, mo } const TypeTitle = styled.div` + display: flex; + justify-content: space-between; + align-items: center; margin: 12px 0; font-size: 14px; font-weight: 600; diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsList.tsx index a76918e17c..a6d7020511 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsList.tsx @@ -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' diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx index 0a29e60f03..4551f839e9 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx @@ -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'