dify
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import React, { type FC } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Theme } from '@/types/app'
|
||||
|
||||
type IconWithTooltipProps = {
|
||||
className?: string
|
||||
popupContent?: string
|
||||
theme: Theme
|
||||
BadgeIconLight: React.ElementType
|
||||
BadgeIconDark: React.ElementType
|
||||
}
|
||||
|
||||
const IconWithTooltip: FC<IconWithTooltipProps> = ({
|
||||
className,
|
||||
theme,
|
||||
popupContent,
|
||||
BadgeIconLight,
|
||||
BadgeIconDark,
|
||||
}) => {
|
||||
const isDark = theme === Theme.dark
|
||||
const iconClassName = cn('h-5 w-5', className)
|
||||
const Icon = isDark ? BadgeIconDark : BadgeIconLight
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
popupClassName='p-1.5 border-[0.5px] border-[0.5px] border-components-panel-border bg-components-tooltip-bg text-text-secondary system-xs-medium'
|
||||
popupContent={popupContent}
|
||||
>
|
||||
<div className='flex shrink-0 items-center justify-center'>
|
||||
<Icon className={iconClassName} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(IconWithTooltip)
|
||||
29
dify/web/app/components/plugins/base/badges/partner.tsx
Normal file
29
dify/web/app/components/plugins/base/badges/partner.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { FC } from 'react'
|
||||
import IconWithTooltip from './icon-with-tooltip'
|
||||
import PartnerDark from '@/app/components/base/icons/src/public/plugins/PartnerDark'
|
||||
import PartnerLight from '@/app/components/base/icons/src/public/plugins/PartnerLight'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
|
||||
type PartnerProps = {
|
||||
className?: string
|
||||
text: string
|
||||
}
|
||||
|
||||
const Partner: FC<PartnerProps> = ({
|
||||
className,
|
||||
text,
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<IconWithTooltip
|
||||
className={className}
|
||||
theme={theme}
|
||||
BadgeIconLight={PartnerLight}
|
||||
BadgeIconDark={PartnerDark}
|
||||
popupContent={text}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Partner
|
||||
29
dify/web/app/components/plugins/base/badges/verified.tsx
Normal file
29
dify/web/app/components/plugins/base/badges/verified.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { FC } from 'react'
|
||||
import IconWithTooltip from './icon-with-tooltip'
|
||||
import VerifiedDark from '@/app/components/base/icons/src/public/plugins/VerifiedDark'
|
||||
import VerifiedLight from '@/app/components/base/icons/src/public/plugins/VerifiedLight'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
|
||||
type VerifiedProps = {
|
||||
className?: string
|
||||
text: string
|
||||
}
|
||||
|
||||
const Verified: FC<VerifiedProps> = ({
|
||||
className,
|
||||
text,
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<IconWithTooltip
|
||||
className={className}
|
||||
theme={theme}
|
||||
BadgeIconLight={VerifiedLight}
|
||||
BadgeIconDark={VerifiedDark}
|
||||
popupContent={text}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Verified
|
||||
105
dify/web/app/components/plugins/base/deprecation-notice.tsx
Normal file
105
dify/web/app/components/plugins/base/deprecation-notice.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import Link from 'next/link'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RiAlertFill } from '@remixicon/react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useMixedTranslation } from '../marketplace/hooks'
|
||||
import { camelCase } from 'lodash-es'
|
||||
|
||||
type DeprecationNoticeProps = {
|
||||
status: 'deleted' | 'active'
|
||||
deprecatedReason: string
|
||||
alternativePluginId: string
|
||||
alternativePluginURL: string
|
||||
locale?: string
|
||||
className?: string
|
||||
innerWrapperClassName?: string
|
||||
iconWrapperClassName?: string
|
||||
textClassName?: string
|
||||
}
|
||||
|
||||
const i18nPrefix = 'plugin.detailPanel.deprecation'
|
||||
|
||||
const DeprecationNotice: FC<DeprecationNoticeProps> = ({
|
||||
status,
|
||||
deprecatedReason,
|
||||
alternativePluginId,
|
||||
alternativePluginURL,
|
||||
locale,
|
||||
className,
|
||||
innerWrapperClassName,
|
||||
iconWrapperClassName,
|
||||
textClassName,
|
||||
}) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
|
||||
const deprecatedReasonKey = useMemo(() => {
|
||||
if (!deprecatedReason) return ''
|
||||
return camelCase(deprecatedReason)
|
||||
}, [deprecatedReason])
|
||||
|
||||
// Check if the deprecatedReasonKey exists in i18n
|
||||
const hasValidDeprecatedReason = useMemo(() => {
|
||||
if (!deprecatedReason || !deprecatedReasonKey) return false
|
||||
|
||||
// Define valid reason keys that exist in i18n
|
||||
const validReasonKeys = ['businessAdjustments', 'ownershipTransferred', 'noMaintainer']
|
||||
return validReasonKeys.includes(deprecatedReasonKey)
|
||||
}, [deprecatedReason, deprecatedReasonKey])
|
||||
|
||||
if (status !== 'deleted')
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className={cn('w-full', className)}>
|
||||
<div className={cn(
|
||||
'relative flex items-start gap-x-0.5 overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-2 shadow-xs shadow-shadow-shadow-3 backdrop-blur-[5px]',
|
||||
innerWrapperClassName,
|
||||
)}>
|
||||
<div className='absolute left-0 top-0 -z-10 h-full w-full bg-toast-warning-bg opacity-40' />
|
||||
<div className={cn('flex size-6 shrink-0 items-center justify-center', iconWrapperClassName)}>
|
||||
<RiAlertFill className='size-4 text-text-warning-secondary' />
|
||||
</div>
|
||||
<div className={cn('system-xs-regular grow py-1 text-text-primary', textClassName)}>
|
||||
{
|
||||
hasValidDeprecatedReason && alternativePluginId && (
|
||||
<Trans
|
||||
t={t}
|
||||
i18nKey={`${i18nPrefix}.fullMessage`}
|
||||
components={{
|
||||
CustomLink: (
|
||||
<Link
|
||||
href={alternativePluginURL}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='underline'
|
||||
/>
|
||||
),
|
||||
}}
|
||||
values={{
|
||||
deprecatedReason: t(`${i18nPrefix}.reason.${deprecatedReasonKey}`),
|
||||
alternativePluginId,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
hasValidDeprecatedReason && !alternativePluginId && (
|
||||
<span>
|
||||
{t(`${i18nPrefix}.onlyReason`, { deprecatedReason: t(`${i18nPrefix}.reason.${deprecatedReasonKey}`) })}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{
|
||||
!hasValidDeprecatedReason && (
|
||||
<span>{t(`${i18nPrefix}.noReason`)}</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(DeprecationNotice)
|
||||
66
dify/web/app/components/plugins/base/key-value-item.tsx
Normal file
66
dify/web/app/components/plugins/base/key-value-item.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import {
|
||||
RiClipboardLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { CopyCheck } from '../../base/icons/src/vender/line/files'
|
||||
import Tooltip from '../../base/tooltip'
|
||||
import cn from '@/utils/classnames'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
|
||||
type Props = {
|
||||
label: string
|
||||
labelWidthClassName?: string
|
||||
value: string
|
||||
maskedValue?: string
|
||||
valueMaxWidthClassName?: string
|
||||
}
|
||||
|
||||
const KeyValueItem: FC<Props> = ({
|
||||
label,
|
||||
labelWidthClassName = 'w-10',
|
||||
value,
|
||||
maskedValue,
|
||||
valueMaxWidthClassName = 'max-w-[162px]',
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const handleCopy = useCallback(() => {
|
||||
copy(value)
|
||||
setIsCopied(true)
|
||||
}, [value])
|
||||
|
||||
useEffect(() => {
|
||||
if (isCopied) {
|
||||
const timer = setTimeout(() => {
|
||||
setIsCopied(false)
|
||||
}, 2000)
|
||||
return () => {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
}, [isCopied])
|
||||
|
||||
const CopyIcon = isCopied ? CopyCheck : RiClipboardLine
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-1'>
|
||||
<span className={cn('system-xs-medium flex flex-col items-start justify-center text-text-tertiary', labelWidthClassName)}>{label}</span>
|
||||
<div className='flex items-center justify-center gap-0.5'>
|
||||
<span className={cn(valueMaxWidthClassName, ' system-xs-medium truncate text-text-secondary')}>
|
||||
{maskedValue || value}
|
||||
</span>
|
||||
<Tooltip popupContent={t(`common.operation.${isCopied ? 'copied' : 'copy'}`)} position='top'>
|
||||
<ActionButton onClick={handleCopy}>
|
||||
<CopyIcon className='h-3.5 w-3.5 shrink-0 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(KeyValueItem)
|
||||
Reference in New Issue
Block a user