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:
Phantom 2025-10-22 04:39:38 +08:00 committed by GitHub
parent 5c7b81569e
commit 39fa080263
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 58 additions and 27 deletions

View File

@ -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>

View File

@ -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,

View File

@ -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>
),

View File

@ -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>
),

View File

@ -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[]

View File

@ -7,7 +7,7 @@ export interface BaseOption {
key: string
label: string
// img src
avatar: string
avatar?: string
}
export interface ModelOption extends BaseOption {

View File

@ -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
}

View File

@ -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

View File

@ -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>
),

View File

@ -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
// 获取模型名称

View File

@ -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 }) => {

View File

@ -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 }) => {

View File

@ -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',

View File

@ -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