mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-14 06:25:58 +08:00
plugin detail panel header
This commit is contained in:
parent
ecd2a1be9f
commit
99f5fea001
@ -0,0 +1,9 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icon">
|
||||
<path id="Vector" fill-rule="evenodd" clip-rule="evenodd" d="M11.3891 2.41987C11.6635 2.58871 11.749 2.94802 11.5802 3.22239L10.3324 5.25H11.0833C11.4055 5.25 11.6667 5.51117 11.6667 5.83334V11.6667C11.6667 12.311 11.1444 12.8333 10.5 12.8333H3.50001C2.85568 12.8333 2.33334 12.311 2.33334 11.6667V5.83334C2.33334 5.51117 2.59451 5.25 2.91668 5.25H8.96252L10.5865 2.61094C10.7554 2.33657 11.1147 2.25102 11.3891 2.41987ZM5.83334 7.58334C5.51118 7.58334 5.25001 7.84449 5.25001 8.16667C5.25001 8.48884 5.51118 8.75 5.83334 8.75H8.16668C8.48885 8.75 8.75001 8.48884 8.75001 8.16667C8.75001 7.84449 8.48885 7.58334 8.16668 7.58334H5.83334Z" fill="#676F83"/>
|
||||
<g id="Vector_2" opacity="0.5">
|
||||
<path d="M6.91257 1.79347C6.96898 1.76525 7.01477 1.71948 7.043 1.66303L7.32195 1.10508C7.42946 0.890105 7.73623 0.890105 7.84374 1.10508L8.12269 1.66303C8.15093 1.71948 8.19672 1.76525 8.25313 1.79347L8.81108 2.07245C9.0261 2.17994 9.0261 2.48672 8.81108 2.5942L8.25313 2.87318C8.19672 2.9014 8.15093 2.94717 8.12269 3.00362L7.84374 3.56158C7.73623 3.77655 7.42946 3.77655 7.32195 3.56158L7.043 3.00362C7.01477 2.94717 6.96898 2.9014 6.91257 2.87318L6.35461 2.5942C6.13965 2.48672 6.13965 2.17994 6.35461 2.07245L6.91257 1.79347Z" fill="#676F83"/>
|
||||
<path d="M3.80145 2.7657C3.85789 2.73748 3.90366 2.69171 3.93189 2.63526L4.11364 2.27174C4.22113 2.05677 4.5279 2.05677 4.63539 2.27174L4.81715 2.63526C4.84537 2.6917 4.89114 2.73748 4.94759 2.7657L5.3111 2.94745C5.52607 3.05494 5.52607 3.36172 5.3111 3.4692L4.94759 3.65096C4.89114 3.67919 4.84537 3.72495 4.81715 3.7814L4.63539 4.14491C4.5279 4.35988 4.22113 4.35988 4.11364 4.14491L3.93189 3.7814C3.90366 3.72495 3.85789 3.67919 3.80145 3.65096L3.43793 3.4692C3.22296 3.36172 3.22296 3.05494 3.43793 2.94745L3.80145 2.7657Z" fill="#676F83"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,66 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "14",
|
||||
"height": "14",
|
||||
"viewBox": "0 0 14 14",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Icon"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"id": "Vector",
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M11.3891 2.41987C11.6635 2.58871 11.749 2.94802 11.5802 3.22239L10.3324 5.25H11.0833C11.4055 5.25 11.6667 5.51117 11.6667 5.83334V11.6667C11.6667 12.311 11.1444 12.8333 10.5 12.8333H3.50001C2.85568 12.8333 2.33334 12.311 2.33334 11.6667V5.83334C2.33334 5.51117 2.59451 5.25 2.91668 5.25H8.96252L10.5865 2.61094C10.7554 2.33657 11.1147 2.25102 11.3891 2.41987ZM5.83334 7.58334C5.51118 7.58334 5.25001 7.84449 5.25001 8.16667C5.25001 8.48884 5.51118 8.75 5.83334 8.75H8.16668C8.48885 8.75 8.75001 8.48884 8.75001 8.16667C8.75001 7.84449 8.48885 7.58334 8.16668 7.58334H5.83334Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"id": "Vector_2",
|
||||
"opacity": "0.5"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M6.91257 1.79347C6.96898 1.76525 7.01477 1.71948 7.043 1.66303L7.32195 1.10508C7.42946 0.890105 7.73623 0.890105 7.84374 1.10508L8.12269 1.66303C8.15093 1.71948 8.19672 1.76525 8.25313 1.79347L8.81108 2.07245C9.0261 2.17994 9.0261 2.48672 8.81108 2.5942L8.25313 2.87318C8.19672 2.9014 8.15093 2.94717 8.12269 3.00362L7.84374 3.56158C7.73623 3.77655 7.42946 3.77655 7.32195 3.56158L7.043 3.00362C7.01477 2.94717 6.96898 2.9014 6.91257 2.87318L6.35461 2.5942C6.13965 2.48672 6.13965 2.17994 6.35461 2.07245L6.91257 1.79347Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M3.80145 2.7657C3.85789 2.73748 3.90366 2.69171 3.93189 2.63526L4.11364 2.27174C4.22113 2.05677 4.5279 2.05677 4.63539 2.27174L4.81715 2.63526C4.84537 2.6917 4.89114 2.73748 4.94759 2.7657L5.3111 2.94745C5.52607 3.05494 5.52607 3.36172 5.3111 3.4692L4.94759 3.65096C4.89114 3.67919 4.84537 3.72495 4.81715 3.7814L4.63539 4.14491C4.5279 4.35988 4.22113 4.35988 4.11364 4.14491L3.93189 3.7814C3.90366 3.72495 3.85789 3.67919 3.80145 3.65096L3.43793 3.4692C3.22296 3.36172 3.22296 3.05494 3.43793 2.94745L3.80145 2.7657Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "BoxSparkleFill"
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './BoxSparkleFill.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
|
||||
props,
|
||||
ref,
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />)
|
||||
|
||||
Icon.displayName = 'BoxSparkleFill'
|
||||
|
||||
export default Icon
|
@ -1 +1,2 @@
|
||||
export { default as BoxSparkleFill } from './BoxSparkleFill'
|
||||
export { default as LeftCorner } from './LeftCorner'
|
||||
|
@ -1,16 +1,20 @@
|
||||
'use client'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { RiVerifiedBadgeLine } from '@remixicon/react'
|
||||
import Badge from '../../base/badge'
|
||||
import { RiCloseLine, RiVerifiedBadgeLine } from '@remixicon/react'
|
||||
import type { Plugin } from '../types'
|
||||
import Badge from '../../base/badge'
|
||||
import Description from '../card/base/description'
|
||||
import Icon from '../card/base/card-icon'
|
||||
import Title from '../card/base/title'
|
||||
import DownloadCount from '../card/base/download-count'
|
||||
import OperationDropdown from './operation-dropdown'
|
||||
import type { Locale } from '@/i18n'
|
||||
import { fetchPluginDetail } from '@/app/(commonLayout)/plugins/test/card/actions'
|
||||
import { BoxSparkleFill } from '@/app/components/base/icons/src/vender/plugin'
|
||||
import Button from '@/app/components/base/button'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import cn from '@/utils/classnames'
|
||||
@ -27,6 +31,7 @@ type Props = {
|
||||
const PluginDetailPanel: FC<Props> = ({
|
||||
locale,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const searchParams = useSearchParams()
|
||||
const pluginID = searchParams.get('pluginID')
|
||||
const router = useRouter()
|
||||
@ -34,6 +39,12 @@ const PluginDetailPanel: FC<Props> = ({
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [pluginDetail, setPluginDetail] = useState<Plugin>()
|
||||
|
||||
const hasNewVersion = useMemo(() => {
|
||||
if (!pluginDetail)
|
||||
return false
|
||||
return pluginDetail.latest_version !== pluginDetail.version
|
||||
}, [pluginDetail])
|
||||
|
||||
const getPluginDetail = async (pluginID: string) => {
|
||||
setLoading(true)
|
||||
const detail = await fetchPluginDetail(pluginID)
|
||||
@ -49,6 +60,8 @@ const PluginDetailPanel: FC<Props> = ({
|
||||
router.replace(pathname)
|
||||
}
|
||||
|
||||
const handleUpdate = () => {}
|
||||
|
||||
useEffect(() => {
|
||||
if (pluginID)
|
||||
getPluginDetail(pluginID)
|
||||
@ -65,40 +78,43 @@ const PluginDetailPanel: FC<Props> = ({
|
||||
footer={null}
|
||||
mask={false}
|
||||
positionCenter={false}
|
||||
panelClassname={cn('mt-[65px] !w-[405px] !max-w-[405px]')}
|
||||
panelClassname={cn('mt-[64px] mr-2 mb-2 !w-[420px] !max-w-[420px] !p-0 !bg-components-panel-bg rounded-2xl border-[0.5px] border-components-panel-border shadow-xl')}
|
||||
>
|
||||
{loading && <Loading type='area' />}
|
||||
{!loading && pluginDetail && (
|
||||
<div
|
||||
className={cn('w-full flex flex-col bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl')}
|
||||
style={{
|
||||
height: 'calc(100vh - 65px)',
|
||||
}}
|
||||
>
|
||||
<div className={cn('group 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')}>
|
||||
{/* Header */}
|
||||
<div className={cn('w-full flex flex-col')}>
|
||||
<div className={cn('shrink-0 p-4 pb-3 border-b border-divider-subtle bg-components-panel-bg')}>
|
||||
<div className="flex">
|
||||
<Icon src={pluginDetail.icon} />
|
||||
<div className="ml-3 w-0 grow">
|
||||
<div className="flex items-center h-5">
|
||||
<Title title={pluginDetail.label[locale]} />
|
||||
<RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />
|
||||
<Badge
|
||||
className='mx-1'
|
||||
text={pluginDetail.version}
|
||||
hasRedCornerMark={hasNewVersion}
|
||||
/>
|
||||
{hasNewVersion && (
|
||||
<Button variant='secondary-accent' size='small' className='!h-5' onClick={handleUpdate}>{t('plugin.detailPanel.operation.update')}</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className='mb-1 flex justify-between items-center h-4'>
|
||||
<div className='flex items-center'>
|
||||
<div className='text-text-tertiary system-xs-regular'>{pluginDetail.org}</div>
|
||||
<div className='mx-2 text-text-quaternary system-xs-regular'>·</div>
|
||||
<DownloadCount downloadCount={pluginDetail.install_count || 0} />
|
||||
<BoxSparkleFill className='w-3.5 h-3.5 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-1'>
|
||||
<OperationDropdown />
|
||||
<ActionButton onClick={handleClose}>
|
||||
<RiCloseLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
<Description className='mt-3' text={pluginDetail.brief[locale]} descriptionLineRows={2}></Description>
|
||||
<div className='mt-3 flex space-x-0.5'>
|
||||
{['LLM', 'text embedding', 'speech2text'].map(tag => (
|
||||
<Badge key={tag} text={tag} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -0,0 +1,61 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiArrowRightUpLine, RiMoreFill } from '@remixicon/react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
// import Button from '@/app/components/base/button'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
}
|
||||
|
||||
const OperationDropdown: FC<Props> = () => {
|
||||
const { t } = useTranslation()
|
||||
const [open, doSetOpen] = useState(false)
|
||||
const openRef = useRef(open)
|
||||
const setOpen = useCallback((v: boolean) => {
|
||||
doSetOpen(v)
|
||||
openRef.current = v
|
||||
}, [doSetOpen])
|
||||
|
||||
const handleTrigger = useCallback(() => {
|
||||
setOpen(!openRef.current)
|
||||
}, [setOpen])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={{
|
||||
mainAxis: -12,
|
||||
crossAxis: 36,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
<ActionButton className={cn(open && 'bg-state-base-hover')}>
|
||||
<RiMoreFill className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-50'>
|
||||
<div className='w-[160px] p-1 bg-components-panel-bg-blur rounded-xl border-[0.5px] border-components-panel-border shadow-lg'>
|
||||
<div className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>{t('plugin.detailPanel.operation.info')}</div>
|
||||
<div className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>{t('plugin.detailPanel.operation.checkUpdate')}</div>
|
||||
<div className='flex items-center px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>
|
||||
<div className='grow'>{t('plugin.detailPanel.operation.viewDetail')}</div>
|
||||
<RiArrowRightUpLine className='shrink-0 w-3.5 h-3.5 text-text-tertiary' />
|
||||
</div>
|
||||
<div className='my-1 h-px bg-divider-subtle'></div>
|
||||
<div className='px-3 py-1.5 rounded-lg text-text-secondary system-md-regular cursor-pointer hover:bg-state-base-hover'>{t('plugin.detailPanel.operation.remove')}</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
export default React.memo(OperationDropdown)
|
@ -1,6 +1,15 @@
|
||||
const translation = {
|
||||
from: 'From',
|
||||
endpointsEnabled: '{{num}} sets of endpoints enabled',
|
||||
detailPanel: {
|
||||
operation: {
|
||||
update: 'Update',
|
||||
info: 'Plugin Info',
|
||||
checkUpdate: 'Check Update',
|
||||
viewDetail: 'View Detail',
|
||||
remove: 'Remove',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
@ -1,6 +1,15 @@
|
||||
const translation = {
|
||||
from: '来自',
|
||||
endpointsEnabled: '{{num}} 组端点已启用',
|
||||
detailPanel: {
|
||||
operation: {
|
||||
update: '更新',
|
||||
info: '插件信息',
|
||||
checkUpdate: '检查更新',
|
||||
viewDetail: '查看详情',
|
||||
remove: '移除',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default translation
|
||||
|
Loading…
x
Reference in New Issue
Block a user