From 425f624de589e9d6ae389a360ebc24c8065725d4 Mon Sep 17 00:00:00 2001 From: Yi Date: Fri, 18 Oct 2024 14:02:40 +0800 Subject: [PATCH] chore: add plugin panel --- .../marketplace/search-box/tags-filter.tsx | 2 +- .../components/plugins/plugin-item/index.tsx | 52 +++++-- .../filter-management/category-filter.tsx | 136 ++++++++++++++++++ .../plugin-page/filter-management/constant.ts | 11 ++ .../plugin-page/filter-management/index.tsx | 47 ++++++ .../filter-management/search-box.tsx | 26 ++++ .../plugin-page/filter-management/store.ts | 27 ++++ .../filter-management/tag-filter.tsx | 128 +++++++++++++++++ .../plugins/plugin-page/list/index.tsx | 8 +- .../plugins/plugin-page/plugins-panel.tsx | 16 ++- 10 files changed, 433 insertions(+), 20 deletions(-) create mode 100644 web/app/components/plugins/plugin-page/filter-management/category-filter.tsx create mode 100644 web/app/components/plugins/plugin-page/filter-management/constant.ts create mode 100644 web/app/components/plugins/plugin-page/filter-management/index.tsx create mode 100644 web/app/components/plugins/plugin-page/filter-management/search-box.tsx create mode 100644 web/app/components/plugins/plugin-page/filter-management/store.ts create mode 100644 web/app/components/plugins/plugin-page/filter-management/tag-filter.tsx diff --git a/web/app/components/plugins/marketplace/search-box/tags-filter.tsx b/web/app/components/plugins/marketplace/search-box/tags-filter.tsx index 323c4be1ab..30337567b3 100644 --- a/web/app/components/plugins/marketplace/search-box/tags-filter.tsx +++ b/web/app/components/plugins/marketplace/search-box/tags-filter.tsx @@ -111,11 +111,11 @@ const TagsFilter = ({
handleCheck(option.value)} > handleCheck(option.value)} />
{option.text} diff --git a/web/app/components/plugins/plugin-item/index.tsx b/web/app/components/plugins/plugin-item/index.tsx index 08e4bf1419..739b5a6ebc 100644 --- a/web/app/components/plugins/plugin-item/index.tsx +++ b/web/app/components/plugins/plugin-item/index.tsx @@ -2,7 +2,7 @@ import type { FC } from 'react' import React from 'react' import { useContext } from 'use-context-selector' -import { RiArrowRightUpLine, RiLoginCircleLine, RiVerifiedBadgeLine } from '@remixicon/react' +import { RiArrowRightUpLine, RiBugLine, RiHardDrive3Line, RiLoginCircleLine, RiVerifiedBadgeLine } from '@remixicon/react' import { useTranslation } from 'react-i18next' import { Github } from '../../base/icons/src/public/common' import Badge from '../../base/badge' @@ -19,12 +19,14 @@ import I18n from '@/context/i18n' type Props = { className?: string payload: Plugin + source: 'github' | 'marketplace' | 'local' | 'debug' onDelete: () => void } const PluginItem: FC = ({ className, payload, + source, onDelete, }) => { const { locale } = useContext(I18n) @@ -34,7 +36,11 @@ const PluginItem: FC = ({ const hasNewVersion = payload.latest_version !== payload.version return ( -
+
{/* Header */} @@ -77,12 +83,42 @@ const PluginItem: FC = ({
- {t('plugin.from')} -
- -
GitHub
- -
+ {source === 'github' + && <> + +
{t('plugin.from')}
+
+ +
GitHub
+ +
+
+ + } + {source === 'marketplace' + && <> + +
{t('plugin.from')} marketplace
+ +
+ + } + {source === 'local' + && <> +
+ +
Local Plugin
+
+ + } + {source === 'debug' + && <> +
+ +
Debugging Plugin
+
+ + }
diff --git a/web/app/components/plugins/plugin-page/filter-management/category-filter.tsx b/web/app/components/plugins/plugin-page/filter-management/category-filter.tsx new file mode 100644 index 0000000000..b7a60a7e43 --- /dev/null +++ b/web/app/components/plugins/plugin-page/filter-management/category-filter.tsx @@ -0,0 +1,136 @@ +'use client' + +import { useState } from 'react' +import { + RiArrowDownSLine, + RiCloseCircleFill, +} from '@remixicon/react' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import Checkbox from '@/app/components/base/checkbox' +import cn from '@/utils/classnames' +import Input from '@/app/components/base/input' + +type CategoriesFilterProps = { + value: string[] + onChange: (categories: string[]) => void +} +const CategoriesFilter = ({ + value, + onChange, +}: CategoriesFilterProps) => { + const [open, setOpen] = useState(false) + const [searchText, setSearchText] = useState('') + const options = [ + { + value: 'model', + text: 'Model', + }, + { + value: 'tool', + text: 'Tool', + }, + { + value: 'extension', + text: 'Extension', + }, + { + value: 'bundle', + text: 'Bundle', + }, + ] + 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)) + else + onChange([...value, id]) + } + const selectedTagsLength = value.length + + return ( + + setOpen(v => !v)}> +
+
+ { + !selectedTagsLength && 'All Categories' + } + { + !!selectedTagsLength && value.slice(0, 2).join(',') + } + { + selectedTagsLength > 2 && ( +
+ +{selectedTagsLength - 2} +
+ ) + } +
+ { + !!selectedTagsLength && ( + onChange([])} + /> + ) + } + { + !selectedTagsLength && ( + + ) + } +
+
+ +
+
+ setSearchText(e.target.value)} + placeholder='Search categories' + /> +
+
+ { + filteredOptions.map(option => ( +
handleCheck(option.value)} + > + +
+ {option.text} +
+
+ )) + } +
+
+
+
+ ) +} + +export default CategoriesFilter diff --git a/web/app/components/plugins/plugin-page/filter-management/constant.ts b/web/app/components/plugins/plugin-page/filter-management/constant.ts new file mode 100644 index 0000000000..80f786230c --- /dev/null +++ b/web/app/components/plugins/plugin-page/filter-management/constant.ts @@ -0,0 +1,11 @@ +export type Tag = { + id: string + name: string + type: string + binding_count: number +} + +export type Category = { + name: 'model' | 'tool' | 'extension' | 'bundle' + binding_count: number +} diff --git a/web/app/components/plugins/plugin-page/filter-management/index.tsx b/web/app/components/plugins/plugin-page/filter-management/index.tsx new file mode 100644 index 0000000000..1b09f4875e --- /dev/null +++ b/web/app/components/plugins/plugin-page/filter-management/index.tsx @@ -0,0 +1,47 @@ +import React, { useState } from 'react' +import CategoriesFilter from './category-filter' +import TagFilter from './tag-filter' +import SearchBox from './search-box' + +export type FilterState = { + categories: string[] + tags: string[] + searchQuery: string +} + +type FilterManagementProps = { + onFilterChange: (filters: FilterState) => void +} + +const FilterManagement: React.FC = ({ onFilterChange }) => { + const [filters, setFilters] = useState({ + categories: [], + tags: [], + searchQuery: '', + }) + + const updateFilters = (newFilters: Partial) => { + const updatedFilters = { ...filters, ...newFilters } + setFilters(updatedFilters) + onFilterChange(updatedFilters) + } + + return ( +
+ updateFilters({ categories })} + /> + updateFilters({ tags })} + /> + updateFilters({ searchQuery })} + /> +
+ ) +} + +export default FilterManagement diff --git a/web/app/components/plugins/plugin-page/filter-management/search-box.tsx b/web/app/components/plugins/plugin-page/filter-management/search-box.tsx new file mode 100644 index 0000000000..fa158aadf0 --- /dev/null +++ b/web/app/components/plugins/plugin-page/filter-management/search-box.tsx @@ -0,0 +1,26 @@ +'use client' + +import Input from '@/app/components/base/input' +type SearchBoxProps = { + searchQuery: string + onChange: (query: string) => void +} + +const SearchBox: React.FC = ({ + searchQuery, + onChange, +}) => { + return ( + { + onChange(e.target.value) + }} + /> + ) +} + +export default SearchBox diff --git a/web/app/components/plugins/plugin-page/filter-management/store.ts b/web/app/components/plugins/plugin-page/filter-management/store.ts new file mode 100644 index 0000000000..4b55bf2681 --- /dev/null +++ b/web/app/components/plugins/plugin-page/filter-management/store.ts @@ -0,0 +1,27 @@ +import { create } from 'zustand' +import type { Category, Tag } from './constant' + +type State = { + tagList: Tag[] + categoryList: Category[] + showTagManagementModal: boolean + showCategoryManagementModal: boolean +} + +type Action = { + setTagList: (tagList?: Tag[]) => void + setCategoryList: (categoryList?: Category[]) => void + setShowTagManagementModal: (showTagManagementModal: boolean) => void + setShowCategoryManagementModal: (showCategoryManagementModal: boolean) => void +} + +export const useStore = create(set => ({ + tagList: [], + categoryList: [], + setTagList: tagList => set(() => ({ tagList })), + setCategoryList: categoryList => set(() => ({ categoryList })), + showTagManagementModal: false, + showCategoryManagementModal: false, + setShowTagManagementModal: showTagManagementModal => set(() => ({ showTagManagementModal })), + setShowCategoryManagementModal: showCategoryManagementModal => set(() => ({ showCategoryManagementModal })), +})) diff --git a/web/app/components/plugins/plugin-page/filter-management/tag-filter.tsx b/web/app/components/plugins/plugin-page/filter-management/tag-filter.tsx new file mode 100644 index 0000000000..d337f17495 --- /dev/null +++ b/web/app/components/plugins/plugin-page/filter-management/tag-filter.tsx @@ -0,0 +1,128 @@ +'use client' + +import { useState } from 'react' +import { + RiArrowDownSLine, + RiCloseCircleFill, +} from '@remixicon/react' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +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 [open, setOpen] = useState(false) + const [searchText, setSearchText] = useState('') + const options = [ + { + value: 'search', + text: 'Search', + }, + { + value: 'image', + text: 'Image', + }, + ] + 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)) + else + onChange([...value, id]) + } + const selectedTagsLength = value.length + + return ( + + setOpen(v => !v)}> +
+
+ { + !selectedTagsLength && 'All Tags' + } + { + !!selectedTagsLength && value.slice(0, 2).join(',') + } + { + selectedTagsLength > 2 && ( +
+ +{selectedTagsLength - 2} +
+ ) + } +
+ { + !!selectedTagsLength && ( + onChange([])} + /> + ) + } + { + !selectedTagsLength && ( + + ) + } +
+
+ +
+
+ setSearchText(e.target.value)} + placeholder='Search tags' + /> +
+
+ { + filteredOptions.map(option => ( +
handleCheck(option.value)} + > + +
+ {option.text} +
+
+ )) + } +
+
+
+
+ ) +} + +export default TagsFilter diff --git a/web/app/components/plugins/plugin-page/list/index.tsx b/web/app/components/plugins/plugin-page/list/index.tsx index 92b2a06805..0939c36ca7 100644 --- a/web/app/components/plugins/plugin-page/list/index.tsx +++ b/web/app/components/plugins/plugin-page/list/index.tsx @@ -1,12 +1,7 @@ -import { useTranslation } from 'react-i18next' -import { useContext } from 'use-context-selector' import PluginItem from '../../plugin-item' import { customTool, extensionDallE, modelGPT4, toolNotion } from '@/app/components/plugins/card/card-mock' -import I18n from '@/context/i18n' const PluginList = () => { - const { locale } = useContext(I18n) - const { t } = useTranslation() const pluginList = [toolNotion, extensionDallE, modelGPT4, customTool] return ( @@ -18,8 +13,7 @@ const PluginList = () => { key={index} payload={plugin as any} onDelete={() => {}} - pluginI8n={t} - locale={locale} + source={'debug'} /> ))}
diff --git a/web/app/components/plugins/plugin-page/plugins-panel.tsx b/web/app/components/plugins/plugin-page/plugins-panel.tsx index 81385bcb57..da36e6c424 100644 --- a/web/app/components/plugins/plugin-page/plugins-panel.tsx +++ b/web/app/components/plugins/plugin-page/plugins-panel.tsx @@ -1,18 +1,26 @@ 'use client' +import type { FilterState } from './filter-management' +import FilterManagement from './filter-management' import List from './list' const PluginsPanel = () => { + const handleFilterChange = (filters: FilterState) => { + // + } + return ( <>
-
- {/* Filters go here */} -
+
- +
+ +
)