feat: enhance model plugin workflow checks and model provider management UX (#33289)

Signed-off-by: yyh <yuanyouhuilyz@gmail.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: CodingOnStar <hanxujiang@dify.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Coding On Star <447357187@qq.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: -LAN- <laipz8200@outlook.com>
Co-authored-by: statxc <tyleradams93226@gmail.com>
This commit is contained in:
yyh
2026-03-18 10:16:15 +08:00
committed by GitHub
parent aa4a9877f5
commit bbe975c6bc
319 changed files with 19582 additions and 5541 deletions

View File

@ -2,6 +2,7 @@ import type { Plugin } from '../../types'
import { render, screen } from '@testing-library/react'
import * as React from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { API_PREFIX, MARKETPLACE_API_PREFIX } from '@/config'
import { PluginCategoryEnum } from '../../types'
import Card from '../index'
@ -40,6 +41,12 @@ vi.mock('@/utils/format', () => ({
formatNumber: (num: number) => num.toLocaleString(),
}))
vi.mock('@/context/app-context', () => ({
useSelector: (selector: (value: { currentWorkspace: { id: string } }) => string) => selector({
currentWorkspace: { id: 'workspace-123' },
}),
}))
vi.mock('@/utils/mcp', () => ({
shouldUseMcpIcon: (src: unknown) => typeof src === 'object' && src !== null && (src as { content?: string })?.content === '🔗',
}))
@ -189,6 +196,36 @@ describe('Card', () => {
expect(iconElement).toBeInTheDocument()
})
it('should normalize package icon filenames to workspace icon urls', () => {
const plugin = createMockPlugin({
from: 'package',
icon: 'custom-icon.png',
})
const { container } = render(<Card payload={plugin} />)
const iconElement = container.querySelector('[style*="background-image"]')
expect(iconElement).toBeInTheDocument()
expect(iconElement).toHaveStyle({
backgroundImage: `url(${API_PREFIX}/workspaces/current/plugin/icon?tenant_id=workspace-123&filename=custom-icon.png)`,
})
})
it('should normalize marketplace icon filenames to marketplace icon urls', () => {
const plugin = createMockPlugin({
from: 'marketplace',
icon: 'custom-icon.png',
})
const { container } = render(<Card payload={plugin} />)
const iconElement = container.querySelector('[style*="background-image"]')
expect(iconElement).toBeInTheDocument()
expect(iconElement).toHaveStyle({
backgroundImage: `url(${MARKETPLACE_API_PREFIX}/plugins/${plugin.org}/${plugin.name}/icon)`,
})
})
it('should use icon_dark when theme is dark and icon_dark is provided', () => {
// Set theme to dark
mockTheme = 'dark'

View File

@ -3,6 +3,7 @@ import type { Plugin } from '../types'
import { useTranslation } from '#i18n'
import { RiAlertFill } from '@remixicon/react'
import * as React from 'react'
import { useSelector } from '@/context/app-context'
import { useGetLanguage } from '@/context/i18n'
import useTheme from '@/hooks/use-theme'
import {
@ -14,6 +15,7 @@ import Partner from '../base/badges/partner'
import Verified from '../base/badges/verified'
import Icon from '../card/base/card-icon'
import { useCategories } from '../hooks'
import { getPluginCardIconUrl } from '../utils'
import CornerMark from './base/corner-mark'
import Description from './base/description'
import OrgInfo from './base/org-info'
@ -50,9 +52,14 @@ const Card = ({
const locale = useGetLanguage()
const { t } = useTranslation()
const { categoriesMap } = useCategories(true)
const { category, type, name, org, label, brief, icon, icon_dark, verified, badges = [] } = payload
const currentWorkspaceId = useSelector(s => s.currentWorkspace.id)
const { category, type, name, org, label, brief, icon, icon_dark, verified, badges = [], from } = payload
const { theme } = useTheme()
const iconSrc = theme === Theme.dark && icon_dark ? icon_dark : icon
const iconSrc = getPluginCardIconUrl(
{ from, name, org, type },
theme === Theme.dark && icon_dark ? icon_dark : icon,
currentWorkspaceId,
)
const getLocalizedText = (obj: Record<string, string> | undefined) =>
obj ? renderI18nObject(obj, locale) : ''
const isPartner = badges.includes('partner')
@ -101,7 +108,7 @@ const Card = ({
&& (
<div className="relative flex h-8 items-center gap-x-2 px-3 after:absolute after:bottom-0 after:left-0 after:right-0 after:top-0 after:bg-toast-warning-bg after:opacity-40">
<RiAlertFill className="h-3 w-3 shrink-0 text-text-warning-secondary" />
<p className="system-xs-regular z-10 grow text-text-secondary">
<p className="z-10 grow text-text-secondary system-xs-regular">
{t('installModal.installWarning', { ns: 'plugin' })}
</p>
</div>