Introduce Plugins (#13836)
Signed-off-by: yihong0618 <zouzou0208@gmail.com> Signed-off-by: -LAN- <laipz8200@outlook.com> Signed-off-by: xhe <xw897002528@gmail.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: takatost <takatost@gmail.com> Co-authored-by: kurokobo <kuro664@gmail.com> Co-authored-by: Novice Lee <novicelee@NoviPro.local> Co-authored-by: zxhlyh <jasonapring2015@outlook.com> Co-authored-by: AkaraChen <akarachen@outlook.com> Co-authored-by: Yi <yxiaoisme@gmail.com> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: twwu <twwu@dify.ai> Co-authored-by: Hiroshi Fujita <fujita-h@users.noreply.github.com> Co-authored-by: AkaraChen <85140972+AkaraChen@users.noreply.github.com> Co-authored-by: NFish <douxc512@gmail.com> Co-authored-by: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Co-authored-by: 非法操作 <hjlarry@163.com> Co-authored-by: Novice <857526207@qq.com> Co-authored-by: Hiroki Nagai <82458324+nagaihiroki-git@users.noreply.github.com> Co-authored-by: Gen Sato <52241300+halogen22@users.noreply.github.com> Co-authored-by: eux <euxuuu@gmail.com> Co-authored-by: huangzhuo1949 <167434202+huangzhuo1949@users.noreply.github.com> Co-authored-by: huangzhuo <huangzhuo1@xiaomi.com> Co-authored-by: lotsik <lotsik@mail.ru> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: nite-knite <nkCoding@gmail.com> Co-authored-by: Jyong <76649700+JohnJyong@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: gakkiyomi <gakkiyomi@aliyun.com> Co-authored-by: CN-P5 <heibai2006@gmail.com> Co-authored-by: CN-P5 <heibai2006@qq.com> Co-authored-by: Chuehnone <1897025+chuehnone@users.noreply.github.com> Co-authored-by: yihong <zouzou0208@gmail.com> Co-authored-by: Kevin9703 <51311316+Kevin9703@users.noreply.github.com> Co-authored-by: -LAN- <laipz8200@outlook.com> Co-authored-by: Boris Feld <lothiraldan@gmail.com> Co-authored-by: mbo <himabo@gmail.com> Co-authored-by: mabo <mabo@aeyes.ai> Co-authored-by: Warren Chen <warren.chen830@gmail.com> Co-authored-by: JzoNgKVO <27049666+JzoNgKVO@users.noreply.github.com> Co-authored-by: jiandanfeng <chenjh3@wangsu.com> Co-authored-by: zhu-an <70234959+xhdd123321@users.noreply.github.com> Co-authored-by: zhaoqingyu.1075 <zhaoqingyu.1075@bytedance.com> Co-authored-by: 海狸大師 <86974027+yenslife@users.noreply.github.com> Co-authored-by: Xu Song <xusong.vip@gmail.com> Co-authored-by: rayshaw001 <396301947@163.com> Co-authored-by: Ding Jiatong <dingjiatong@gmail.com> Co-authored-by: Bowen Liang <liangbowen@gf.com.cn> Co-authored-by: JasonVV <jasonwangiii@outlook.com> Co-authored-by: le0zh <newlight@qq.com> Co-authored-by: zhuxinliang <zhuxinliang@didiglobal.com> Co-authored-by: k-zaku <zaku99@outlook.jp> Co-authored-by: luckylhb90 <luckylhb90@gmail.com> Co-authored-by: hobo.l <hobo.l@binance.com> Co-authored-by: jiangbo721 <365065261@qq.com> Co-authored-by: 刘江波 <jiangbo721@163.com> Co-authored-by: Shun Miyazawa <34241526+miya@users.noreply.github.com> Co-authored-by: EricPan <30651140+Egfly@users.noreply.github.com> Co-authored-by: crazywoola <427733928@qq.com> Co-authored-by: sino <sino2322@gmail.com> Co-authored-by: Jhvcc <37662342+Jhvcc@users.noreply.github.com> Co-authored-by: lowell <lowell.hu@zkteco.in> Co-authored-by: Boris Polonsky <BorisPolonsky@users.noreply.github.com> Co-authored-by: Ademílson Tonato <ademilsonft@outlook.com> Co-authored-by: Ademílson Tonato <ademilson.tonato@refurbed.com> Co-authored-by: IWAI, Masaharu <iwaim.sub@gmail.com> Co-authored-by: Yueh-Po Peng (Yabi) <94939112+y10ab1@users.noreply.github.com> Co-authored-by: Jason <ggbbddjm@gmail.com> Co-authored-by: Xin Zhang <sjhpzx@gmail.com> Co-authored-by: yjc980121 <3898524+yjc980121@users.noreply.github.com> Co-authored-by: heyszt <36215648+hieheihei@users.noreply.github.com> Co-authored-by: Abdullah AlOsaimi <osaimiacc@gmail.com> Co-authored-by: Abdullah AlOsaimi <189027247+osaimi@users.noreply.github.com> Co-authored-by: Yingchun Lai <laiyingchun@apache.org> Co-authored-by: Hash Brown <hi@xzd.me> Co-authored-by: zuodongxu <192560071+zuodongxu@users.noreply.github.com> Co-authored-by: Masashi Tomooka <tmokmss@users.noreply.github.com> Co-authored-by: aplio <ryo.091219@gmail.com> Co-authored-by: Obada Khalili <54270856+obadakhalili@users.noreply.github.com> Co-authored-by: Nam Vu <zuzoovn@gmail.com> Co-authored-by: Kei YAMAZAKI <1715090+kei-yamazaki@users.noreply.github.com> Co-authored-by: TechnoHouse <13776377+deephbz@users.noreply.github.com> Co-authored-by: Riddhimaan-Senapati <114703025+Riddhimaan-Senapati@users.noreply.github.com> Co-authored-by: MaFee921 <31881301+2284730142@users.noreply.github.com> Co-authored-by: te-chan <t-nakanome@sakura-is.co.jp> Co-authored-by: HQidea <HQidea@users.noreply.github.com> Co-authored-by: Joshbly <36315710+Joshbly@users.noreply.github.com> Co-authored-by: xhe <xw897002528@gmail.com> Co-authored-by: weiwenyan-dev <154779315+weiwenyan-dev@users.noreply.github.com> Co-authored-by: ex_wenyan.wei <ex_wenyan.wei@tcl.com> Co-authored-by: engchina <12236799+engchina@users.noreply.github.com> Co-authored-by: engchina <atjapan2015@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: 呆萌闷油瓶 <253605712@qq.com> Co-authored-by: Kemal <kemalmeler@outlook.com> Co-authored-by: Lazy_Frog <4590648+lazyFrogLOL@users.noreply.github.com> Co-authored-by: Yi Xiao <54782454+YIXIAO0@users.noreply.github.com> Co-authored-by: Steven sun <98230804+Tuyohai@users.noreply.github.com> Co-authored-by: steven <sunzwj@digitalchina.com> Co-authored-by: Kalo Chin <91766386+fdb02983rhy@users.noreply.github.com> Co-authored-by: Katy Tao <34019945+KatyTao@users.noreply.github.com> Co-authored-by: depy <42985524+h4ckdepy@users.noreply.github.com> Co-authored-by: 胡春东 <gycm520@gmail.com> Co-authored-by: Junjie.M <118170653@qq.com> Co-authored-by: MuYu <mr.muzea@gmail.com> Co-authored-by: Naoki Takashima <39912547+takatea@users.noreply.github.com> Co-authored-by: Summer-Gu <37869445+gubinjie@users.noreply.github.com> Co-authored-by: Fei He <droxer.he@gmail.com> Co-authored-by: ybalbert001 <120714773+ybalbert001@users.noreply.github.com> Co-authored-by: Yuanbo Li <ybalbert@amazon.com> Co-authored-by: douxc <7553076+douxc@users.noreply.github.com> Co-authored-by: liuzhenghua <1090179900@qq.com> Co-authored-by: Wu Jiayang <62842862+Wu-Jiayang@users.noreply.github.com> Co-authored-by: Your Name <you@example.com> Co-authored-by: kimjion <45935338+kimjion@users.noreply.github.com> Co-authored-by: AugNSo <song.tiankai@icloud.com> Co-authored-by: llinvokerl <38915183+llinvokerl@users.noreply.github.com> Co-authored-by: liusurong.lsr <liusurong.lsr@alibaba-inc.com> Co-authored-by: Vasu Negi <vasu-negi@users.noreply.github.com> Co-authored-by: Hundredwz <1808096180@qq.com> Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com>
This commit is contained in:
66
web/app/components/plugins/card/base/card-icon.tsx
Normal file
66
web/app/components/plugins/card/base/card-icon.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { RiCheckLine, RiCloseLine } from '@remixicon/react'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const iconSizeMap = {
|
||||
xs: 'w-4 h-4 text-base',
|
||||
tiny: 'w-6 h-6 text-base',
|
||||
small: 'w-8 h-8',
|
||||
medium: 'w-9 h-9',
|
||||
large: 'w-10 h-10',
|
||||
}
|
||||
const Icon = ({
|
||||
className,
|
||||
src,
|
||||
installed = false,
|
||||
installFailed = false,
|
||||
size = 'large',
|
||||
}: {
|
||||
className?: string
|
||||
src: string | {
|
||||
content: string
|
||||
background: string
|
||||
}
|
||||
installed?: boolean
|
||||
installFailed?: boolean
|
||||
size?: 'xs' | 'tiny' | 'small' | 'medium' | 'large'
|
||||
}) => {
|
||||
const iconClassName = 'flex justify-center items-center gap-2 absolute bottom-[-4px] right-[-4px] w-[18px] h-[18px] rounded-full border-2 border-components-panel-bg'
|
||||
if (typeof src === 'object') {
|
||||
return (
|
||||
<div className={cn('relative', className)}>
|
||||
<AppIcon
|
||||
size={size}
|
||||
iconType={'emoji'}
|
||||
icon={src.content}
|
||||
background={src.background}
|
||||
className='rounded-md'
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('shrink-0 relative rounded-md bg-center bg-no-repeat bg-contain', iconSizeMap[size], className)}
|
||||
style={{
|
||||
backgroundImage: `url(${src})`,
|
||||
}}
|
||||
>
|
||||
{
|
||||
installed
|
||||
&& <div className={cn(iconClassName, 'bg-state-success-solid')}>
|
||||
<RiCheckLine className='w-3 h-3 text-text-primary-on-surface' />
|
||||
</div>
|
||||
}
|
||||
{
|
||||
installFailed
|
||||
&& <div className={cn(iconClassName, 'bg-state-destructive-solid')}>
|
||||
<RiCloseLine className='w-3 h-3 text-text-primary-on-surface' />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Icon
|
12
web/app/components/plugins/card/base/corner-mark.tsx
Normal file
12
web/app/components/plugins/card/base/corner-mark.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { LeftCorner } from '../../../base/icons/src/vender/plugin'
|
||||
|
||||
const CornerMark = ({ text }: { text: string }) => {
|
||||
return (
|
||||
<div className='absolute top-0 right-0 flex pl-[13px] '>
|
||||
<LeftCorner className="text-background-section" />
|
||||
<div className="h-5 leading-5 rounded-tr-xl pr-2 bg-background-section text-text-tertiary system-2xs-medium-uppercase">{text}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CornerMark
|
31
web/app/components/plugins/card/base/description.tsx
Normal file
31
web/app/components/plugins/card/base/description.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { FC } from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
text: string
|
||||
descriptionLineRows: number
|
||||
}
|
||||
|
||||
const Description: FC<Props> = ({
|
||||
className,
|
||||
text,
|
||||
descriptionLineRows,
|
||||
}) => {
|
||||
const lineClassName = useMemo(() => {
|
||||
if (descriptionLineRows === 1)
|
||||
return 'h-4 truncate'
|
||||
else if (descriptionLineRows === 2)
|
||||
return 'h-8 line-clamp-2'
|
||||
else
|
||||
return 'h-12 line-clamp-3'
|
||||
}, [descriptionLineRows])
|
||||
return (
|
||||
<div className={cn('text-text-tertiary system-xs-regular', lineClassName, className)}>
|
||||
{text}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Description
|
19
web/app/components/plugins/card/base/download-count.tsx
Normal file
19
web/app/components/plugins/card/base/download-count.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { RiInstallLine } from '@remixicon/react'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
|
||||
type Props = {
|
||||
downloadCount: number
|
||||
}
|
||||
|
||||
const DownloadCount = ({
|
||||
downloadCount,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className="flex items-center space-x-1 text-text-tertiary">
|
||||
<RiInstallLine className="shrink-0 w-3 h-3" />
|
||||
<div className="system-xs-regular">{formatNumber(downloadCount)}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DownloadCount
|
30
web/app/components/plugins/card/base/org-info.tsx
Normal file
30
web/app/components/plugins/card/base/org-info.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import cn from '@/utils/classnames'
|
||||
type Props = {
|
||||
className?: string
|
||||
orgName?: string
|
||||
packageName: string
|
||||
packageNameClassName?: string
|
||||
}
|
||||
|
||||
const OrgInfo = ({
|
||||
className,
|
||||
orgName,
|
||||
packageName,
|
||||
packageNameClassName,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className={cn('flex items-center h-4 space-x-0.5', className)}>
|
||||
{orgName && (
|
||||
<>
|
||||
<span className='shrink-0 text-text-tertiary system-xs-regular'>{orgName}</span>
|
||||
<span className='shrink-0 text-text-quaternary system-xs-regular'>/</span>
|
||||
</>
|
||||
)}
|
||||
<span className={cn('shrink-0 w-0 grow truncate text-text-tertiary system-xs-regular', packageNameClassName)}>
|
||||
{packageName}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrgInfo
|
51
web/app/components/plugins/card/base/placeholder.tsx
Normal file
51
web/app/components/plugins/card/base/placeholder.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Group } from '../../../base/icons/src/vender/other'
|
||||
import Title from './title'
|
||||
import { SkeletonContainer, SkeletonPoint, SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
wrapClassName: string
|
||||
loadingFileName?: string
|
||||
}
|
||||
|
||||
export const LoadingPlaceholder = ({ className }: { className?: string }) => (
|
||||
<div className={cn('h-2 rounded-sm opacity-20 bg-text-quaternary', className)} />
|
||||
)
|
||||
|
||||
const Placeholder = ({
|
||||
wrapClassName,
|
||||
loadingFileName,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className={wrapClassName}>
|
||||
<SkeletonRow>
|
||||
<div
|
||||
className='flex w-10 h-10 p-1 justify-center items-center gap-2 rounded-[10px]
|
||||
border-[0.5px] border-components-panel-border bg-background-default backdrop-blur-sm'>
|
||||
<div className='flex w-5 h-5 justify-center items-center'>
|
||||
<Group className='text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grow">
|
||||
<SkeletonContainer>
|
||||
<div className="flex items-center h-5">
|
||||
{loadingFileName ? (
|
||||
<Title title={loadingFileName} />
|
||||
) : (
|
||||
<SkeletonRectangle className="w-[260px]" />
|
||||
)}
|
||||
</div>
|
||||
<SkeletonRow className="h-4">
|
||||
<SkeletonRectangle className="w-[41px]" />
|
||||
<SkeletonPoint />
|
||||
<SkeletonRectangle className="w-[180px]" />
|
||||
</SkeletonRow>
|
||||
</SkeletonContainer>
|
||||
</div>
|
||||
</SkeletonRow>
|
||||
<SkeletonRectangle className="mt-3 w-[420px]" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Placeholder
|
13
web/app/components/plugins/card/base/title.tsx
Normal file
13
web/app/components/plugins/card/base/title.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
const Title = ({
|
||||
title,
|
||||
}: {
|
||||
title: string
|
||||
}) => {
|
||||
return (
|
||||
<div className='truncate text-text-secondary system-md-semibold'>
|
||||
{title}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Title
|
36
web/app/components/plugins/card/card-more-info.tsx
Normal file
36
web/app/components/plugins/card/card-more-info.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import DownloadCount from './base/download-count'
|
||||
|
||||
type Props = {
|
||||
downloadCount?: number
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
const CardMoreInfo = ({
|
||||
downloadCount,
|
||||
tags,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className="flex items-center h-5">
|
||||
{downloadCount !== undefined && <DownloadCount downloadCount={downloadCount} />}
|
||||
{downloadCount !== undefined && tags && tags.length > 0 && <div className="mx-2 text-text-quaternary system-xs-regular">·</div>}
|
||||
{tags && tags.length > 0 && (
|
||||
<>
|
||||
<div className="flex flex-wrap space-x-2 h-4 overflow-hidden">
|
||||
{tags.map(tag => (
|
||||
<div
|
||||
key={tag}
|
||||
className="flex space-x-1 system-xs-regular max-w-[120px] overflow-hidden"
|
||||
title={`# ${tag}`}
|
||||
>
|
||||
<span className="text-text-quaternary">#</span>
|
||||
<span className="truncate text-text-tertiary">{tag}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardMoreInfo
|
94
web/app/components/plugins/card/index.tsx
Normal file
94
web/app/components/plugins/card/index.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
'use client'
|
||||
import React from 'react'
|
||||
import { RiVerifiedBadgeLine } from '@remixicon/react'
|
||||
import type { Plugin } from '../types'
|
||||
import Icon from '../card/base/card-icon'
|
||||
import CornerMark from './base/corner-mark'
|
||||
import Title from './base/title'
|
||||
import OrgInfo from './base/org-info'
|
||||
import Description from './base/description'
|
||||
import Placeholder from './base/placeholder'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import { getLanguage } from '@/i18n/language'
|
||||
import { useSingleCategories } from '../hooks'
|
||||
import { renderI18nObject } from '@/hooks/use-i18n'
|
||||
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
|
||||
|
||||
export type Props = {
|
||||
className?: string
|
||||
payload: Plugin
|
||||
titleLeft?: React.ReactNode
|
||||
installed?: boolean
|
||||
installFailed?: boolean
|
||||
hideCornerMark?: boolean
|
||||
descriptionLineRows?: number
|
||||
footer?: React.ReactNode
|
||||
isLoading?: boolean
|
||||
loadingFileName?: string
|
||||
locale?: string
|
||||
}
|
||||
|
||||
const Card = ({
|
||||
className,
|
||||
payload,
|
||||
titleLeft,
|
||||
installed,
|
||||
installFailed,
|
||||
hideCornerMark,
|
||||
descriptionLineRows = 2,
|
||||
footer,
|
||||
isLoading = false,
|
||||
loadingFileName,
|
||||
locale: localeFromProps,
|
||||
}: Props) => {
|
||||
const defaultLocale = useGetLanguage()
|
||||
const locale = localeFromProps ? getLanguage(localeFromProps) : defaultLocale
|
||||
const { t } = useMixedTranslation(localeFromProps)
|
||||
const { categoriesMap } = useSingleCategories(t)
|
||||
const { category, type, name, org, label, brief, icon, verified } = payload
|
||||
const isBundle = !['plugin', 'model', 'tool', 'extension', 'agent-strategy'].includes(type)
|
||||
const cornerMark = isBundle ? categoriesMap.bundle?.label : categoriesMap[category]?.label
|
||||
const getLocalizedText = (obj: Record<string, string> | undefined) =>
|
||||
obj ? renderI18nObject(obj, locale) : ''
|
||||
|
||||
const wrapClassName = cn('relative p-4 pb-3 border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg hover-bg-components-panel-on-panel-item-bg rounded-xl shadow-xs', className)
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Placeholder
|
||||
wrapClassName={wrapClassName}
|
||||
loadingFileName={loadingFileName!}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={wrapClassName}>
|
||||
{!hideCornerMark && <CornerMark text={cornerMark} />}
|
||||
{/* Header */}
|
||||
<div className="flex">
|
||||
<Icon src={icon} installed={installed} installFailed={installFailed} />
|
||||
<div className="ml-3 w-0 grow">
|
||||
<div className="flex items-center h-5">
|
||||
<Title title={getLocalizedText(label)} />
|
||||
{verified && <RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />}
|
||||
{titleLeft} {/* This can be version badge */}
|
||||
</div>
|
||||
<OrgInfo
|
||||
className="mt-0.5"
|
||||
orgName={org}
|
||||
packageName={name}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Description
|
||||
className="mt-3"
|
||||
text={getLocalizedText(brief)}
|
||||
descriptionLineRows={descriptionLineRows}
|
||||
/>
|
||||
{footer && <div>{footer}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(Card)
|
Reference in New Issue
Block a user