mirror of
https://github.com/langgenius/dify.git
synced 2026-06-07 16:32:01 +08:00
fix(web): resolve model provider console warnings (#36422)
This commit is contained in:
parent
7bc5c89e3c
commit
77f1aeb1ac
@ -21,6 +21,12 @@ describe('CustomizeModal', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
const getAnchorButton = (name: RegExp) => {
|
||||
const button = screen.getByRole('button', { name })
|
||||
expect(button.tagName).toBe('A')
|
||||
return button as HTMLAnchorElement
|
||||
}
|
||||
|
||||
// Rendering tests - verify component renders correctly with various configurations
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing when isShow is true', async () => {
|
||||
@ -131,7 +137,7 @@ describe('CustomizeModal', () => {
|
||||
|
||||
// Assert - find the GitHub link and verify it contains an SVG icon
|
||||
await waitFor(() => {
|
||||
const githubLink = screen.getByRole('link', { name: /step1Operation/i })
|
||||
const githubLink = getAnchorButton(/step1Operation/i)
|
||||
expect(githubLink).toBeInTheDocument()
|
||||
expect(githubLink.querySelector('svg')).toBeInTheDocument()
|
||||
})
|
||||
@ -182,7 +188,7 @@ describe('CustomizeModal', () => {
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
const githubLink = screen.getByRole('link', { name: /step1Operation/i })
|
||||
const githubLink = getAnchorButton(/step1Operation/i)
|
||||
expect(githubLink).toHaveAttribute('href', 'https://github.com/langgenius/webapp-conversation')
|
||||
})
|
||||
})
|
||||
@ -196,7 +202,7 @@ describe('CustomizeModal', () => {
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
const githubLink = screen.getByRole('link', { name: /step1Operation/i })
|
||||
const githubLink = getAnchorButton(/step1Operation/i)
|
||||
expect(githubLink).toHaveAttribute('href', 'https://github.com/langgenius/webapp-conversation')
|
||||
})
|
||||
})
|
||||
@ -210,7 +216,7 @@ describe('CustomizeModal', () => {
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
const githubLink = screen.getByRole('link', { name: /step1Operation/i })
|
||||
const githubLink = getAnchorButton(/step1Operation/i)
|
||||
expect(githubLink).toHaveAttribute('href', 'https://github.com/langgenius/webapp-text-generator')
|
||||
})
|
||||
})
|
||||
@ -224,7 +230,7 @@ describe('CustomizeModal', () => {
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
const githubLink = screen.getByRole('link', { name: /step1Operation/i })
|
||||
const githubLink = getAnchorButton(/step1Operation/i)
|
||||
expect(githubLink).toHaveAttribute('href', 'https://github.com/langgenius/webapp-text-generator')
|
||||
})
|
||||
})
|
||||
@ -238,7 +244,7 @@ describe('CustomizeModal', () => {
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
const githubLink = screen.getByRole('link', { name: /step1Operation/i })
|
||||
const githubLink = getAnchorButton(/step1Operation/i)
|
||||
expect(githubLink).toHaveAttribute('href', 'https://github.com/langgenius/webapp-text-generator')
|
||||
})
|
||||
})
|
||||
@ -255,7 +261,7 @@ describe('CustomizeModal', () => {
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
const githubLink = screen.getByRole('link', { name: /step1Operation/i })
|
||||
const githubLink = getAnchorButton(/step1Operation/i)
|
||||
expect(githubLink).toHaveAttribute('target', '_blank')
|
||||
expect(githubLink).toHaveAttribute('rel', 'noopener noreferrer')
|
||||
})
|
||||
@ -270,7 +276,7 @@ describe('CustomizeModal', () => {
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
const vercelLink = screen.getByRole('link', { name: /step2Operation/i })
|
||||
const vercelLink = getAnchorButton(/step2Operation/i)
|
||||
expect(vercelLink).toHaveAttribute('href', 'https://vercel.com/docs/concepts/deployments/git/vercel-for-github')
|
||||
expect(vercelLink).toHaveAttribute('target', '_blank')
|
||||
expect(vercelLink).toHaveAttribute('rel', 'noopener noreferrer')
|
||||
@ -291,7 +297,7 @@ describe('CustomizeModal', () => {
|
||||
expect(screen.getByText('appOverview.overview.appInfo.customize.way2.operation')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const way2Link = screen.getByRole('link', { name: /way2\.operation/i })
|
||||
const way2Link = getAnchorButton(/way2\.operation/i)
|
||||
expect(way2Link).toHaveAttribute('href', expect.stringContaining('/use-dify/publish/developing-with-apis'))
|
||||
expect(way2Link).toHaveAttribute('target', '_blank')
|
||||
expect(way2Link).toHaveAttribute('rel', 'noopener noreferrer')
|
||||
@ -405,7 +411,7 @@ describe('CustomizeModal', () => {
|
||||
|
||||
// Assert - Find GitHub link and verify it contains an SVG icon with expected class
|
||||
await waitFor(() => {
|
||||
const githubLink = screen.getByRole('link', { name: /step1Operation/i })
|
||||
const githubLink = getAnchorButton(/step1Operation/i)
|
||||
const githubIcon = githubLink.querySelector('svg')
|
||||
expect(githubIcon).toBeInTheDocument()
|
||||
expect(githubIcon).toHaveClass('text-text-secondary')
|
||||
|
||||
@ -67,7 +67,7 @@ const CustomizeModal: FC<IShareLinkProps> = ({
|
||||
<div className="flex flex-col">
|
||||
<div className="text-text-primary">{t(`${prefixCustomize}.way1.step1`, { ns: 'appOverview' })}</div>
|
||||
<div className="mt-1 mb-2 text-xs text-text-tertiary">{t(`${prefixCustomize}.way1.step1Tip`, { ns: 'appOverview' })}</div>
|
||||
<Button render={<a href={`https://github.com/langgenius/${isChatApp ? 'webapp-conversation' : 'webapp-text-generator'}`} target="_blank" rel="noopener noreferrer" />}>
|
||||
<Button nativeButton={false} render={<a href={`https://github.com/langgenius/${isChatApp ? 'webapp-conversation' : 'webapp-text-generator'}`} target="_blank" rel="noopener noreferrer" />}>
|
||||
<GithubIcon className="mr-2 text-text-secondary" />
|
||||
{t(`${prefixCustomize}.way1.step1Operation`, { ns: 'appOverview' })}
|
||||
</Button>
|
||||
@ -78,7 +78,7 @@ const CustomizeModal: FC<IShareLinkProps> = ({
|
||||
<div className="flex flex-col">
|
||||
<div className="text-text-primary">{t(`${prefixCustomize}.way1.step2`, { ns: 'appOverview' })}</div>
|
||||
<div className="mt-1 mb-2 text-xs text-text-tertiary">{t(`${prefixCustomize}.way1.step2Tip`, { ns: 'appOverview' })}</div>
|
||||
<Button render={<a href="https://vercel.com/docs/concepts/deployments/git/vercel-for-github" target="_blank" rel="noopener noreferrer" />}>
|
||||
<Button nativeButton={false} render={<a href="https://vercel.com/docs/concepts/deployments/git/vercel-for-github" target="_blank" rel="noopener noreferrer" />}>
|
||||
<div className="mr-1.5 border-t-0 border-r-[7px] border-b-12 border-l-[7px] border-solid border-text-primary border-t-transparent border-r-transparent border-l-transparent"></div>
|
||||
<span>{t(`${prefixCustomize}.way1.step2Operation`, { ns: 'appOverview' })}</span>
|
||||
</Button>
|
||||
@ -113,6 +113,7 @@ const CustomizeModal: FC<IShareLinkProps> = ({
|
||||
</Tag>
|
||||
<p className="my-2 system-sm-medium text-text-secondary">{t(`${prefixCustomize}.way2.name`, { ns: 'appOverview' })}</p>
|
||||
<Button
|
||||
nativeButton={false}
|
||||
render={<a href={apiDocLink} target="_blank" rel="noopener noreferrer" />}
|
||||
className="mt-2"
|
||||
>
|
||||
|
||||
@ -101,8 +101,8 @@ describe('Header Component', () => {
|
||||
})
|
||||
|
||||
const buttons = screen.getAllByRole('button')
|
||||
// Sidebar(1) + NewChat(1) + ResetChat(1) + ViewForm(1) = 4 buttons
|
||||
expect(buttons).toHaveLength(4)
|
||||
// Sidebar(1) + Conversation operation(1) + NewChat(1) + ResetChat(1) + ViewForm(1) = 5 buttons
|
||||
expect(buttons).toHaveLength(5)
|
||||
})
|
||||
})
|
||||
|
||||
@ -306,8 +306,8 @@ describe('Header Component', () => {
|
||||
})
|
||||
|
||||
const buttons = screen.getAllByRole('button')
|
||||
// Sidebar(1) + NewChat(1) + ResetChat(1) = 3 buttons
|
||||
expect(buttons).toHaveLength(3)
|
||||
// Sidebar(1) + Conversation operation(1) + NewChat(1) + ResetChat(1) = 4 buttons
|
||||
expect(buttons).toHaveLength(4)
|
||||
})
|
||||
|
||||
it('should render system title if conversation id is missing', () => {
|
||||
|
||||
@ -46,12 +46,13 @@ const Operation: FC<Props> = ({
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<DropdownMenuTrigger
|
||||
render={<div />}
|
||||
className={cn(
|
||||
'flex cursor-pointer items-center rounded-lg border-none bg-transparent p-1.5 pl-2 text-text-secondary outline-hidden hover:bg-state-base-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid',
|
||||
open && 'bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<div className={cn('flex cursor-pointer items-center rounded-lg p-1.5 pl-2 text-text-secondary hover:bg-state-base-hover', open && 'bg-state-base-hover')}>
|
||||
<div className="system-md-semibold">{title}</div>
|
||||
<span aria-hidden className="i-ri-arrow-down-s-line size-4" />
|
||||
</div>
|
||||
<span className="system-md-semibold">{title}</span>
|
||||
<span aria-hidden className="i-ri-arrow-down-s-line size-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
placement={placement}
|
||||
|
||||
@ -108,6 +108,7 @@ const AddCustomModel = ({
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
<PopoverTrigger
|
||||
nativeButton={false}
|
||||
render={<div className="inline-block">{renderTrigger(open)}</div>}
|
||||
/>
|
||||
<PopoverContent
|
||||
|
||||
@ -175,6 +175,7 @@ const Authorized = ({
|
||||
onOpenChange={setMergedIsOpen}
|
||||
>
|
||||
<PopoverTrigger
|
||||
nativeButton={false}
|
||||
render={<div className={triggerPopupSameWidth ? 'w-full' : 'inline-block'}>{renderTrigger(mergedIsOpen)}</div>}
|
||||
onClick={handleTriggerClick}
|
||||
/>
|
||||
|
||||
@ -81,6 +81,30 @@ describe('ModelIcon', () => {
|
||||
expect(screen.getByRole('img', { name: /model-icon/i })).toHaveAttribute('src', 'dark.png')
|
||||
})
|
||||
|
||||
it('should fall back to the light icon when dark icon is empty', () => {
|
||||
mockTheme = Theme.dark
|
||||
const provider = createModel({
|
||||
icon_small: createI18nText('light.png'),
|
||||
icon_small_dark: createI18nText(''),
|
||||
})
|
||||
|
||||
render(<ModelIcon provider={provider} />)
|
||||
|
||||
expect(screen.getByRole('img', { name: /model-icon/i })).toHaveAttribute('src', 'light.png')
|
||||
})
|
||||
|
||||
it('should render fallback when icon urls are empty', () => {
|
||||
const provider = createModel({
|
||||
icon_small: createI18nText(''),
|
||||
icon_small_dark: createI18nText(''),
|
||||
})
|
||||
|
||||
const { container } = render(<ModelIcon provider={provider} />)
|
||||
|
||||
expect(screen.queryByRole('img', { name: /model-icon/i })).not.toBeInTheDocument()
|
||||
expect(container.querySelector('.i-custom-vender-other-group')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Provider override
|
||||
it('should ignore icon_small for OpenAI models starting with "o"', () => {
|
||||
const provider = createModel({
|
||||
|
||||
@ -5,7 +5,6 @@ import type {
|
||||
} from '../declarations'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { OpenaiYellow } from '@/app/components/base/icons/src/public/llm'
|
||||
import { Group } from '@/app/components/base/icons/src/vender/other'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import { renderI18nObject } from '@/i18n-config'
|
||||
import { Theme } from '@/types/app'
|
||||
@ -27,20 +26,19 @@ const ModelIcon: FC<ModelIconProps> = ({
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const language = useLanguage()
|
||||
const lightIconUrl = provider?.icon_small ? renderI18nObject(provider.icon_small, language) : ''
|
||||
const darkIconUrl = provider?.icon_small_dark ? renderI18nObject(provider.icon_small_dark, language) : ''
|
||||
const iconUrl = theme === Theme.dark ? darkIconUrl || lightIconUrl : lightIconUrl
|
||||
|
||||
if (provider?.provider && ['openai', 'langgenius/openai/openai'].includes(provider.provider) && modelName?.startsWith('o'))
|
||||
return <div className="flex items-center justify-center"><OpenaiYellow className={cn('size-5', className)} /></div>
|
||||
|
||||
if (provider?.icon_small) {
|
||||
if (iconUrl) {
|
||||
return (
|
||||
<div className={cn('flex size-5 items-center justify-center', isDeprecated && 'opacity-50', className)}>
|
||||
<img
|
||||
alt="model-icon"
|
||||
src={renderI18nObject(
|
||||
theme === Theme.dark && provider.icon_small_dark
|
||||
? provider.icon_small_dark
|
||||
: provider.icon_small,
|
||||
language,
|
||||
)}
|
||||
src={iconUrl}
|
||||
className={iconClassName}
|
||||
/>
|
||||
</div>
|
||||
@ -54,7 +52,7 @@ const ModelIcon: FC<ModelIconProps> = ({
|
||||
)}
|
||||
>
|
||||
<div className={cn('flex size-5 items-center justify-center opacity-35', iconClassName)}>
|
||||
<Group className="size-3 text-text-tertiary" />
|
||||
<span aria-hidden className="i-custom-vender-other-group size-3 text-text-tertiary" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -284,7 +284,7 @@ describe('ModelSelectorTrigger', () => {
|
||||
)
|
||||
|
||||
expect(container.querySelector('img[alt="model-icon"]')).not.toBeInTheDocument()
|
||||
expect(container.querySelector('svg')).toBeInTheDocument()
|
||||
expect(container.querySelector('.i-custom-vender-other-group')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -115,6 +115,32 @@ describe('ProviderIcon', () => {
|
||||
expect(img.src).toBe('https://example.com/dark.png')
|
||||
})
|
||||
|
||||
it('should fall back to light icon when dark icon is empty', () => {
|
||||
const mockTheme = vi.mocked(useTheme)
|
||||
mockTheme.mockReturnValue({ theme: Theme.dark, themes: [], setTheme: vi.fn() } as UseThemeReturnType)
|
||||
const provider = createProvider({
|
||||
icon_small: { en_US: 'https://example.com/light.png', zh_Hans: 'https://example.com/light.png' },
|
||||
icon_small_dark: { en_US: '', zh_Hans: '' },
|
||||
})
|
||||
|
||||
render(<ProviderIcon provider={provider} />)
|
||||
|
||||
const img = screen.getByAltText('provider-icon') as HTMLImageElement
|
||||
expect(img.src).toBe('https://example.com/light.png')
|
||||
})
|
||||
|
||||
it('should render fallback icon when provider icon is empty', () => {
|
||||
const provider = createProvider({
|
||||
icon_small: { en_US: '', zh_Hans: '' },
|
||||
icon_small_dark: { en_US: '', zh_Hans: '' },
|
||||
})
|
||||
|
||||
const { container } = render(<ProviderIcon provider={provider} />)
|
||||
|
||||
expect(screen.queryByAltText('provider-icon')).not.toBeInTheDocument()
|
||||
expect(container.querySelector('.i-custom-vender-other-group')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should fall back to localized labels when available', () => {
|
||||
const mockLang = vi.mocked(useLanguage)
|
||||
mockLang.mockReturnValue('zh_Hans')
|
||||
|
||||
@ -18,6 +18,9 @@ const ProviderIcon: FC<ProviderIconProps> = ({
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const language = useLanguage()
|
||||
const lightIconUrl = renderI18nObject(provider.icon_small, language)
|
||||
const darkIconUrl = provider.icon_small_dark ? renderI18nObject(provider.icon_small_dark, language) : ''
|
||||
const iconUrl = theme === Theme.dark ? darkIconUrl || lightIconUrl : lightIconUrl
|
||||
|
||||
if (provider.provider === 'langgenius/anthropic/anthropic') {
|
||||
return (
|
||||
@ -38,16 +41,19 @@ const ProviderIcon: FC<ProviderIconProps> = ({
|
||||
|
||||
return (
|
||||
<div className={cn('inline-flex items-center gap-2', className)}>
|
||||
<img
|
||||
alt="provider-icon"
|
||||
src={renderI18nObject(
|
||||
theme === Theme.dark && provider.icon_small_dark
|
||||
? provider.icon_small_dark
|
||||
: provider.icon_small,
|
||||
language,
|
||||
)}
|
||||
className="size-6"
|
||||
/>
|
||||
{iconUrl
|
||||
? (
|
||||
<img
|
||||
alt="provider-icon"
|
||||
src={iconUrl}
|
||||
className="size-6"
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className="flex size-6 items-center justify-center rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-subtle">
|
||||
<span aria-hidden className="i-custom-vender-other-group size-4 text-text-tertiary" />
|
||||
</div>
|
||||
)}
|
||||
<div className="system-md-semibold text-text-primary">
|
||||
{renderI18nObject(provider.label, language)}
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user