'use client' import type { MotionValue } from 'motion/react' import { motion, useMotionValue, useSpring, useTransform } from 'motion/react' import { useEffect, useLayoutEffect, useRef } from 'react' import { useLocale, useTranslation } from '#i18n' import Divider from '@/app/components/base/divider' import DifyLogo from '@/app/components/base/logo/dify-logo' import { SubmitRequestDropdown } from '@/app/components/plugins/plugin-page/nav-operations' import PluginTypeSwitch from '../plugin-type-switch' import SearchBoxWrapper from '../search-box/search-box-wrapper' type DescriptionProps = { isMarketplacePlatform?: boolean marketplaceNav?: React.ReactNode scrollContainerId?: string } const MAX_SCROLL = 120 const EXPANDED_PADDING_TOP = 32 const COLLAPSED_PADDING_TOP = 12 const EXPANDED_PADDING_BOTTOM = 24 const COLLAPSED_PADDING_BOTTOM = 12 const EXPANDED_TITLE_MARGIN_TOP = 32 const EXPANDED_TABS_MARGIN_TOP = 32 const Description = ({ isMarketplacePlatform = false, marketplaceNav, scrollContainerId = 'marketplace-container', }: DescriptionProps) => { const { t } = useTranslation('plugin') const { t: tCommon } = useTranslation('common') const locale = useLocale() const isZhHans = locale === 'zh-Hans' const rafRef = useRef(null) const lastProgressRef = useRef(0) const headerRef = useRef(null) const titleContentRef = useRef(null) const hasMarketplaceNav = isMarketplacePlatform const progress = useMotionValue(0) const titleHeight = useMotionValue(72) const smoothProgress = useSpring(progress, { stiffness: 260, damping: 34 }) useLayoutEffect(() => { if (!isMarketplacePlatform) return const node = titleContentRef.current if (!node) return const updateHeight = () => { titleHeight.set(node.scrollHeight) } updateHeight() if (typeof ResizeObserver === 'undefined') return const observer = new ResizeObserver(updateHeight) observer.observe(node) return () => observer.disconnect() }, [isMarketplacePlatform, titleHeight]) useEffect(() => { if (!isMarketplacePlatform) return const container = document.getElementById(scrollContainerId) if (!container) return const handleScroll = () => { if (rafRef.current) cancelAnimationFrame(rafRef.current) rafRef.current = requestAnimationFrame(() => { const scrollTop = Math.round(container.scrollTop) const heightDelta = container.scrollHeight - container.clientHeight const effectiveMaxScroll = Math.max(1, Math.min(MAX_SCROLL, heightDelta)) const rawProgress = Math.min(Math.max(scrollTop / effectiveMaxScroll, 0), 1) const snappedProgress = rawProgress >= 0.95 ? 1 : rawProgress <= 0.05 ? 0 : Math.round(rawProgress * 100) / 100 if (snappedProgress !== lastProgressRef.current) { lastProgressRef.current = snappedProgress progress.set(snappedProgress) } }) } container.addEventListener('scroll', handleScroll, { passive: true }) handleScroll() return () => { container.removeEventListener('scroll', handleScroll) if (rafRef.current) cancelAnimationFrame(rafRef.current) } }, [isMarketplacePlatform, progress, scrollContainerId]) useEffect(() => { if (!isMarketplacePlatform) return const container = document.getElementById(scrollContainerId) const header = headerRef.current if (!container || !header) return const previousOverflowAnchor = container.style.overflowAnchor container.style.overflowAnchor = 'none' let maxHeaderHeight = 0 let lastAppliedOffset = 0 const updateOffset = () => { const currentHeaderHeight = Math.round(header.getBoundingClientRect().height) maxHeaderHeight = Math.max(maxHeaderHeight, currentHeaderHeight) const collapsedHeight = Math.max(0, maxHeaderHeight - currentHeaderHeight) const currentScrollableTop = container.scrollHeight - container.clientHeight const baseScrollableTop = Math.max(0, currentScrollableTop - lastAppliedOffset) const shouldCompensate = baseScrollableTop <= maxHeaderHeight const nextOffset = shouldCompensate ? collapsedHeight : 0 if (nextOffset > 0) { container.style.setProperty('--marketplace-header-collapse-offset', `${nextOffset}px`) } else { container.style.removeProperty('--marketplace-header-collapse-offset') } lastAppliedOffset = nextOffset } updateOffset() if (typeof ResizeObserver === 'undefined') { return () => { container.style.removeProperty('--marketplace-header-collapse-offset') container.style.overflowAnchor = previousOverflowAnchor } } const observer = new ResizeObserver(updateOffset) observer.observe(header) return () => { observer.disconnect() container.style.removeProperty('--marketplace-header-collapse-offset') container.style.overflowAnchor = previousOverflowAnchor } }, [isMarketplacePlatform, scrollContainerId]) const contentOpacity = useTransform(smoothProgress, [0, 1], [1, 0]) const contentScale = useTransform(smoothProgress, [0, 1], [1, 0.9]) const titleMaxHeight: MotionValue = useTransform( [smoothProgress, titleHeight], (values: number[]) => { const currentProgress = values[0] ?? 0 const currentTitleHeight = values[1] ?? 72 return currentTitleHeight * (1 - currentProgress) }, ) const tabsMarginTop = useTransform( smoothProgress, [0, 1], [EXPANDED_TABS_MARGIN_TOP, hasMarketplaceNav ? 16 : 0], ) const titleMarginTop = useTransform( smoothProgress, [0, 1], [hasMarketplaceNav ? EXPANDED_TITLE_MARGIN_TOP : 0, 0], ) const paddingTop = useTransform( smoothProgress, [0, 1], [hasMarketplaceNav ? COLLAPSED_PADDING_TOP : EXPANDED_PADDING_TOP, COLLAPSED_PADDING_TOP], ) const paddingBottom = useTransform( smoothProgress, [0, 1], [EXPANDED_PADDING_BOTTOM, COLLAPSED_PADDING_BOTTOM], ) if (!isMarketplacePlatform) { return ( <>

{t(($) => $['marketplace.empower'])}

{isZhHans && ( <> {tCommon(($) => $['operation.in'])} {t(($) => $['marketplace.difyMarketplace'])} {t(($) => $['marketplace.discover'])} )} {!isZhHans && <>{t(($) => $['marketplace.discover'])}} {t(($) => $['category.models'])} , {t(($) => $['category.tools'])} , {t(($) => $['category.datasources'])} , {t(($) => $['category.triggers'])} , {t(($) => $['category.agents'])} , {t(($) => $['category.extensions'])} {t(($) => $['marketplace.and'])} {t(($) => $['category.bundles'])} {!isZhHans && ( <> {tCommon(($) => $['operation.in'])} {t(($) => $['marketplace.difyMarketplace'])} )}

) } const defaultMarketplaceNav = (
{tCommon(($) => $['mainNav.marketplace'])}
$['placeholder.search'])} showTags={false} usedInMarketplace={false} />
) return (
{marketplaceNav ?? defaultMarketplaceNav}

{t(($) => $['marketplace.pluginsHeroTitle'])}

{t(($) => $['marketplace.pluginsHeroSubtitle'])}

) } export default Description