mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:28:10 +08:00
feat: implement model status mapping and enhance UI components
- Added a new status-mapping file to define internationalization keys for model statuses. - Updated ModelName and Trigger components to conditionally display model metadata based on status. - Enhanced tests for ModelSelectorTrigger to validate rendering behavior for different credential panel states. - Improved styling and tooltip integration for status badges in the Trigger component.
This commit is contained in:
@ -39,7 +39,7 @@ const ModelName: FC<ModelNameProps> = ({
|
||||
if (!modelItem)
|
||||
return null
|
||||
return (
|
||||
<div className={cn('system-sm-regular flex items-center gap-0.5 overflow-hidden truncate text-ellipsis text-components-input-text-filled', className)}>
|
||||
<div className={cn('flex items-center gap-0.5 overflow-hidden truncate text-ellipsis text-components-input-text-filled system-sm-regular', className)}>
|
||||
<div
|
||||
className="truncate"
|
||||
title={modelItem.label[language] || modelItem.label.en_US}
|
||||
|
||||
@ -76,7 +76,6 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
} = useTextGenerationCurrentProviderAndModelAndModelList(
|
||||
{ provider, model: modelId },
|
||||
)
|
||||
|
||||
const hasDeprecated = !currentProvider || !currentModel
|
||||
const modelDisabled = currentModel?.status !== ModelStatusEnum.active
|
||||
const disabled = !isAPIKeySet || hasDeprecated || modelDisabled
|
||||
|
||||
@ -26,7 +26,21 @@ vi.mock('../model-icon', () => ({
|
||||
}))
|
||||
|
||||
vi.mock('../model-name', () => ({
|
||||
default: ({ modelItem }: { modelItem: { model: string } }) => <div>{modelItem.model}</div>,
|
||||
default: ({
|
||||
modelItem,
|
||||
showMode,
|
||||
showFeatures,
|
||||
}: {
|
||||
modelItem: { model: string }
|
||||
showMode?: boolean
|
||||
showFeatures?: boolean
|
||||
}) => (
|
||||
<div>
|
||||
<span>{modelItem.model}</span>
|
||||
{showMode && <span data-testid="model-name-mode">mode</span>}
|
||||
{showFeatures && <span data-testid="model-name-features">features</span>}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
describe('Trigger', () => {
|
||||
@ -67,6 +81,8 @@ describe('Trigger', () => {
|
||||
|
||||
expect(screen.getByText('gpt-4')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('model-icon')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('model-name-mode')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('model-name-features')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render fallback model id when current model is missing', () => {
|
||||
@ -128,6 +144,8 @@ describe('Trigger', () => {
|
||||
|
||||
expect(screen.getByText('common.modelProvider.selector.creditsExhausted')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('model-icon')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('model-name-mode')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('model-name-features')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should resolve provider from context when currentProvider is missing in split layout', () => {
|
||||
@ -178,6 +196,56 @@ describe('Trigger', () => {
|
||||
expect(screen.getByText('common.modelProvider.selector.apiKeyUnavailable')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render credits exhausted badge in workflow mode', () => {
|
||||
mockUseCredentialPanelState.mockReturnValue({
|
||||
variant: 'credits-exhausted',
|
||||
supportsCredits: true,
|
||||
isCreditsExhausted: true,
|
||||
priority: 'credits',
|
||||
showPrioritySwitcher: true,
|
||||
hasCredentials: false,
|
||||
credentialName: undefined,
|
||||
credits: 0,
|
||||
})
|
||||
|
||||
render(
|
||||
<Trigger
|
||||
currentProvider={currentProvider}
|
||||
currentModel={currentModel}
|
||||
providerName="openai"
|
||||
modelId="gpt-4"
|
||||
isInWorkflow
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('common.modelProvider.selector.creditsExhausted')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render api unavailable badge in workflow mode', () => {
|
||||
mockUseCredentialPanelState.mockReturnValue({
|
||||
variant: 'api-unavailable',
|
||||
supportsCredits: true,
|
||||
isCreditsExhausted: false,
|
||||
priority: 'apiKey',
|
||||
showPrioritySwitcher: true,
|
||||
hasCredentials: true,
|
||||
credentialName: 'Primary Key',
|
||||
credits: 0,
|
||||
})
|
||||
|
||||
render(
|
||||
<Trigger
|
||||
currentProvider={currentProvider}
|
||||
currentModel={currentModel}
|
||||
providerName="openai"
|
||||
modelId="gpt-4"
|
||||
isInWorkflow
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('common.modelProvider.selector.apiKeyUnavailable')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render incompatible badge when deprecated model is disabled', () => {
|
||||
render(
|
||||
<Trigger
|
||||
@ -191,9 +259,11 @@ describe('Trigger', () => {
|
||||
)
|
||||
|
||||
expect(screen.getByText('common.modelProvider.selector.incompatible')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('model-name-mode')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('model-name-features')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render warning icon when model status is disabled but not deprecated', () => {
|
||||
it('should render incompatible badge when model status is disabled but not deprecated', () => {
|
||||
render(
|
||||
<Trigger
|
||||
currentProvider={currentProvider}
|
||||
@ -205,7 +275,7 @@ describe('Trigger', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(document.querySelector('.text-\\[\\#F79009\\]')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.modelProvider.selector.incompatible')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -6,16 +6,15 @@ import type {
|
||||
} from '../declarations'
|
||||
import { RiArrowDownSLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
|
||||
import { SlidersH } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { MODEL_STATUS_TEXT } from '../declarations'
|
||||
import { useLanguage } from '../hooks'
|
||||
import { ModelStatusEnum } from '../declarations'
|
||||
import ModelIcon from '../model-icon'
|
||||
import ModelName from '../model-name'
|
||||
import { useCredentialPanelState } from '../provider-added-card/use-credential-panel-state'
|
||||
import { MODEL_STATUS_I18N_KEY } from '../status-mapping'
|
||||
|
||||
export type TriggerProps = {
|
||||
open?: boolean
|
||||
@ -35,18 +34,24 @@ const Trigger: FC<TriggerProps> = ({
|
||||
providerName,
|
||||
modelId,
|
||||
hasDeprecated,
|
||||
modelDisabled,
|
||||
modelDisabled: _modelDisabled,
|
||||
isInWorkflow,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useLanguage()
|
||||
const { modelProviders } = useProviderContext()
|
||||
const isEmpty = !modelId || !providerName
|
||||
const currentModelProvider = modelProviders.find(p => p.provider === providerName)
|
||||
const state = useCredentialPanelState(currentModelProvider)
|
||||
const hasCredits = !state.isCreditsExhausted
|
||||
const showCreditsExhausted = !isEmpty && !hasCredits && state.supportsCredits
|
||||
const showCreditsExhausted = !isEmpty && state.priority === 'credits' && state.supportsCredits && state.isCreditsExhausted
|
||||
const showApiKeyUnavailable = !isEmpty && state.variant === 'api-unavailable'
|
||||
const effectiveStatus = showCreditsExhausted
|
||||
? ModelStatusEnum.quotaExceeded
|
||||
: showApiKeyUnavailable
|
||||
? ModelStatusEnum.credentialRemoved
|
||||
: currentModel?.status
|
||||
const statusI18nKey = effectiveStatus ? MODEL_STATUS_I18N_KEY[effectiveStatus] : undefined
|
||||
const isCreditsExhausted = effectiveStatus === ModelStatusEnum.quotaExceeded
|
||||
const shouldShowModelMeta = effectiveStatus === ModelStatusEnum.active && !(disabled && hasDeprecated)
|
||||
|
||||
// Non-workflow status error: split layout with badge + settings button
|
||||
if ((showCreditsExhausted || showApiKeyUnavailable) && !isInWorkflow) {
|
||||
@ -60,7 +65,14 @@ const Trigger: FC<TriggerProps> = ({
|
||||
/>
|
||||
<div className="flex flex-1 items-center truncate px-1 py-[3px]">
|
||||
{currentModel
|
||||
? <ModelName className="grow" modelItem={currentModel} showMode showFeatures />
|
||||
? (
|
||||
<ModelName
|
||||
className="grow"
|
||||
modelItem={currentModel}
|
||||
showMode={shouldShowModelMeta}
|
||||
showFeatures={shouldShowModelMeta}
|
||||
/>
|
||||
)
|
||||
: <div className="truncate text-[13px] font-normal text-components-input-text-filled">{modelId}</div>}
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center pr-0.5">
|
||||
@ -121,8 +133,8 @@ const Trigger: FC<TriggerProps> = ({
|
||||
<ModelName
|
||||
className="mr-1.5 text-text-primary"
|
||||
modelItem={currentModel}
|
||||
showMode
|
||||
showFeatures
|
||||
showMode={shouldShowModelMeta}
|
||||
showFeatures={shouldShowModelMeta}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@ -144,32 +156,49 @@ const Trigger: FC<TriggerProps> = ({
|
||||
!isEmpty && (
|
||||
disabled
|
||||
? (
|
||||
hasDeprecated
|
||||
? (
|
||||
<Tooltip popupContent={t('modelProvider.selector.incompatibleTip', { ns: 'common' })}>
|
||||
<div className="ml-auto flex min-w-[20px] shrink-0 items-center justify-center gap-[3px] rounded-md border border-text-warning bg-components-badge-bg-dimm px-[5px] py-0.5">
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<div className="ml-auto flex min-w-[20px] shrink-0 items-center justify-center gap-[3px] rounded-md border border-text-warning bg-components-badge-bg-dimm px-[5px] py-0.5">
|
||||
<span className="i-ri-alert-fill h-3 w-3 text-text-warning" />
|
||||
<span className="whitespace-nowrap text-text-warning system-xs-medium">
|
||||
{t('modelProvider.selector.incompatible', { ns: 'common' })}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent placement="top">
|
||||
{t('modelProvider.selector.incompatibleTip', { ns: 'common' })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
: statusI18nKey
|
||||
? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
disabled={effectiveStatus !== ModelStatusEnum.noPermission}
|
||||
render={(
|
||||
<div
|
||||
className={cn(
|
||||
'ml-auto flex min-w-[20px] shrink-0 items-center justify-center gap-[3px] rounded-md border border-text-warning px-[5px] py-0.5',
|
||||
isCreditsExhausted && 'bg-components-badge-bg-dimm',
|
||||
)}
|
||||
>
|
||||
<span className="i-ri-alert-fill h-3 w-3 text-text-warning" />
|
||||
<span className="whitespace-nowrap text-text-warning system-xs-medium">
|
||||
{t('modelProvider.selector.incompatible', { ns: 'common' })}
|
||||
{t(statusI18nKey as 'modelProvider.selector.creditsExhausted', { ns: 'common' })}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
: (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
(modelDisabled && currentModel)
|
||||
? MODEL_STATUS_TEXT[currentModel.status as string][language]
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<AlertTriangle className="h-4 w-4 text-[#F79009]" />
|
||||
</Tooltip>
|
||||
)
|
||||
)
|
||||
: (
|
||||
<SlidersH className={cn(!isInWorkflow ? 'text-indigo-600' : 'text-text-tertiary', 'h-4 w-4 shrink-0')} />
|
||||
)
|
||||
)}
|
||||
/>
|
||||
<TooltipContent placement="top">
|
||||
{t('modelProvider.selector.incompatibleTip', { ns: 'common' })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)
|
||||
: (
|
||||
<SlidersH className={cn(!isInWorkflow ? 'text-indigo-600' : 'text-text-tertiary', 'h-4 w-4 shrink-0')} />
|
||||
)
|
||||
)
|
||||
}
|
||||
{
|
||||
|
||||
@ -166,6 +166,30 @@ describe('ModelSelectorTrigger', () => {
|
||||
)
|
||||
|
||||
expect(screen.getByText('common.modelProvider.selector.creditsExhausted').parentElement).toHaveClass('bg-components-badge-bg-dimm')
|
||||
expect(screen.queryByText('CHAT')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should hide model meta when api key is unavailable', () => {
|
||||
mockUseCredentialPanelState.mockReturnValue({
|
||||
variant: 'api-unavailable',
|
||||
priority: 'apiKey',
|
||||
supportsCredits: true,
|
||||
showPrioritySwitcher: true,
|
||||
hasCredentials: true,
|
||||
isCreditsExhausted: false,
|
||||
credentialName: 'Primary Key',
|
||||
credits: 0,
|
||||
})
|
||||
|
||||
render(
|
||||
<ModelSelectorTrigger
|
||||
currentProvider={createModel()}
|
||||
currentModel={createModelItem()}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('common.modelProvider.selector.apiKeyUnavailable')).toBeInTheDocument()
|
||||
expect(screen.queryByText('CHAT')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not show status badge when selected model is readonly', () => {
|
||||
@ -189,6 +213,7 @@ describe('ModelSelectorTrigger', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText('CHAT')).not.toBeInTheDocument()
|
||||
await user.hover(screen.getByText('common.modelProvider.selector.incompatible'))
|
||||
|
||||
expect(await screen.findByText('common.modelProvider.selector.incompatibleTip')).toBeInTheDocument()
|
||||
@ -196,20 +221,18 @@ describe('ModelSelectorTrigger', () => {
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should show deprecated tooltip when hovering warn icon', async () => {
|
||||
it('should show incompatible badge for deprecated selection', async () => {
|
||||
const user = userEvent.setup()
|
||||
const { container } = render(
|
||||
render(
|
||||
<ModelSelectorTrigger
|
||||
defaultModel={{ provider: 'openai', model: 'legacy-model' }}
|
||||
/>,
|
||||
)
|
||||
|
||||
const warnIcon = container.querySelector('.i-ri-alert-line')
|
||||
expect(warnIcon).toBeInTheDocument()
|
||||
expect(screen.getByText('common.modelProvider.selector.incompatible')).toBeInTheDocument()
|
||||
await user.hover(screen.getByText('common.modelProvider.selector.incompatible'))
|
||||
|
||||
await user.hover(warnIcon as HTMLElement)
|
||||
|
||||
expect(await screen.findByText('common.modelProvider.deprecated')).toBeInTheDocument()
|
||||
expect(await screen.findByText('common.modelProvider.selector.incompatibleTip')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render fallback icon when deprecated provider is not found', () => {
|
||||
|
||||
@ -12,14 +12,7 @@ import { ModelStatusEnum } from '../declarations'
|
||||
import ModelIcon from '../model-icon'
|
||||
import ModelName from '../model-name'
|
||||
import { useCredentialPanelState } from '../provider-added-card/use-credential-panel-state'
|
||||
|
||||
const STATUS_I18N_KEY: Partial<Record<ModelStatusEnum, string>> = {
|
||||
[ModelStatusEnum.quotaExceeded]: 'modelProvider.selector.creditsExhausted',
|
||||
[ModelStatusEnum.noConfigure]: 'modelProvider.selector.configureRequired',
|
||||
[ModelStatusEnum.noPermission]: 'modelProvider.selector.incompatible',
|
||||
[ModelStatusEnum.disabled]: 'modelProvider.selector.disabled',
|
||||
[ModelStatusEnum.credentialRemoved]: 'modelProvider.selector.apiKeyUnavailable',
|
||||
}
|
||||
import { MODEL_STATUS_I18N_KEY } from '../status-mapping'
|
||||
|
||||
type ModelSelectorTriggerProps = {
|
||||
currentProvider?: Model
|
||||
@ -56,14 +49,18 @@ const ModelSelectorTrigger: FC<ModelSelectorTriggerProps> = ({
|
||||
&& selectedProviderState.priority === 'credits'
|
||||
&& selectedProviderState.supportsCredits
|
||||
&& selectedProviderState.isCreditsExhausted
|
||||
const shouldShowApiKeyUnavailable = isSelected && selectedProviderState.variant === 'api-unavailable'
|
||||
const effectiveStatus = shouldShowCreditsExhausted
|
||||
? ModelStatusEnum.quotaExceeded
|
||||
: currentModel?.status
|
||||
: shouldShowApiKeyUnavailable
|
||||
? ModelStatusEnum.credentialRemoved
|
||||
: currentModel?.status
|
||||
|
||||
const isActive = isSelected && effectiveStatus === ModelStatusEnum.active
|
||||
const isDisabled = isDeprecated || (isSelected && !isActive)
|
||||
const statusI18nKey = isSelected && effectiveStatus ? STATUS_I18N_KEY[effectiveStatus] : undefined
|
||||
const statusI18nKey = isSelected && effectiveStatus ? MODEL_STATUS_I18N_KEY[effectiveStatus] : undefined
|
||||
const isCreditsExhausted = isSelected && effectiveStatus === ModelStatusEnum.quotaExceeded
|
||||
const shouldShowModelMeta = effectiveStatus === ModelStatusEnum.active
|
||||
|
||||
const deprecatedProvider = isDeprecated
|
||||
? modelProviders.find(p => p.provider === defaultModel.provider)
|
||||
@ -102,8 +99,8 @@ const ModelSelectorTrigger: FC<ModelSelectorTriggerProps> = ({
|
||||
<ModelName
|
||||
className="grow"
|
||||
modelItem={currentModel}
|
||||
showMode
|
||||
showFeatures
|
||||
showMode={shouldShowModelMeta}
|
||||
showFeatures={shouldShowModelMeta}
|
||||
/>
|
||||
)}
|
||||
{isDeprecated && (
|
||||
@ -143,12 +140,18 @@ const ModelSelectorTrigger: FC<ModelSelectorTriggerProps> = ({
|
||||
|
||||
{isDeprecated && showDeprecatedWarnIcon && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={(
|
||||
<span className="i-ri-alert-line h-4 w-4 shrink-0 text-text-warning-secondary" />
|
||||
)}
|
||||
<TooltipTrigger
|
||||
render={(
|
||||
<div className="flex shrink-0 items-center gap-[3px] rounded-md border border-text-warning bg-components-badge-bg-dimm px-[5px] py-0.5">
|
||||
<span className="i-ri-alert-fill h-3 w-3 text-text-warning" />
|
||||
<span className="whitespace-nowrap text-text-warning system-xs-medium">
|
||||
{t('modelProvider.selector.incompatible', { ns: 'common' })}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
<TooltipContent placement="top">
|
||||
{t('modelProvider.deprecated', { ns: 'common' })}
|
||||
{t('modelProvider.selector.incompatibleTip', { ns: 'common' })}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { ModelStatusEnum } from './declarations'
|
||||
|
||||
export const MODEL_STATUS_I18N_KEY: Partial<Record<ModelStatusEnum, string>> = {
|
||||
[ModelStatusEnum.quotaExceeded]: 'modelProvider.selector.creditsExhausted',
|
||||
[ModelStatusEnum.noConfigure]: 'modelProvider.selector.configureRequired',
|
||||
[ModelStatusEnum.noPermission]: 'modelProvider.selector.incompatible',
|
||||
[ModelStatusEnum.disabled]: 'modelProvider.selector.disabled',
|
||||
[ModelStatusEnum.credentialRemoved]: 'modelProvider.selector.apiKeyUnavailable',
|
||||
}
|
||||
@ -27,6 +27,7 @@ import {
|
||||
VariableX,
|
||||
WebhookLine,
|
||||
} from '@/app/components/base/icons/src/vender/workflow'
|
||||
import { API_PREFIX } from '@/config'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { BlockEnum } from './types'
|
||||
|
||||
@ -82,6 +83,17 @@ const getIcon = (type: BlockEnum, className: string) => {
|
||||
|
||||
return <DefaultIcon className={className} />
|
||||
}
|
||||
|
||||
const normalizeToolIconUrl = (toolIcon: string) => {
|
||||
const protectedPluginIconPath = '/workspaces/current/plugin/icon'
|
||||
const pathIndex = toolIcon.indexOf(protectedPluginIconPath)
|
||||
|
||||
if (pathIndex < 0)
|
||||
return toolIcon
|
||||
|
||||
return `${API_PREFIX}${toolIcon.slice(pathIndex)}`
|
||||
}
|
||||
|
||||
const ICON_CONTAINER_BG_COLOR_MAP: Record<string, string> = {
|
||||
[BlockEnum.Start]: 'bg-util-colors-blue-brand-blue-brand-500',
|
||||
[BlockEnum.LLM]: 'bg-util-colors-indigo-indigo-500',
|
||||
@ -119,6 +131,9 @@ const BlockIcon: FC<BlockIconProps> = ({
|
||||
}) => {
|
||||
const isToolOrDataSourceOrTriggerPlugin = type === BlockEnum.Tool || type === BlockEnum.DataSource || type === BlockEnum.TriggerPlugin
|
||||
const showDefaultIcon = !isToolOrDataSourceOrTriggerPlugin || !toolIcon
|
||||
const resolvedToolIcon = typeof toolIcon === 'string'
|
||||
? normalizeToolIconUrl(toolIcon)
|
||||
: toolIcon
|
||||
|
||||
return (
|
||||
<div className={
|
||||
@ -142,12 +157,12 @@ const BlockIcon: FC<BlockIconProps> = ({
|
||||
!showDefaultIcon && (
|
||||
<>
|
||||
{
|
||||
typeof toolIcon === 'string'
|
||||
typeof resolvedToolIcon === 'string'
|
||||
? (
|
||||
<div
|
||||
className="h-full w-full shrink-0 rounded-md bg-cover bg-center"
|
||||
style={{
|
||||
backgroundImage: `url(${toolIcon})`,
|
||||
backgroundImage: `url(${resolvedToolIcon})`,
|
||||
}}
|
||||
>
|
||||
</div>
|
||||
@ -156,8 +171,8 @@ const BlockIcon: FC<BlockIconProps> = ({
|
||||
<AppIcon
|
||||
className="!h-full !w-full shrink-0"
|
||||
size="tiny"
|
||||
icon={toolIcon?.content}
|
||||
background={toolIcon?.background}
|
||||
icon={resolvedToolIcon?.content}
|
||||
background={resolvedToolIcon?.background}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user