mirror of
https://github.com/langgenius/dify.git
synced 2024-11-16 11:42:29 +08:00
feat: marketplace list
This commit is contained in:
parent
ca9e23d6ea
commit
9a65c3391b
|
@ -2,21 +2,42 @@
|
|||
|
||||
import type { ReactNode } from 'react'
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
createContext,
|
||||
useContextSelector,
|
||||
} from 'use-context-selector'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { PLUGIN_TYPE_SEARCH_MAP } from './plugin-type-switch'
|
||||
import type { Plugin } from '../types'
|
||||
import type { PluginsSearchParams } from './types'
|
||||
|
||||
export type MarketplaceContextValue = {
|
||||
intersected: boolean
|
||||
setIntersected: (intersected: boolean) => void
|
||||
searchPluginText: string
|
||||
handleSearchPluginTextChange: (text: string) => void
|
||||
filterPluginTags: string[]
|
||||
handleFilterPluginTagsChange: (tags: string[]) => void
|
||||
activePluginType: string
|
||||
handleActivePluginTypeChange: (type: string) => void
|
||||
plugins?: Plugin[]
|
||||
setPlugins?: (plugins: Plugin[]) => void
|
||||
}
|
||||
|
||||
export const MarketplaceContext = createContext<MarketplaceContextValue>({
|
||||
intersected: true,
|
||||
setIntersected: () => {},
|
||||
searchPluginText: '',
|
||||
handleSearchPluginTextChange: () => {},
|
||||
filterPluginTags: [],
|
||||
handleFilterPluginTagsChange: () => {},
|
||||
activePluginType: PLUGIN_TYPE_SEARCH_MAP.all,
|
||||
handleActivePluginTypeChange: () => {},
|
||||
plugins: undefined,
|
||||
setPlugins: () => {},
|
||||
})
|
||||
|
||||
type MarketplaceContextProviderProps = {
|
||||
|
@ -31,12 +52,69 @@ export const MarketplaceContextProvider = ({
|
|||
children,
|
||||
}: MarketplaceContextProviderProps) => {
|
||||
const [intersected, setIntersected] = useState(true)
|
||||
const [searchPluginText, setSearchPluginText] = useState('')
|
||||
const [filterPluginTags, setFilterPluginTags] = useState<string[]>([])
|
||||
const [activePluginType, setActivePluginType] = useState(PLUGIN_TYPE_SEARCH_MAP.all)
|
||||
const [plugins, setPlugins] = useState<Plugin[]>()
|
||||
|
||||
const handleUpdatePlugins = useCallback((query: PluginsSearchParams) => {
|
||||
const fetchPlugins = async () => {
|
||||
const response = await fetch(
|
||||
'https://marketplace.dify.dev/api/v1/plugins/search/basic',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: query.query,
|
||||
page: 1,
|
||||
page_size: 10,
|
||||
sort_by: query.sortBy,
|
||||
sort_order: query.sortOrder,
|
||||
category: query.category,
|
||||
tag: query.tag,
|
||||
}),
|
||||
},
|
||||
)
|
||||
const data = await response.json()
|
||||
setPlugins(data.data.plugins)
|
||||
}
|
||||
|
||||
fetchPlugins()
|
||||
}, [])
|
||||
|
||||
const { run: handleUpdatePluginsWithDebounced } = useDebounceFn(handleUpdatePlugins, {
|
||||
wait: 500,
|
||||
})
|
||||
|
||||
const handleSearchPluginTextChange = useCallback((text: string) => {
|
||||
setSearchPluginText(text)
|
||||
|
||||
handleUpdatePluginsWithDebounced({ query: text })
|
||||
}, [handleUpdatePluginsWithDebounced])
|
||||
|
||||
const handleFilterPluginTagsChange = useCallback((tags: string[]) => {
|
||||
setFilterPluginTags(tags)
|
||||
}, [])
|
||||
|
||||
const handleActivePluginTypeChange = useCallback((type: string) => {
|
||||
setActivePluginType(type)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<MarketplaceContext.Provider
|
||||
value={{
|
||||
intersected,
|
||||
setIntersected,
|
||||
searchPluginText,
|
||||
handleSearchPluginTextChange,
|
||||
filterPluginTags,
|
||||
handleFilterPluginTagsChange,
|
||||
activePluginType,
|
||||
handleActivePluginTypeChange,
|
||||
plugins,
|
||||
setPlugins,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -4,7 +4,7 @@ import type { Plugin } from '@/app/components/plugins/types'
|
|||
import Card from '@/app/components/plugins/card'
|
||||
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
|
||||
|
||||
interface ListWithCollectionProps {
|
||||
type ListWithCollectionProps = {
|
||||
marketplaceCollections: MarketplaceCollection[]
|
||||
marketplaceCollectionPluginsMap: Record<string, Plugin[]>
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
'use client'
|
||||
import type { Plugin } from '../../types'
|
||||
import type { MarketplaceCollection } from '../types'
|
||||
import { useMarketplaceContext } from '../context'
|
||||
import List from './index'
|
||||
|
||||
interface ListWrapperProps {
|
||||
type ListWrapperProps = {
|
||||
marketplaceCollections: MarketplaceCollection[]
|
||||
marketplaceCollectionPluginsMap: Record<string, Plugin[]>
|
||||
}
|
||||
|
@ -11,10 +12,13 @@ const ListWrapper = ({
|
|||
marketplaceCollections,
|
||||
marketplaceCollectionPluginsMap,
|
||||
}: ListWrapperProps) => {
|
||||
const plugins = useMarketplaceContext(s => s.plugins)
|
||||
|
||||
return (
|
||||
<List
|
||||
marketplaceCollections={marketplaceCollections}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
|
||||
plugins={plugins}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,25 +1,22 @@
|
|||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { PluginType } from '../types'
|
||||
import {
|
||||
RiArchive2Line,
|
||||
RiBrain2Line,
|
||||
RiHammerLine,
|
||||
RiPuzzle2Line,
|
||||
} from '@remixicon/react'
|
||||
import { PluginType } from '../types'
|
||||
import { useMarketplaceContext } from './context'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const PLUGIN_TYPE_SEARCH_MAP = {
|
||||
export const PLUGIN_TYPE_SEARCH_MAP = {
|
||||
all: 'all',
|
||||
model: PluginType.model,
|
||||
tool: PluginType.tool,
|
||||
extension: PluginType.extension,
|
||||
bundle: 'bundle',
|
||||
}
|
||||
type PluginTypeSwitchProps = {
|
||||
onChange?: (type: string) => void
|
||||
}
|
||||
const options = [
|
||||
{
|
||||
value: PLUGIN_TYPE_SEARCH_MAP.all,
|
||||
|
@ -47,10 +44,9 @@ const options = [
|
|||
icon: <RiArchive2Line className='mr-1.5 w-4 h-4' />,
|
||||
},
|
||||
]
|
||||
const PluginTypeSwitch = ({
|
||||
onChange,
|
||||
}: PluginTypeSwitchProps) => {
|
||||
const [activeType, setActiveType] = useState(PLUGIN_TYPE_SEARCH_MAP.all)
|
||||
const PluginTypeSwitch = () => {
|
||||
const activePluginType = useMarketplaceContext(s => s.activePluginType)
|
||||
const handleActivePluginTypeChange = useMarketplaceContext(s => s.handleActivePluginTypeChange)
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
|
@ -62,11 +58,10 @@ const PluginTypeSwitch = ({
|
|||
key={option.value}
|
||||
className={cn(
|
||||
'flex items-center px-3 h-8 border border-transparent rounded-xl cursor-pointer hover:bg-state-base-hover hover:text-text-secondary system-md-medium text-text-tertiary',
|
||||
activeType === option.value && 'border-components-main-nav-nav-button-border !bg-components-main-nav-nav-button-bg-active !text-components-main-nav-nav-button-text-active shadow-xs',
|
||||
activePluginType === option.value && 'border-components-main-nav-nav-button-border !bg-components-main-nav-nav-button-bg-active !text-components-main-nav-nav-button-text-active shadow-xs',
|
||||
)}
|
||||
onClick={() => {
|
||||
setActiveType(option.value)
|
||||
onChange?.(option.value)
|
||||
handleActivePluginTypeChange(option.value)
|
||||
}}
|
||||
>
|
||||
{option.icon}
|
||||
|
|
|
@ -1,29 +1,14 @@
|
|||
'use client'
|
||||
|
||||
import {
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { useMarketplaceContext } from '../context'
|
||||
import TagsFilter from './tags-filter'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type SearchBoxProps = {
|
||||
onChange?: (searchText: string, tags: string[]) => void
|
||||
}
|
||||
const SearchBox = ({
|
||||
onChange,
|
||||
}: SearchBoxProps) => {
|
||||
const SearchBox = () => {
|
||||
const intersected = useMarketplaceContext(v => v.intersected)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [selectedTags, setSelectedTags] = useState<string[]>([])
|
||||
|
||||
const handleTagsChange = useCallback((tags: string[]) => {
|
||||
setSelectedTags(tags)
|
||||
onChange?.(searchText, tags)
|
||||
}, [searchText, onChange])
|
||||
const searchPluginText = useMarketplaceContext(v => v.searchPluginText)
|
||||
const handleSearchPluginTextChange = useMarketplaceContext(v => v.handleSearchPluginTextChange)
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -32,24 +17,20 @@ const SearchBox = ({
|
|||
!intersected && 'w-[508px] transition-[width] duration-300',
|
||||
)}
|
||||
>
|
||||
<TagsFilter
|
||||
value={selectedTags}
|
||||
onChange={handleTagsChange}
|
||||
/>
|
||||
<TagsFilter />
|
||||
<div className='mx-1 w-[1px] h-3.5 bg-divider-regular'></div>
|
||||
<div className='grow flex items-center p-1 pl-2'>
|
||||
<div className='flex items-center mr-2 py-0.5 w-full'>
|
||||
<input
|
||||
className='grow block outline-none appearance-none body-md-medium text-text-secondary'
|
||||
value={searchText}
|
||||
value={searchPluginText}
|
||||
onChange={(e) => {
|
||||
setSearchText(e.target.value)
|
||||
onChange?.(e.target.value, selectedTags)
|
||||
handleSearchPluginTextChange(e.target.value)
|
||||
}}
|
||||
/>
|
||||
{
|
||||
searchText && (
|
||||
<ActionButton onClick={() => setSearchText('')}>
|
||||
searchPluginText && (
|
||||
<ActionButton onClick={() => handleSearchPluginTextChange('')}>
|
||||
<RiCloseLine className='w-4 h-4' />
|
||||
</ActionButton>
|
||||
)
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
RiCloseCircleFill,
|
||||
RiFilter3Line,
|
||||
} from '@remixicon/react'
|
||||
import { useMarketplaceContext } from '../context'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
|
@ -15,14 +16,9 @@ import Checkbox from '@/app/components/base/checkbox'
|
|||
import cn from '@/utils/classnames'
|
||||
import Input from '@/app/components/base/input'
|
||||
|
||||
type TagsFilterProps = {
|
||||
value: string[]
|
||||
onChange: (tags: string[]) => void
|
||||
}
|
||||
const TagsFilter = ({
|
||||
value,
|
||||
onChange,
|
||||
}: TagsFilterProps) => {
|
||||
const TagsFilter = () => {
|
||||
const filterPluginTags = useMarketplaceContext(v => v.filterPluginTags)
|
||||
const handleFilterPluginTagsChange = useMarketplaceContext(v => v.handleFilterPluginTagsChange)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const options = [
|
||||
|
@ -37,12 +33,12 @@ const TagsFilter = ({
|
|||
]
|
||||
const filteredOptions = options.filter(option => option.text.toLowerCase().includes(searchText.toLowerCase()))
|
||||
const handleCheck = (id: string) => {
|
||||
if (value.includes(id))
|
||||
onChange(value.filter(tag => tag !== id))
|
||||
if (filterPluginTags.includes(id))
|
||||
handleFilterPluginTagsChange(filterPluginTags.filter((tag: string) => tag !== id))
|
||||
else
|
||||
onChange([...value, id])
|
||||
handleFilterPluginTagsChange([...filterPluginTags, id])
|
||||
}
|
||||
const selectedTagsLength = value.length
|
||||
const selectedTagsLength = filterPluginTags.length
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
|
@ -70,7 +66,7 @@ const TagsFilter = ({
|
|||
!selectedTagsLength && 'All Tags'
|
||||
}
|
||||
{
|
||||
!!selectedTagsLength && value.slice(0, 2).join(',')
|
||||
!!selectedTagsLength && filterPluginTags.slice(0, 2).join(',')
|
||||
}
|
||||
{
|
||||
selectedTagsLength > 2 && (
|
||||
|
@ -84,7 +80,7 @@ const TagsFilter = ({
|
|||
!!selectedTagsLength && (
|
||||
<RiCloseCircleFill
|
||||
className='w-4 h-4 text-text-quaternary cursor-pointer'
|
||||
onClick={() => onChange([])}
|
||||
onClick={() => handleFilterPluginTagsChange([])}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -115,7 +111,7 @@ const TagsFilter = ({
|
|||
>
|
||||
<Checkbox
|
||||
className='mr-1'
|
||||
checked={value.includes(option.value)}
|
||||
checked={filterPluginTags.includes(option.value)}
|
||||
/>
|
||||
<div className='px-1 system-sm-medium text-text-secondary'>
|
||||
{option.text}
|
||||
|
|
|
@ -17,3 +17,13 @@ export type MarketplaceCollectionPluginsResponse = {
|
|||
plugins: Plugin[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export type PluginsSearchParams = {
|
||||
query: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
sortBy?: string
|
||||
sortOrder?: string
|
||||
category?: string
|
||||
tag?: string
|
||||
}
|
||||
|
|
|
@ -1,233 +0,0 @@
|
|||
import { RiArrowUpDoubleLine } from '@remixicon/react'
|
||||
import Card from '@/app/components/plugins/card'
|
||||
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
|
||||
import { toolNotion } from '@/app/components/plugins/card/card-mock'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
|
||||
type MarketplaceProps = {
|
||||
onMarketplaceScroll: () => void
|
||||
}
|
||||
const Marketplace = ({
|
||||
onMarketplaceScroll,
|
||||
}: MarketplaceProps) => {
|
||||
const locale = useGetLanguage()
|
||||
|
||||
return (
|
||||
<div className='shrink-0 sticky -bottom-[442px] h-[530px] overflow-y-auto px-12 py-2 pt-0 bg-background-default-subtle'>
|
||||
<RiArrowUpDoubleLine
|
||||
className='absolute top-2 left-1/2 -translate-x-1/2 w-4 h-4 text-text-quaternary cursor-pointer'
|
||||
onClick={() => onMarketplaceScroll()}
|
||||
/>
|
||||
<div className='sticky top-0 pt-5 pb-3 bg-background-default-subtle z-10'>
|
||||
<div className='title-2xl-semi-bold bg-gradient-to-r from-[rgba(11,165,236,0.95)] to-[rgba(21,90,239,0.95)] bg-clip-text text-transparent'>More from Marketplace</div>
|
||||
<div className='flex items-center text-center body-md-regular text-text-tertiary'>
|
||||
Discover
|
||||
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
models
|
||||
</span>
|
||||
,
|
||||
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
tools
|
||||
</span>
|
||||
,
|
||||
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
extensions
|
||||
</span>
|
||||
and
|
||||
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
bundles
|
||||
</span>
|
||||
in Dify Marketplace
|
||||
</div>
|
||||
</div>
|
||||
<div className='py-3'>
|
||||
<div className='title-xl-semi-bold text-text-primary'>Featured</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>Our top picks to get you started</div>
|
||||
<div className='grid grid-cols-4 gap-3 mt-2'>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='py-3'>
|
||||
<div className='title-xl-semi-bold text-text-primary'>Popular</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>Explore the library and discover the incredible work of our community</div>
|
||||
<div className='grid grid-cols-4 gap-3 mt-2'>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
<Card
|
||||
payload={toolNotion as any}
|
||||
footer={
|
||||
<CardMoreInfo downloadCount={1234} tags={['Search', 'Productivity']} />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Marketplace
|
35
web/app/components/tools/marketplace/hooks.ts
Normal file
35
web/app/components/tools/marketplace/hooks.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { MarketplaceCollection } from '@/app/components/plugins/marketplace/types'
|
||||
|
||||
export const useMarketplace = () => {
|
||||
const [marketplaceCollections, setMarketplaceCollections] = useState<MarketplaceCollection[]>([])
|
||||
const [marketplaceCollectionPluginsMap, setMarketplaceCollectionPluginsMap] = useState<Record<string, Plugin[]>>({})
|
||||
const getMarketplaceCollections = useCallback(async () => {
|
||||
const marketplaceCollectionsData = await globalThis.fetch('https://marketplace.dify.dev/api/v1/collections')
|
||||
const marketplaceCollectionsDataJson = await marketplaceCollectionsData.json()
|
||||
const marketplaceCollections = marketplaceCollectionsDataJson.data.collections
|
||||
const marketplaceCollectionPluginsMap = {} as Record<string, Plugin[]>
|
||||
await Promise.all(marketplaceCollections.map(async (collection: MarketplaceCollection) => {
|
||||
const marketplaceCollectionPluginsData = await globalThis.fetch(`https://marketplace.dify.dev/api/v1/collections/${collection.name}/plugins`)
|
||||
const marketplaceCollectionPluginsDataJson = await marketplaceCollectionPluginsData.json()
|
||||
const plugins = marketplaceCollectionPluginsDataJson.data.plugins
|
||||
|
||||
marketplaceCollectionPluginsMap[collection.name] = plugins
|
||||
}))
|
||||
setMarketplaceCollections(marketplaceCollections)
|
||||
setMarketplaceCollectionPluginsMap(marketplaceCollectionPluginsMap)
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
getMarketplaceCollections()
|
||||
}, [getMarketplaceCollections])
|
||||
|
||||
return {
|
||||
marketplaceCollections,
|
||||
marketplaceCollectionPluginsMap,
|
||||
}
|
||||
}
|
48
web/app/components/tools/marketplace/index.tsx
Normal file
48
web/app/components/tools/marketplace/index.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { RiArrowUpDoubleLine } from '@remixicon/react'
|
||||
import { useMarketplace } from './hooks'
|
||||
import List from '@/app/components/plugins/marketplace/list'
|
||||
|
||||
type MarketplaceProps = {
|
||||
onMarketplaceScroll: () => void
|
||||
}
|
||||
const Marketplace = ({
|
||||
onMarketplaceScroll,
|
||||
}: MarketplaceProps) => {
|
||||
const { marketplaceCollections, marketplaceCollectionPluginsMap } = useMarketplace()
|
||||
return (
|
||||
<div className='shrink-0 sticky -bottom-[442px] h-[530px] overflow-y-auto px-12 py-2 pt-0 bg-background-default-subtle'>
|
||||
<RiArrowUpDoubleLine
|
||||
className='absolute top-2 left-1/2 -translate-x-1/2 w-4 h-4 text-text-quaternary cursor-pointer'
|
||||
onClick={() => onMarketplaceScroll()}
|
||||
/>
|
||||
<div className='sticky top-0 pt-5 pb-3 bg-background-default-subtle z-10'>
|
||||
<div className='title-2xl-semi-bold bg-gradient-to-r from-[rgba(11,165,236,0.95)] to-[rgba(21,90,239,0.95)] bg-clip-text text-transparent'>More from Marketplace</div>
|
||||
<div className='flex items-center text-center body-md-regular text-text-tertiary'>
|
||||
Discover
|
||||
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
models
|
||||
</span>
|
||||
,
|
||||
<span className="relative ml-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
tools
|
||||
</span>
|
||||
,
|
||||
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
extensions
|
||||
</span>
|
||||
and
|
||||
<span className="relative ml-1 mr-1 body-md-medium text-text-secondary after:content-[''] after:absolute after:left-0 after:bottom-[1.5px] after:w-full after:h-2 after:bg-text-text-selected">
|
||||
bundles
|
||||
</span>
|
||||
in Dify Marketplace
|
||||
</div>
|
||||
</div>
|
||||
<List
|
||||
marketplaceCollections={marketplaceCollections}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Marketplace
|
Loading…
Reference in New Issue
Block a user