'use client' import type { PluginDetail } from '../../types' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Badge from '@/app/components/base/badge' import Button from '@/app/components/base/button' import { Tooltip, TooltipContent, TooltipTrigger } from '@/app/components/base/ui/tooltip' import { AuthCategory, PluginAuth } from '@/app/components/plugins/plugin-auth' import OperationDropdown from '@/app/components/plugins/plugin-detail-panel/operation-dropdown' import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker' import { API_PREFIX } from '@/config' import { useAppContext } from '@/context/app-context' import { useGetLanguage, useLocale } from '@/context/i18n' import useTheme from '@/hooks/use-theme' import { useAllToolProviders } from '@/service/use-tools' import { cn } from '@/utils/classnames' 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 = { 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 = ({ detail, isReadmeView = false, onHide, onUpdate, }: Props) => { const { t } = useTranslation() const { userProfile: { timezone } } = useAppContext() const { theme } = useTheme() const locale = useGetLanguage() const currentLocale = useLocale() const { referenceSetting } = useReferenceSetting() 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, 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 { auto_upgrade: autoUpgradeInfo } = referenceSetting || {} 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 h-4 w-4" text={t('marketplace.verifiedTip', { ns: 'plugin' })} />} {/* Version Picker */} {!!version && ( <PluginVersionPicker disabled={!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 || isFromMarketplace) && 'hover:bg-state-base-hover', )} uppercase={false} text={( <> <div>{isFromGitHub ? (meta?.version ?? version ?? '') : version}</div> {isFromMarketplace && !isReadmeView && <span aria-hidden className="i-ri-arrow-left-right-line ml-1 h-3 w-3 text-text-tertiary" />} </> )} hasRedCornerMark={hasNewVersion} /> )} /> )} {/* Auto Update Badge */} {isAutoUpgradeEnabled && !isReadmeView && ( <Tooltip> <TooltipTrigger delay={0} 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 */} {(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} detailUrl={detailUrl} /> <ActionButton onClick={onHide}> <span aria-hidden className="i-ri-close-line h-4 w-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="mb-2 mt-3 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