From f213c8f393d7196b97dcb5988fee411541aff0ee Mon Sep 17 00:00:00 2001 From: JzoNg Date: Thu, 21 Nov 2024 17:38:05 +0800 Subject: [PATCH 1/4] fix: version switch --- .../plugins/plugin-detail-panel/detail-header.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx index 1037f4670e..767366938f 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx @@ -173,7 +173,7 @@ const DetailHeader = ({ {verified && <RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />} <PluginVersionPicker - disabled={!isFromMarketplace || !hasNewVersion} + disabled={!isFromMarketplace} isShow={isShow} onShowChange={setIsShow} pluginID={plugin_id} @@ -187,13 +187,13 @@ const DetailHeader = ({ className={cn( 'mx-1', isShow && 'bg-state-base-hover', - (isShow || isFromMarketplace) && hasNewVersion && 'hover:bg-state-base-hover', + (isShow || isFromMarketplace) && 'hover:bg-state-base-hover', )} uppercase={false} text={ <> <div>{isFromGitHub ? meta!.version : version}</div> - {isFromMarketplace && hasNewVersion && <RiArrowLeftRightLine className='ml-1 w-3 h-3 text-text-tertiary' />} + {isFromMarketplace && <RiArrowLeftRightLine className='ml-1 w-3 h-3 text-text-tertiary' />} </> } hasRedCornerMark={hasNewVersion} From 78c867b9a3c45f46db45d3218342baa7043be8d6 Mon Sep 17 00:00:00 2001 From: JzoNg <jzongcode@gmail.com> Date: Thu, 21 Nov 2024 18:11:19 +0800 Subject: [PATCH 2/4] use plugin detail for builtin tool --- .../plugins/plugin-detail-panel/index.tsx | 13 +- .../plugins/plugin-page/plugins-panel.tsx | 9 +- web/app/components/tools/provider-list.tsx | 161 ++++++++++-------- web/app/components/tools/types.ts | 1 + 4 files changed, 102 insertions(+), 82 deletions(-) diff --git a/web/app/components/plugins/plugin-detail-panel/index.tsx b/web/app/components/plugins/plugin-detail-panel/index.tsx index c9e4213137..d42304742b 100644 --- a/web/app/components/plugins/plugin-detail-panel/index.tsx +++ b/web/app/components/plugins/plugin-detail-panel/index.tsx @@ -6,26 +6,23 @@ import EndpointList from './endpoint-list' import ActionList from './action-list' import ModelList from './model-list' import Drawer from '@/app/components/base/drawer' -import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context' import type { PluginDetail } from '@/app/components/plugins/types' import cn from '@/utils/classnames' type Props = { detail?: PluginDetail onUpdate: () => void + onHide: () => void } const PluginDetailPanel: FC<Props> = ({ detail, onUpdate, + onHide, }) => { - const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID) - - const handleHide = () => setCurrentPluginID(undefined) - const handleUpdate = (isDelete = false) => { if (isDelete) - handleHide() + onHide() onUpdate() } @@ -36,7 +33,7 @@ const PluginDetailPanel: FC<Props> = ({ <Drawer isOpen={!!detail} clickOutsideNotOpen={false} - onClose={handleHide} + onClose={onHide} footer={null} mask={false} positionCenter={false} @@ -46,7 +43,7 @@ const PluginDetailPanel: FC<Props> = ({ <> <DetailHeader detail={detail} - onHide={handleHide} + onHide={onHide} onUpdate={handleUpdate} /> <div className='grow overflow-y-auto'> diff --git a/web/app/components/plugins/plugin-page/plugins-panel.tsx b/web/app/components/plugins/plugin-page/plugins-panel.tsx index 49153bbb53..76e3ea7eca 100644 --- a/web/app/components/plugins/plugin-page/plugins-panel.tsx +++ b/web/app/components/plugins/plugin-page/plugins-panel.tsx @@ -15,6 +15,7 @@ const PluginsPanel = () => { const { data: pluginList, isLoading: isPluginListLoading } = useInstalledPluginList() const invalidateInstalledPluginList = useInvalidateInstalledPluginList() const currentPluginID = usePluginPageContext(v => v.currentPluginID) + const setCurrentPluginID = usePluginPageContext(v => v.setCurrentPluginID) const { run: handleFilterChange } = useDebounceFn((filters: FilterState) => { setFilters(filters) @@ -37,6 +38,8 @@ const PluginsPanel = () => { return detail }, [currentPluginID, pluginList?.plugins]) + const handleHide = () => setCurrentPluginID(undefined) + return ( <> <div className='flex flex-col pt-1 pb-3 px-12 justify-center items-start gap-3 self-stretch'> @@ -54,7 +57,11 @@ const PluginsPanel = () => { ) : ( <Empty /> )} - <PluginDetailPanel detail={currentPluginDetail} onUpdate={() => invalidateInstalledPluginList()}/> + <PluginDetailPanel + detail={currentPluginDetail} + onUpdate={() => invalidateInstalledPluginList()} + onHide={handleHide} + /> </> ) } diff --git a/web/app/components/tools/provider-list.tsx b/web/app/components/tools/provider-list.tsx index 863d4ed5f7..a6f5accec2 100644 --- a/web/app/components/tools/provider-list.tsx +++ b/web/app/components/tools/provider-list.tsx @@ -14,8 +14,10 @@ import CustomCreateCard from '@/app/components/tools/provider/custom-create-card import WorkflowToolEmpty from '@/app/components/tools/add-tool-modal/empty' import Card from '@/app/components/plugins/card' import CardMoreInfo from '@/app/components/plugins/card/card-more-info' +import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel' import { useSelector as useAppContextSelector } from '@/context/app-context' import { useAllToolProviders } from '@/service/use-tools' +import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins' const ProviderList = () => { const { t } = useTranslation() @@ -52,92 +54,105 @@ const ProviderList = () => { }, [activeTab, tagFilterValue, keywords, collectionList]) const [currentProvider, setCurrentProvider] = useState<Collection | undefined>() + const { data: pluginList } = useInstalledPluginList() + const invalidateInstalledPluginList = useInvalidateInstalledPluginList() + const currentPluginDetail = useMemo(() => { + const detail = pluginList?.plugins.find(plugin => plugin.plugin_id === currentProvider?.plugin_id) + return detail + }, [currentProvider?.plugin_id, pluginList?.plugins]) return ( - <div className='relative flex overflow-hidden bg-gray-100 shrink-0 h-0 grow'> - <div - ref={containerRef} - className='relative flex flex-col overflow-y-auto bg-gray-100 grow' - > - <div className={cn( - 'sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-gray-100 z-20 flex-wrap gap-y-2', - currentProvider && 'pr-6', - )}> - <TabSliderNew - value={activeTab} - onChange={(state) => { - setActiveTab(state) - if (state !== activeTab) - setCurrentProvider(undefined) - }} - options={options} - /> - <div className='flex items-center gap-2'> - <LabelFilter value={tagFilterValue} onChange={handleTagsChange} /> - <Input - showLeftIcon - showClearIcon - wrapperClassName='w-[200px]' - value={keywords} - onChange={e => handleKeywordsChange(e.target.value)} - onClear={() => handleKeywordsChange('')} - /> - </div> - </div> - {(filteredCollectionList.length > 0 || activeTab !== 'builtin') && ( + <> + <div className='relative flex overflow-hidden bg-gray-100 shrink-0 h-0 grow'> + <div + ref={containerRef} + className='relative flex flex-col overflow-y-auto bg-gray-100 grow' + > <div className={cn( - 'relative grid content-start grid-cols-1 gap-4 px-12 pt-2 pb-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0', + 'sticky top-0 flex justify-between items-center pt-4 px-12 pb-2 leading-[56px] bg-gray-100 z-20 flex-wrap gap-y-2', + currentProvider && 'pr-6', )}> - {activeTab === 'api' && <CustomCreateCard onRefreshData={refetch} />} - {filteredCollectionList.map(collection => ( - <div - key={collection.id} - onClick={() => setCurrentProvider(collection)} - > - <Card - className={cn( - 'border-[1.5px] border-transparent cursor-pointer', - currentProvider?.id === collection.id && 'border-components-option-card-option-selected-border', - )} - hideCornerMark - payload={{ - ...collection, - brief: collection.description, - } as any} - footer={ - <CardMoreInfo - tags={collection.labels} - /> - } - /> - </div> - ))} - {!filteredCollectionList.length && activeTab === 'workflow' && <div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'><WorkflowToolEmpty /></div>} - </div> - )} - {!filteredCollectionList.length && activeTab === 'builtin' && ( - <Empty lightCard text={t('tools.noTools')} className='px-12' /> - )} - { - enable_marketplace && activeTab === 'builtin' && ( - <Marketplace - onMarketplaceScroll={() => { - containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'smooth' }) + <TabSliderNew + value={activeTab} + onChange={(state) => { + setActiveTab(state) + if (state !== activeTab) + setCurrentProvider(undefined) }} - searchPluginText={keywords} - filterPluginTags={tagFilterValue} + options={options} /> - ) - } + <div className='flex items-center gap-2'> + <LabelFilter value={tagFilterValue} onChange={handleTagsChange} /> + <Input + showLeftIcon + showClearIcon + wrapperClassName='w-[200px]' + value={keywords} + onChange={e => handleKeywordsChange(e.target.value)} + onClear={() => handleKeywordsChange('')} + /> + </div> + </div> + {(filteredCollectionList.length > 0 || activeTab !== 'builtin') && ( + <div className={cn( + 'relative grid content-start grid-cols-1 gap-4 px-12 pt-2 pb-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 grow shrink-0', + )}> + {activeTab === 'api' && <CustomCreateCard onRefreshData={refetch} />} + {filteredCollectionList.map(collection => ( + <div + key={collection.id} + onClick={() => setCurrentProvider(collection)} + > + <Card + className={cn( + 'border-[1.5px] border-transparent cursor-pointer', + currentProvider?.id === collection.id && 'border-components-option-card-option-selected-border', + )} + hideCornerMark + payload={{ + ...collection, + brief: collection.description, + } as any} + footer={ + <CardMoreInfo + tags={collection.labels} + /> + } + /> + </div> + ))} + {!filteredCollectionList.length && activeTab === 'workflow' && <div className='absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'><WorkflowToolEmpty /></div>} + </div> + )} + {!filteredCollectionList.length && activeTab === 'builtin' && ( + <Empty lightCard text={t('tools.noTools')} className='px-12' /> + )} + { + enable_marketplace && activeTab === 'builtin' && ( + <Marketplace + onMarketplaceScroll={() => { + containerRef.current?.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'smooth' }) + }} + searchPluginText={keywords} + filterPluginTags={tagFilterValue} + /> + ) + } + </div> </div> - {currentProvider && ( + {currentProvider && !currentProvider.plugin_id && ( <ProviderDetail collection={currentProvider} onHide={() => setCurrentProvider(undefined)} onRefreshData={refetch} /> )} - </div> + <PluginDetailPanel + detail={currentPluginDetail} + onUpdate={() => invalidateInstalledPluginList()} + onHide={() => setCurrentProvider(undefined)} + /> + </> ) } ProviderList.displayName = 'ToolProviderList' diff --git a/web/app/components/tools/types.ts b/web/app/components/tools/types.ts index 34cc491481..27f187d1f7 100644 --- a/web/app/components/tools/types.ts +++ b/web/app/components/tools/types.ts @@ -48,6 +48,7 @@ export type Collection = { is_team_authorization: boolean allow_delete: boolean labels: string[] + plugin_id?: string } export type ToolParameter = { From 3f0b35d72e627957a847c0cbb3ca36e4d985ef74 Mon Sep 17 00:00:00 2001 From: Joel <iamjoel007@gmail.com> Date: Thu, 21 Nov 2024 18:21:57 +0800 Subject: [PATCH 3/4] feat: install bundle from marketplace code --- .../install-from-marketplace/index.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx index 553c30f6be..44b63cf7dd 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useState } from 'react' import Modal from '@/app/components/base/modal' -import type { Plugin, PluginManifestInMarket } from '../../types' +import type { Dependency, Plugin, PluginManifestInMarket } from '../../types' import { InstallStep, PluginType } from '../../types' import Install from './steps/install' import Installed from '../base/installed' @@ -10,12 +10,15 @@ import { useTranslation } from 'react-i18next' import { useUpdateModelProviders } from '@/app/components/header/account-setting/model-provider-page/hooks' import { useInvalidateInstalledPluginList } from '@/service/use-plugins' import { useInvalidateAllToolProviders } from '@/service/use-tools' +import ReadyToInstallBundle from '../install-bundle/ready-to-install' const i18nPrefix = 'plugin.installModal' type InstallFromMarketplaceProps = { uniqueIdentifier: string manifest: PluginManifestInMarket | Plugin + isBundle?: boolean + dependencies?: Dependency[] onSuccess: () => void onClose: () => void } @@ -23,6 +26,8 @@ type InstallFromMarketplaceProps = { const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({ uniqueIdentifier, manifest, + isBundle, + dependencies, onSuccess, onClose, }) => { @@ -83,7 +88,14 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({ ) } { - ([InstallStep.installed, InstallStep.installFailed].includes(step)) && ( + isBundle ? ( + <ReadyToInstallBundle + step={step} + onStepChange={setStep} + onClose={onClose} + allPlugins={dependencies!} + /> + ) : ([InstallStep.installed, InstallStep.installFailed].includes(step)) && ( <Installed payload={manifest!} isMarketPayload From bd6c2e519c62780103a0093f63d1f6b6a5a8c51f Mon Sep 17 00:00:00 2001 From: JzoNg <jzongcode@gmail.com> Date: Fri, 22 Nov 2024 09:09:22 +0800 Subject: [PATCH 4/4] fix: corner mark --- web/app/components/plugins/card/index.tsx | 9 +++++---- web/app/components/plugins/types.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/web/app/components/plugins/card/index.tsx b/web/app/components/plugins/card/index.tsx index b262727506..2d66e1b872 100644 --- a/web/app/components/plugins/card/index.tsx +++ b/web/app/components/plugins/card/index.tsx @@ -11,6 +11,7 @@ import Placeholder from './base/placeholder' import cn from '@/utils/classnames' import { useGetLanguage } from '@/context/i18n' import { getLanguage } from '@/i18n/language' +import { useCategories } from '../hooks' export type Props = { className?: string @@ -41,9 +42,9 @@ const Card = ({ }: Props) => { const defaultLocale = useGetLanguage() const locale = localeFromProps ? getLanguage(localeFromProps) : defaultLocale - - const { type, name, org, label, brief, icon, verified } = payload - + const { categoriesMap } = useCategories() + const { type, category, name, org, label, brief, icon, verified } = payload + const cornerMark = type !== 'plugin' ? type : categoriesMap[category].label const getLocalizedText = (obj: Record<string, string> | undefined) => obj?.[locale] || obj?.['en-US'] || obj?.en_US || '' @@ -59,7 +60,7 @@ const Card = ({ return ( <div className={wrapClassName}> - {!hideCornerMark && <CornerMark text={type} />} + {!hideCornerMark && <CornerMark text={cornerMark} />} {/* Header */} <div className="flex"> <Icon src={icon} installed={installed} installFailed={installFailed} /> diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 4bf4f54c66..b300b6b6e4 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -107,7 +107,7 @@ export type PluginDetail = { } export type Plugin = { - type: PluginType + type: 'plugin' | 'bundle' org: string name: string plugin_id: string