'use client' import type { Plugin } from '../types' import { cn } from '@langgenius/dify-ui/cn' import { useAtomValue } from 'jotai' import * as React from 'react' import { useTranslation } from '#i18n' import { useGetLanguage } from '@/context/i18n' import { currentWorkspaceIdAtom } from '@/context/workspace-state' import useTheme from '@/hooks/use-theme' import { renderI18nObject } from '@/i18n-config' import { Theme } from '@/types/app' import { formatNumber } from '@/utils/format' 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' import Placeholder from './base/placeholder' import Title from './base/title' export type CardPayload = Omit & { icon: string | { content: string; background: string } icon_dark?: string | { content: string; background: string } } type Props = Readonly<{ className?: string payload: CardPayload titleLeft?: React.ReactNode installed?: boolean installFailed?: boolean hideCornerMark?: boolean descriptionLineRows?: number footer?: React.ReactNode isLoading?: boolean loadingFileName?: string limitedInstall?: boolean compact?: boolean variant?: 'default' | 'marketplace' }> const Card = ({ className, payload, titleLeft, installed, installFailed, hideCornerMark, descriptionLineRows, footer, isLoading = false, loadingFileName, limitedInstall = false, compact = false, variant = 'default', }: Props) => { const locale = useGetLanguage() const { t } = useTranslation() const { categoriesMap } = useCategories(true) const currentWorkspaceId = useAtomValue(currentWorkspaceIdAtom) const { category, type, name, org, label, brief, icon, icon_dark, verified, from } = payload const badges = payload.badges ?? [] const { theme } = useTheme() const iconSrc = getPluginCardIconUrl( { from, name, org, type }, theme === Theme.dark && icon_dark ? icon_dark : icon, currentWorkspaceId, ) const getLocalizedText = (obj: Record | undefined) => obj ? renderI18nObject(obj, locale) : '' const isPartner = badges.includes('partner') const effectiveDescriptionLineRows = descriptionLineRows ?? (compact ? 1 : 2) const isMarketplaceVariant = variant === 'marketplace' const cornerMarkText = categoriesMap[type === 'bundle' ? type : category]?.label ?? '' const wrapClassName = cn( 'hover-bg-components-panel-on-panel-item-bg relative overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', isMarketplaceVariant && 'h-[148px] transition-all group-hover:bg-components-panel-on-panel-item-bg-hover group-hover:shadow-md', className, ) if (isLoading) { return } if (isMarketplaceVariant) { return (
{!hideCornerMark && }
{getLocalizedText(label)}
{isPartner && ( $['marketplace.partnerTip'], { ns: 'plugin' })} /> )} {verified && ( $['marketplace.verifiedTip'], { ns: 'plugin' })} /> )} {titleLeft}
{org && (
{t(($) => $.author, { ns: 'tools' })} {org}
)} {org && payload.install_count !== undefined && ( ยท )} {payload.install_count !== undefined && ( {t(($) => $.install, { ns: 'plugin', num: formatNumber(payload.install_count), })} )}
{!!footer &&
{footer}
}
) } return (
{!hideCornerMark && } {/* Header */}
{isPartner && ( <Partner className="ml-0.5 size-4" text={t(($) => $['marketplace.partnerTip'], { ns: 'plugin' })} /> )} {verified && ( <Verified className="ml-0.5 size-4" text={t(($) => $['marketplace.verifiedTip'], { ns: 'plugin' })} /> )} {titleLeft} {/* This can be version badge */} </div> <OrgInfo className="mt-0.5" orgName={org} packageName={name} /> </div> </div> <Description className={compact ? 'mt-1' : 'mt-3'} text={getLocalizedText(brief)} descriptionLineRows={effectiveDescriptionLineRows} /> {!!footer && <div>{footer}</div>} </div> {limitedInstall && ( <div className="relative flex h-8 items-center gap-x-2 px-3 after:absolute after:inset-0 after:bg-toast-warning-bg after:opacity-40"> <span aria-hidden className="i-ri-alert-fill size-3 shrink-0 text-text-warning-secondary" /> <p className="z-10 grow system-xs-regular text-text-secondary"> {t(($) => $['installModal.installWarning'], { ns: 'plugin' })} </p> </div> )} </div> ) } export default React.memo(Card)