'use client' import type { PluginDetail } from '../../types' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { useQuery } from '@tanstack/react-query' import { useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Badge from '@/app/components/base/badge' import { AuthCategory, PluginAuth } from '@/app/components/plugins/plugin-auth' import { OperationDropdown } from '@/app/components/plugins/plugin-detail-panel/operation-dropdown' import { BUILTIN_TOOLS_ARRAY } from '@/app/components/plugins/readme-panel/constants' import { useReadmePanelStore } from '@/app/components/plugins/readme-panel/store' import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker' import { API_PREFIX } from '@/config' import { useGetLanguage, useLocale } from '@/context/i18n' import { userProfileQueryOptions } from '@/features/account-profile/client' import useTheme from '@/hooks/use-theme' import { useAllToolProviders } from '@/service/use-tools' import { getMarketplaceUrl } from '@/utils/var' import { AutoUpdateLine } from '../../../base/icons/src/vender/system' import Verified from '../../base/badges/verified' import DeprecationNotice from '../../base/deprecation-notice' import Icon from '../../card/base/card-icon' import Description from '../../card/base/description' import OrgInfo from '../../card/base/org-info' import Title from '../../card/base/title' import useReferenceSetting from '../../plugin-page/use-reference-setting' import { convertUTCDaySecondsToLocalSeconds, timeOfDayToDayjs, } from '../../reference-setting-modal/auto-update-setting/utils' import { PluginCategoryEnum, PluginSource } from '../../types' import { HeaderModals, PluginSourceBadge } from './components' import { useDetailHeaderState, usePluginOperations } from './hooks' type Props = Readonly<{ canDeletePlugin?: boolean canUpdatePlugin?: boolean detail: PluginDetail isReadmeView?: boolean onHide?: () => void onUpdate?: (isDelete?: boolean) => void }> const getIconSrc = ( icon: string | undefined, iconDark: string | undefined, theme: string, tenantId: string, ): string => { const iconFileName = theme === 'dark' && iconDark ? iconDark : icon if (!iconFileName) return '' return iconFileName.startsWith('http') ? iconFileName : `${API_PREFIX}/workspaces/current/plugin/icon?tenant_id=${tenantId}&filename=${iconFileName}` } const getDetailUrl = ( source: PluginSource, meta: PluginDetail['meta'], author: string, name: string, locale: string, theme: string, ): string => { if (source === PluginSource.github) { const repo = meta?.repo if (!repo) return '' return `https://github.com/${repo}` } if (source === PluginSource.marketplace) return getMarketplaceUrl(`/plugins/${author}/${name}`, { language: locale, theme }) return '' } const DetailHeader = ({ canDeletePlugin = true, canUpdatePlugin = true, detail, isReadmeView = false, onHide, onUpdate, }: Props) => { const { t } = useTranslation() const openReadmePanel = useReadmePanelStore((s) => s.openReadmePanel) const { data: timezone } = useQuery({ ...userProfileQueryOptions(), select: (data) => data.profile.timezone ?? undefined, }) const { theme } = useTheme() const locale = useGetLanguage() const currentLocale = useLocale() const detailCategory = detail.declaration?.category ?? PluginCategoryEnum.tool const { referenceSetting } = useReferenceSetting(detailCategory) const { source, tenant_id, version, latest_version, latest_unique_identifier, meta, plugin_id, status, deprecated_reason, alternative_plugin_id, } = detail const { author, category, name, label, description, icon, icon_dark, verified, tool } = detail.declaration || detail const { modalStates, versionPicker, hasNewVersion, isAutoUpgradeEnabled, isFromGitHub, isFromMarketplace, } = useDetailHeaderState(detail) const { handleUpdate, handleUpdatedFromMarketplace, handleDelete } = usePluginOperations({ detail, modalStates, versionPicker, isFromMarketplace, canDeletePlugin, canUpdatePlugin, onUpdate, }) const isTool = category === PluginCategoryEnum.tool const providerBriefInfo = tool?.identity const providerKey = `${plugin_id}/${providerBriefInfo?.name}` const { data: collectionList = [] } = useAllToolProviders(isTool) const provider = useMemo(() => { return collectionList.find((collection) => collection.name === providerKey) }, [collectionList, providerKey]) const iconSrc = getIconSrc(icon, icon_dark, theme, tenant_id) const detailUrl = getDetailUrl(source, meta, author, name, currentLocale, theme) const canViewReadme = !!detail.plugin_unique_identifier && !BUILTIN_TOOLS_ARRAY.includes(detail.id) const { auto_upgrade: autoUpgradeInfo } = referenceSetting || {} const handleViewReadme = useCallback(() => { openReadmePanel({ detail, presentation: 'drawer', }) }, [detail, openReadmePanel]) const handleVersionSelect = (state: { version: string unique_identifier: string isDowngrade?: boolean }) => { versionPicker.setTargetVersion(state) handleUpdate(state.isDowngrade) } const handleTriggerLatestUpdate = () => { if (isFromMarketplace) { versionPicker.setTargetVersion({ version: latest_version, unique_identifier: latest_unique_identifier, }) } handleUpdate() } return (
{/* Plugin Icon */}
{/* Plugin Info */}
{/* Title Row */}
{verified && !isReadmeView && ( <Verified className="ml-0.5 size-4" text={t(($) => $['marketplace.verifiedTip'], { ns: 'plugin' })} /> )} {/* Version Picker */} {!!version && ( <PluginVersionPicker disabled={!canUpdatePlugin || !isFromMarketplace || isReadmeView} isShow={versionPicker.isShow} onShowChange={versionPicker.setIsShow} pluginID={plugin_id} currentVersion={version} onSelect={handleVersionSelect} trigger={ <Badge className={cn( 'mx-1', versionPicker.isShow && 'bg-state-base-hover', (versionPicker.isShow || (canUpdatePlugin && isFromMarketplace)) && 'hover:bg-state-base-hover', )} uppercase={false} text={ <> <div>{isFromGitHub ? (meta?.version ?? version ?? '') : version}</div> {canUpdatePlugin && isFromMarketplace && !isReadmeView && ( <span aria-hidden className="ml-1 i-ri-arrow-left-right-line size-3 text-text-tertiary" /> )} </> } hasRedCornerMark={hasNewVersion} /> } /> )} {/* Auto Update Badge */} {isAutoUpgradeEnabled && !isReadmeView && ( <Tooltip> <TooltipTrigger render={ <div> <Badge className="mr-1 cursor-pointer px-1"> <AutoUpdateLine className="size-3" /> </Badge> </div> } /> <TooltipContent> {t(($) => $['autoUpdate.nextUpdateTime'], { ns: 'plugin', time: timeOfDayToDayjs( convertUTCDaySecondsToLocalSeconds( autoUpgradeInfo?.upgrade_time_of_day || 0, timezone!, ), ).format('hh:mm A'), })} </TooltipContent> </Tooltip> )} {/* Update Button */} {canUpdatePlugin && (hasNewVersion || isFromGitHub) && ( <Tooltip> <TooltipTrigger delay={300} render={ <Button variant="secondary-accent" size="small" className="h-5!" onClick={handleTriggerLatestUpdate} > {t(($) => $['detailPanel.operation.update'], { ns: 'plugin' })} </Button> } /> <TooltipContent> {t(($) => $['detailPanel.operation.updateTooltip'], { ns: 'plugin' })} </TooltipContent> </Tooltip> )} </div> {/* Org Info Row */} <div className="mb-1 flex h-4 items-center justify-between"> <div className="mt-0.5 flex items-center"> <OrgInfo packageNameClassName="w-auto" orgName={author} packageName={name?.includes('/') ? name.split('/').pop() || '' : name} /> {!!source && <PluginSourceBadge source={source} />} </div> </div> </div> {/* Action Buttons */} {!isReadmeView && ( <div className="flex gap-1"> <OperationDropdown source={source} onInfo={modalStates.showPluginInfo} onCheckVersion={handleUpdate} onRemove={modalStates.showDeleteConfirm} onViewReadme={canViewReadme ? handleViewReadme : undefined} detailUrl={detailUrl} showCheckVersion={canUpdatePlugin} showRemove={canDeletePlugin} /> <ActionButton onClick={onHide}> <span aria-hidden className="i-ri-close-line size-4" /> </ActionButton> </div> )} </div> {/* Deprecation Notice */} {isFromMarketplace && ( <DeprecationNotice status={status} deprecatedReason={deprecated_reason} alternativePluginId={alternative_plugin_id} alternativePluginURL={getMarketplaceUrl(`/plugins/${alternative_plugin_id}`, { language: currentLocale, theme, })} className="mt-3" /> )} {/* Description */} {!isReadmeView && ( <Description className="mt-3 mb-2 h-auto" text={description[locale]} descriptionLineRows={2} /> )} {/* Plugin Auth for Tools */} {category === PluginCategoryEnum.tool && !isReadmeView && ( <PluginAuth pluginPayload={{ provider: provider?.name || '', category: AuthCategory.tool, providerType: provider?.type, detail, }} /> )} {/* Modals */} <HeaderModals detail={detail} modalStates={modalStates} targetVersion={versionPicker.targetVersion} isDowngrade={versionPicker.isDowngrade} isAutoUpgradeEnabled={isAutoUpgradeEnabled} onUpdatedFromMarketplace={handleUpdatedFromMarketplace} onDelete={handleDelete} /> </div> ) } export default DetailHeader