mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 18:50:56 +08:00
fix: check model capability with model name (#10860)
* fix(ModelListItem): fallback to model name when logo not found by id Use model name as fallback when fetching model logo if lookup by id fails * refactor(model-logo): simplify model logo handling with unified function Replace direct calls to getModelLogo with model.id with new getModelLogo function that handles both id and name fallback Rename original getModelLogo to getModelLogoById for clarity Update all components to use the new unified function * refactor(model-utils): improve model type detection with fallback logic Add helper function to check both model ID and name as ID for type detection Refactor getThinkModelType and isSupportedThinkingTokenModel to use new fallback logic * refactor(agent-popups): make avatar optional in BaseOption interface update getModelLogo functions to return undefined instead of null for consistency * refactor(models): remove outdated comment in reasoning.ts
This commit is contained in:
parent
5c7b81569e
commit
39fa080263
@ -1,5 +1,5 @@
|
||||
import { Avatar, cn } from '@heroui/react'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { getModelLogoById } from '@renderer/config/models'
|
||||
import { ApiModel } from '@renderer/types'
|
||||
import React from 'react'
|
||||
|
||||
@ -19,7 +19,10 @@ export interface ModelLabelProps extends Omit<React.ComponentPropsWithRef<'div'>
|
||||
export const ApiModelLabel: React.FC<ModelLabelProps> = ({ model, className, classNames, ...props }) => {
|
||||
return (
|
||||
<div className={cn('flex items-center gap-1', className, classNames?.container)} {...props}>
|
||||
<Avatar src={model ? getModelLogo(model.id) : undefined} className={cn('h-4 w-4', classNames?.avatar)} />
|
||||
<Avatar
|
||||
src={model ? (getModelLogoById(model.id) ?? getModelLogoById(model.name)) : undefined}
|
||||
className={cn('h-4 w-4', classNames?.avatar)}
|
||||
/>
|
||||
<Ellipsis className={classNames?.modelName}>{model?.name}</Ellipsis>
|
||||
<span className={classNames?.divider}> | </span>
|
||||
<Ellipsis className={classNames?.providerName}>{model?.provider_name}</Ellipsis>
|
||||
|
||||
@ -14,7 +14,7 @@ interface Props {
|
||||
const ModelAvatar: FC<Props> = ({ model, size, props, className }) => {
|
||||
return (
|
||||
<Avatar
|
||||
src={getModelLogo(model?.id || '')}
|
||||
src={getModelLogo(model)}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
|
||||
@ -3,7 +3,7 @@ import { HStack } from '@renderer/components/Layout'
|
||||
import ModelTagsWithLabel from '@renderer/components/ModelTagsWithLabel'
|
||||
import { TopView } from '@renderer/components/TopView'
|
||||
import { DynamicVirtualList, type DynamicVirtualListRef } from '@renderer/components/VirtualList'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { getModelLogoById } from '@renderer/config/models'
|
||||
import { useApiModels } from '@renderer/hooks/agents/useModels'
|
||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||
import { getProviderNameById } from '@renderer/services/ProviderService'
|
||||
@ -114,7 +114,7 @@ const PopupContainer: React.FC<Props> = ({ model, apiFilter, modelFilter, showTa
|
||||
</TagsContainer>
|
||||
),
|
||||
icon: (
|
||||
<Avatar src={getModelLogo(model.id || '')} size={24}>
|
||||
<Avatar src={getModelLogoById(model.id || '')} size={24}>
|
||||
{first(model.name) || 'M'}
|
||||
</Avatar>
|
||||
),
|
||||
|
||||
@ -123,7 +123,7 @@ const PopupContainer: React.FC<Props> = ({ model, filter: baseFilter, showTagFil
|
||||
</TagsContainer>
|
||||
),
|
||||
icon: (
|
||||
<Avatar src={getModelLogo(model.id || '')} size={24}>
|
||||
<Avatar src={getModelLogo(model)} size={24}>
|
||||
{first(model.name) || 'M'}
|
||||
</Avatar>
|
||||
),
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
import { loggerService } from '@logger'
|
||||
import type { Selection } from '@react-types/shared'
|
||||
import ClaudeIcon from '@renderer/assets/images/models/claude.png'
|
||||
import { agentModelFilter, getModelLogo } from '@renderer/config/models'
|
||||
import { agentModelFilter, getModelLogoById } from '@renderer/config/models'
|
||||
import { permissionModeCards } from '@renderer/constants/permissionModes'
|
||||
import { useAgents } from '@renderer/hooks/agents/useAgents'
|
||||
import { useApiModels } from '@renderer/hooks/agents/useModels'
|
||||
@ -244,7 +244,7 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
|
||||
type: 'model',
|
||||
key: model.id,
|
||||
label: model.name,
|
||||
avatar: getModelLogo(model.id),
|
||||
avatar: getModelLogoById(model.id),
|
||||
providerId: model.provider,
|
||||
providerName: model.provider_name
|
||||
})) satisfies ModelOption[]
|
||||
|
||||
@ -7,7 +7,7 @@ export interface BaseOption {
|
||||
key: string
|
||||
label: string
|
||||
// img src
|
||||
avatar: string
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
export interface ModelOption extends BaseOption {
|
||||
|
||||
@ -155,8 +155,9 @@ import ZhipuModelLogoDark from '@renderer/assets/images/models/zhipu_dark.png'
|
||||
import YoudaoLogo from '@renderer/assets/images/providers/netease-youdao.svg'
|
||||
import NomicLogo from '@renderer/assets/images/providers/nomic.png'
|
||||
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png'
|
||||
import { Model } from '@renderer/types'
|
||||
|
||||
export function getModelLogo(modelId: string) {
|
||||
export function getModelLogoById(modelId: string): string | undefined {
|
||||
const isLight = true
|
||||
|
||||
if (!modelId) {
|
||||
@ -289,7 +290,7 @@ export function getModelLogo(modelId: string) {
|
||||
longcat: LongCatAppLogo,
|
||||
bytedance: BytedanceModelLogo,
|
||||
'(V_1|V_1_TURBO|V_2|V_2A|V_2_TURBO|DESCRIBE|UPSCALE)': IdeogramModelLogo
|
||||
} as const
|
||||
} as const satisfies Record<string, string>
|
||||
|
||||
for (const key in logoMap) {
|
||||
const regex = new RegExp(key, 'i')
|
||||
@ -300,3 +301,7 @@ export function getModelLogo(modelId: string) {
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function getModelLogo(model: Model | undefined | null): string | undefined {
|
||||
return model ? (getModelLogoById(model.id) ?? getModelLogoById(model.name)) : undefined
|
||||
}
|
||||
|
||||
@ -59,7 +59,15 @@ export const MODEL_SUPPORTED_OPTIONS: ThinkingOptionConfig = {
|
||||
deepseek_hybrid: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.deepseek_hybrid] as const
|
||||
} as const
|
||||
|
||||
export const getThinkModelType = (model: Model): ThinkingModelType => {
|
||||
const withModelIdAndNameAsId = <T>(model: Model, fn: (model: Model) => T): { idResult: T; nameResult: T } => {
|
||||
const modelWithNameAsId = { ...model, id: model.name }
|
||||
return {
|
||||
idResult: fn(model),
|
||||
nameResult: fn(modelWithNameAsId)
|
||||
}
|
||||
}
|
||||
|
||||
const _getThinkModelType = (model: Model): ThinkingModelType => {
|
||||
let thinkingModelType: ThinkingModelType = 'default'
|
||||
const modelId = getLowerBaseModelName(model.id)
|
||||
if (isGPT5SeriesModel(model)) {
|
||||
@ -99,12 +107,16 @@ export const getThinkModelType = (model: Model): ThinkingModelType => {
|
||||
return thinkingModelType
|
||||
}
|
||||
|
||||
/** 用于判断是否支持控制思考,但不一定以reasoning_effort的方式 */
|
||||
export function isSupportedThinkingTokenModel(model?: Model): boolean {
|
||||
if (!model) {
|
||||
return false
|
||||
export const getThinkModelType = (model: Model): ThinkingModelType => {
|
||||
const { idResult, nameResult } = withModelIdAndNameAsId(model, _getThinkModelType)
|
||||
if (idResult !== 'default') {
|
||||
return idResult
|
||||
} else {
|
||||
return nameResult
|
||||
}
|
||||
}
|
||||
|
||||
function _isSupportedThinkingTokenModel(model: Model): boolean {
|
||||
// Specifically for DeepSeek V3.1. White list for now
|
||||
if (isDeepSeekHybridInferenceModel(model)) {
|
||||
return (
|
||||
@ -132,6 +144,13 @@ export function isSupportedThinkingTokenModel(model?: Model): boolean {
|
||||
)
|
||||
}
|
||||
|
||||
/** 用于判断是否支持控制思考,但不一定以reasoning_effort的方式 */
|
||||
export function isSupportedThinkingTokenModel(model?: Model): boolean {
|
||||
if (!model) return false
|
||||
const { idResult, nameResult } = withModelIdAndNameAsId(model, _isSupportedThinkingTokenModel)
|
||||
return idResult || nameResult
|
||||
}
|
||||
|
||||
export function isSupportedReasoningEffortModel(model?: Model): boolean {
|
||||
if (!model) {
|
||||
return false
|
||||
|
||||
@ -134,7 +134,7 @@ const MentionModelsButton: FC<Props> = ({
|
||||
),
|
||||
description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />,
|
||||
icon: (
|
||||
<Avatar src={getModelLogo(m.id)} size={20}>
|
||||
<Avatar src={getModelLogo(m)} size={20}>
|
||||
{first(m.name)}
|
||||
</Avatar>
|
||||
),
|
||||
@ -170,7 +170,7 @@ const MentionModelsButton: FC<Props> = ({
|
||||
),
|
||||
description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />,
|
||||
icon: (
|
||||
<Avatar src={getModelLogo(m.id)} size={20}>
|
||||
<Avatar src={getModelLogo(m)} size={20}>
|
||||
{first(m.name)}
|
||||
</Avatar>
|
||||
),
|
||||
|
||||
@ -3,7 +3,7 @@ import '@xyflow/react/dist/style.css'
|
||||
import { RobotOutlined, UserOutlined } from '@ant-design/icons'
|
||||
import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar'
|
||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { getModelLogo, getModelLogoById } from '@renderer/config/models'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import useAvatar from '@renderer/hooks/useAvatar'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
@ -49,6 +49,7 @@ const TooltipFooter = styled.div`
|
||||
`
|
||||
|
||||
// 自定义节点组件
|
||||
// FIXME: no any plz...
|
||||
const CustomNode: FC<{ data: any }> = ({ data }) => {
|
||||
const { t } = useTranslation()
|
||||
const { setTimeoutTimer } = useTimer()
|
||||
@ -87,7 +88,7 @@ const CustomNode: FC<{ data: any }> = ({ data }) => {
|
||||
if (data.modelInfo) {
|
||||
avatar = <ModelAvatar model={data.modelInfo} size={32} />
|
||||
} else if (data.modelId) {
|
||||
const modelLogo = getModelLogo(data.modelId)
|
||||
const modelLogo = getModelLogo(data.modelInfo) ?? getModelLogoById(data.modelId)
|
||||
avatar = (
|
||||
<Avatar
|
||||
src={modelLogo}
|
||||
@ -181,6 +182,7 @@ interface ChatFlowHistoryProps {
|
||||
}
|
||||
|
||||
// 定义节点和边的类型
|
||||
// FIXME: No any plz
|
||||
type FlowNode = Node<any>
|
||||
type FlowEdge = Edge<any>
|
||||
|
||||
@ -202,6 +204,7 @@ const defaultEdgeOptions = {
|
||||
|
||||
const ChatFlowHistory: FC<ChatFlowHistoryProps> = ({ conversationId }) => {
|
||||
const { t } = useTranslation()
|
||||
// FIXME: no any plz
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState<any>([])
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
@ -408,6 +411,7 @@ const ChatFlowHistory: FC<ChatFlowHistoryProps> = ({ conversationId }) => {
|
||||
const assistantNodeId = `orphan-assistant-${aMsg.id}`
|
||||
|
||||
// 获取模型数据
|
||||
// FIXME: No any plz
|
||||
const aMsgAny = aMsg as any
|
||||
|
||||
// 获取模型名称
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar'
|
||||
import { APP_NAME, AppLogo, isLocalAi } from '@renderer/config/env'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { getModelLogoById } from '@renderer/config/models'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import useAvatar from '@renderer/hooks/useAvatar'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
@ -25,7 +25,7 @@ interface MessageLineProps {
|
||||
|
||||
const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => {
|
||||
if (isLocalAi) return AppLogo
|
||||
return modelId ? getModelLogo(modelId) : undefined
|
||||
return modelId ? getModelLogoById(modelId) : undefined
|
||||
}
|
||||
|
||||
const MessageAnchorLine: FC<MessageLineProps> = ({ messages }) => {
|
||||
|
||||
@ -2,7 +2,7 @@ import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import UserPopup from '@renderer/components/Popups/UserPopup'
|
||||
import { APP_NAME, AppLogo, isLocalAi } from '@renderer/config/env'
|
||||
import { getModelLogo } from '@renderer/config/models'
|
||||
import { getModelLogoById } from '@renderer/config/models'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useAgent } from '@renderer/hooks/agents/useAgent'
|
||||
import useAvatar from '@renderer/hooks/useAvatar'
|
||||
@ -32,7 +32,7 @@ interface Props {
|
||||
|
||||
const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => {
|
||||
if (isLocalAi) return AppLogo
|
||||
return modelId ? getModelLogo(modelId) : undefined
|
||||
return modelId ? getModelLogoById(modelId) : undefined
|
||||
}
|
||||
|
||||
const MessageHeader: FC<Props> = memo(({ assistant, model, message, topic, isGroupContextMessage }) => {
|
||||
|
||||
@ -2,7 +2,7 @@ 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 { getModelLogoById } from '@renderer/config/models'
|
||||
import { isNewApiProvider } from '@renderer/config/providers'
|
||||
import FileItem from '@renderer/pages/files/FileItem'
|
||||
import NewApiBatchAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup'
|
||||
@ -200,7 +200,7 @@ const ModelListItem: React.FC<ModelListItemProps> = memo(({ model, provider, onA
|
||||
boxShadow: 'none'
|
||||
}}
|
||||
fileInfo={{
|
||||
icon: <Avatar src={getModelLogo(model.id)}>{model?.name?.[0]?.toUpperCase()}</Avatar>,
|
||||
icon: <Avatar src={getModelLogoById(model.id)}>{model?.name?.[0]?.toUpperCase()}</Avatar>,
|
||||
name: <ModelIdWithTags model={model} />,
|
||||
extra: model.description && <ExpandableText text={model.description} />,
|
||||
ext: '.model',
|
||||
|
||||
@ -36,7 +36,7 @@ const ModelListItem: React.FC<ModelListItemProps> = ({ ref, model, modelStatus,
|
||||
return (
|
||||
<ListItem ref={ref}>
|
||||
<HStack alignItems="center" gap={10} style={{ flex: 1 }}>
|
||||
<Avatar src={getModelLogo(model.id)} size={24}>
|
||||
<Avatar src={getModelLogo(model)} size={24}>
|
||||
{model?.name?.[0]?.toUpperCase()}
|
||||
</Avatar>
|
||||
<ModelIdWithTags
|
||||
|
||||
Loading…
Reference in New Issue
Block a user