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 { Avatar, cn } from '@heroui/react'
import { getModelLogo } from '@renderer/config/models' import { getModelLogoById } from '@renderer/config/models'
import { ApiModel } from '@renderer/types' import { ApiModel } from '@renderer/types'
import React from 'react' 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 }) => { export const ApiModelLabel: React.FC<ModelLabelProps> = ({ model, className, classNames, ...props }) => {
return ( return (
<div className={cn('flex items-center gap-1', className, classNames?.container)} {...props}> <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> <Ellipsis className={classNames?.modelName}>{model?.name}</Ellipsis>
<span className={classNames?.divider}> | </span> <span className={classNames?.divider}> | </span>
<Ellipsis className={classNames?.providerName}>{model?.provider_name}</Ellipsis> <Ellipsis className={classNames?.providerName}>{model?.provider_name}</Ellipsis>

View File

@ -14,7 +14,7 @@ interface Props {
const ModelAvatar: FC<Props> = ({ model, size, props, className }) => { const ModelAvatar: FC<Props> = ({ model, size, props, className }) => {
return ( return (
<Avatar <Avatar
src={getModelLogo(model?.id || '')} src={getModelLogo(model)}
style={{ style={{
width: size, width: size,
height: size, height: size,

View File

@ -3,7 +3,7 @@ import { HStack } from '@renderer/components/Layout'
import ModelTagsWithLabel from '@renderer/components/ModelTagsWithLabel' import ModelTagsWithLabel from '@renderer/components/ModelTagsWithLabel'
import { TopView } from '@renderer/components/TopView' import { TopView } from '@renderer/components/TopView'
import { DynamicVirtualList, type DynamicVirtualListRef } from '@renderer/components/VirtualList' 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 { useApiModels } from '@renderer/hooks/agents/useModels'
import { getModelUniqId } from '@renderer/services/ModelService' import { getModelUniqId } from '@renderer/services/ModelService'
import { getProviderNameById } from '@renderer/services/ProviderService' import { getProviderNameById } from '@renderer/services/ProviderService'
@ -114,7 +114,7 @@ const PopupContainer: React.FC<Props> = ({ model, apiFilter, modelFilter, showTa
</TagsContainer> </TagsContainer>
), ),
icon: ( icon: (
<Avatar src={getModelLogo(model.id || '')} size={24}> <Avatar src={getModelLogoById(model.id || '')} size={24}>
{first(model.name) || 'M'} {first(model.name) || 'M'}
</Avatar> </Avatar>
), ),

View File

@ -123,7 +123,7 @@ const PopupContainer: React.FC<Props> = ({ model, filter: baseFilter, showTagFil
</TagsContainer> </TagsContainer>
), ),
icon: ( icon: (
<Avatar src={getModelLogo(model.id || '')} size={24}> <Avatar src={getModelLogo(model)} size={24}>
{first(model.name) || 'M'} {first(model.name) || 'M'}
</Avatar> </Avatar>
), ),

View File

@ -16,7 +16,7 @@ import {
import { loggerService } from '@logger' import { loggerService } from '@logger'
import type { Selection } from '@react-types/shared' import type { Selection } from '@react-types/shared'
import ClaudeIcon from '@renderer/assets/images/models/claude.png' 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 { permissionModeCards } from '@renderer/constants/permissionModes'
import { useAgents } from '@renderer/hooks/agents/useAgents' import { useAgents } from '@renderer/hooks/agents/useAgents'
import { useApiModels } from '@renderer/hooks/agents/useModels' import { useApiModels } from '@renderer/hooks/agents/useModels'
@ -244,7 +244,7 @@ export const AgentModal: React.FC<Props> = ({ agent, isOpen: _isOpen, onClose: _
type: 'model', type: 'model',
key: model.id, key: model.id,
label: model.name, label: model.name,
avatar: getModelLogo(model.id), avatar: getModelLogoById(model.id),
providerId: model.provider, providerId: model.provider,
providerName: model.provider_name providerName: model.provider_name
})) satisfies ModelOption[] })) satisfies ModelOption[]

View File

@ -7,7 +7,7 @@ export interface BaseOption {
key: string key: string
label: string label: string
// img src // img src
avatar: string avatar?: string
} }
export interface ModelOption extends BaseOption { 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 YoudaoLogo from '@renderer/assets/images/providers/netease-youdao.svg'
import NomicLogo from '@renderer/assets/images/providers/nomic.png' import NomicLogo from '@renderer/assets/images/providers/nomic.png'
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.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 const isLight = true
if (!modelId) { if (!modelId) {
@ -289,7 +290,7 @@ export function getModelLogo(modelId: string) {
longcat: LongCatAppLogo, longcat: LongCatAppLogo,
bytedance: BytedanceModelLogo, bytedance: BytedanceModelLogo,
'(V_1|V_1_TURBO|V_2|V_2A|V_2_TURBO|DESCRIBE|UPSCALE)': IdeogramModelLogo '(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) { for (const key in logoMap) {
const regex = new RegExp(key, 'i') const regex = new RegExp(key, 'i')
@ -300,3 +301,7 @@ export function getModelLogo(modelId: string) {
return undefined 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 deepseek_hybrid: ['off', ...MODEL_SUPPORTED_REASONING_EFFORT.deepseek_hybrid] as const
} 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' let thinkingModelType: ThinkingModelType = 'default'
const modelId = getLowerBaseModelName(model.id) const modelId = getLowerBaseModelName(model.id)
if (isGPT5SeriesModel(model)) { if (isGPT5SeriesModel(model)) {
@ -99,12 +107,16 @@ export const getThinkModelType = (model: Model): ThinkingModelType => {
return thinkingModelType return thinkingModelType
} }
/** 用于判断是否支持控制思考但不一定以reasoning_effort的方式 */ export const getThinkModelType = (model: Model): ThinkingModelType => {
export function isSupportedThinkingTokenModel(model?: Model): boolean { const { idResult, nameResult } = withModelIdAndNameAsId(model, _getThinkModelType)
if (!model) { if (idResult !== 'default') {
return false return idResult
} else {
return nameResult
} }
}
function _isSupportedThinkingTokenModel(model: Model): boolean {
// Specifically for DeepSeek V3.1. White list for now // Specifically for DeepSeek V3.1. White list for now
if (isDeepSeekHybridInferenceModel(model)) { if (isDeepSeekHybridInferenceModel(model)) {
return ( 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 { export function isSupportedReasoningEffortModel(model?: Model): boolean {
if (!model) { if (!model) {
return false return false

View File

@ -134,7 +134,7 @@ const MentionModelsButton: FC<Props> = ({
), ),
description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />, description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />,
icon: ( icon: (
<Avatar src={getModelLogo(m.id)} size={20}> <Avatar src={getModelLogo(m)} size={20}>
{first(m.name)} {first(m.name)}
</Avatar> </Avatar>
), ),
@ -170,7 +170,7 @@ const MentionModelsButton: FC<Props> = ({
), ),
description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />, description: <ModelTagsWithLabel model={m} showLabel={false} size={10} style={{ opacity: 0.8 }} />,
icon: ( icon: (
<Avatar src={getModelLogo(m.id)} size={20}> <Avatar src={getModelLogo(m)} size={20}>
{first(m.name)} {first(m.name)}
</Avatar> </Avatar>
), ),

View File

@ -3,7 +3,7 @@ import '@xyflow/react/dist/style.css'
import { RobotOutlined, UserOutlined } from '@ant-design/icons' import { RobotOutlined, UserOutlined } from '@ant-design/icons'
import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar' import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar'
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar' 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 { useTheme } from '@renderer/context/ThemeProvider'
import useAvatar from '@renderer/hooks/useAvatar' import useAvatar from '@renderer/hooks/useAvatar'
import { useSettings } from '@renderer/hooks/useSettings' 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 CustomNode: FC<{ data: any }> = ({ data }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { setTimeoutTimer } = useTimer() const { setTimeoutTimer } = useTimer()
@ -87,7 +88,7 @@ const CustomNode: FC<{ data: any }> = ({ data }) => {
if (data.modelInfo) { if (data.modelInfo) {
avatar = <ModelAvatar model={data.modelInfo} size={32} /> avatar = <ModelAvatar model={data.modelInfo} size={32} />
} else if (data.modelId) { } else if (data.modelId) {
const modelLogo = getModelLogo(data.modelId) const modelLogo = getModelLogo(data.modelInfo) ?? getModelLogoById(data.modelId)
avatar = ( avatar = (
<Avatar <Avatar
src={modelLogo} src={modelLogo}
@ -181,6 +182,7 @@ interface ChatFlowHistoryProps {
} }
// 定义节点和边的类型 // 定义节点和边的类型
// FIXME: No any plz
type FlowNode = Node<any> type FlowNode = Node<any>
type FlowEdge = Edge<any> type FlowEdge = Edge<any>
@ -202,6 +204,7 @@ const defaultEdgeOptions = {
const ChatFlowHistory: FC<ChatFlowHistoryProps> = ({ conversationId }) => { const ChatFlowHistory: FC<ChatFlowHistoryProps> = ({ conversationId }) => {
const { t } = useTranslation() const { t } = useTranslation()
// FIXME: no any plz
const [nodes, setNodes, onNodesChange] = useNodesState<any>([]) const [nodes, setNodes, onNodesChange] = useNodesState<any>([])
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([]) const [edges, setEdges, onEdgesChange] = useEdgesState<any>([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
@ -408,6 +411,7 @@ const ChatFlowHistory: FC<ChatFlowHistoryProps> = ({ conversationId }) => {
const assistantNodeId = `orphan-assistant-${aMsg.id}` const assistantNodeId = `orphan-assistant-${aMsg.id}`
// 获取模型数据 // 获取模型数据
// FIXME: No any plz
const aMsgAny = aMsg as any const aMsgAny = aMsg as any
// 获取模型名称 // 获取模型名称

View File

@ -1,6 +1,6 @@
import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar' import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar'
import { APP_NAME, AppLogo, isLocalAi } from '@renderer/config/env' 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 { useTheme } from '@renderer/context/ThemeProvider'
import useAvatar from '@renderer/hooks/useAvatar' import useAvatar from '@renderer/hooks/useAvatar'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
@ -25,7 +25,7 @@ interface MessageLineProps {
const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => { const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => {
if (isLocalAi) return AppLogo if (isLocalAi) return AppLogo
return modelId ? getModelLogo(modelId) : undefined return modelId ? getModelLogoById(modelId) : undefined
} }
const MessageAnchorLine: FC<MessageLineProps> = ({ messages }) => { 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 { HStack } from '@renderer/components/Layout'
import UserPopup from '@renderer/components/Popups/UserPopup' import UserPopup from '@renderer/components/Popups/UserPopup'
import { APP_NAME, AppLogo, isLocalAi } from '@renderer/config/env' 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 { useTheme } from '@renderer/context/ThemeProvider'
import { useAgent } from '@renderer/hooks/agents/useAgent' import { useAgent } from '@renderer/hooks/agents/useAgent'
import useAvatar from '@renderer/hooks/useAvatar' import useAvatar from '@renderer/hooks/useAvatar'
@ -32,7 +32,7 @@ interface Props {
const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => { const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => {
if (isLocalAi) return AppLogo if (isLocalAi) return AppLogo
return modelId ? getModelLogo(modelId) : undefined return modelId ? getModelLogoById(modelId) : undefined
} }
const MessageHeader: FC<Props> = memo(({ assistant, model, message, topic, isGroupContextMessage }) => { 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 ModelIdWithTags from '@renderer/components/ModelIdWithTags'
import CustomTag from '@renderer/components/Tags/CustomTag' import CustomTag from '@renderer/components/Tags/CustomTag'
import { DynamicVirtualList } from '@renderer/components/VirtualList' 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 { isNewApiProvider } from '@renderer/config/providers'
import FileItem from '@renderer/pages/files/FileItem' import FileItem from '@renderer/pages/files/FileItem'
import NewApiBatchAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup' import NewApiBatchAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup'
@ -200,7 +200,7 @@ const ModelListItem: React.FC<ModelListItemProps> = memo(({ model, provider, onA
boxShadow: 'none' boxShadow: 'none'
}} }}
fileInfo={{ 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} />, name: <ModelIdWithTags model={model} />,
extra: model.description && <ExpandableText text={model.description} />, extra: model.description && <ExpandableText text={model.description} />,
ext: '.model', ext: '.model',

View File

@ -36,7 +36,7 @@ const ModelListItem: React.FC<ModelListItemProps> = ({ ref, model, modelStatus,
return ( return (
<ListItem ref={ref}> <ListItem ref={ref}>
<HStack alignItems="center" gap={10} style={{ flex: 1 }}> <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()} {model?.name?.[0]?.toUpperCase()}
</Avatar> </Avatar>
<ModelIdWithTags <ModelIdWithTags