merge feat/plugins

This commit is contained in:
StyleZhang 2024-11-27 16:15:57 +08:00
commit 9302a5fac8
31 changed files with 284 additions and 195 deletions

View File

@ -8,7 +8,7 @@ const PluginList = async () => {
return (
<PluginPage
plugins={<PluginsPanel />}
marketplace={<Marketplace locale={locale} />}
marketplace={<Marketplace locale={locale} shouldExclude />}
/>
)
}

View File

@ -72,7 +72,6 @@ import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import NewFeaturePanel from '@/app/components/base/features/new-feature-panel'
import { fetchFileUploadConfig } from '@/service/common'
import { correctProvider } from '@/utils'
import PluginDependency from '@/app/components/workflow/plugin-dependency'
type PublishConfig = {
modelConfig: ModelConfig
@ -1042,7 +1041,6 @@ const Configuration: FC = () => {
onAutoAddPromptVariable={handleAddPromptVariable}
/>
)}
<PluginDependency />
</>
</FeaturesProvider>
</ConfigContext.Provider>

View File

@ -25,7 +25,8 @@ import AppsFull from '@/app/components/billing/apps-full-in-dialog'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { getRedirection } from '@/utils/app-redirection'
import cn from '@/utils/classnames'
import { useMutationCheckDependenciesBeforeImportDSL } from '@/service/use-plugins'
import { useStore as usePluginDependencyStore } from '@/app/components/workflow/plugin-dependency/store'
import PluginDependency from '@/app/components/workflow/plugin-dependency'
type CreateFromDSLModalProps = {
show: boolean
@ -48,7 +49,6 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
const [fileContent, setFileContent] = useState<string>()
const [currentTab, setCurrentTab] = useState(activeTab)
const [dslUrlValue, setDslUrlValue] = useState(dslUrl)
const { mutateAsync } = useMutationCheckDependenciesBeforeImportDSL()
const [showErrorModal, setShowErrorModal] = useState(false)
const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>()
const [importId, setImportId] = useState<string>()
@ -92,20 +92,22 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
mode: DSLImportMode.YAML_CONTENT,
yaml_content: fileContent || '',
})
await mutateAsync({ dslString: fileContent })
}
if (currentTab === CreateFromDSLModalTab.FROM_URL) {
response = await importDSL({
mode: DSLImportMode.YAML_URL,
yaml_url: dslUrlValue || '',
})
await mutateAsync({ url: dslUrlValue })
}
if (!response)
return
const { id, status, app_id, imported_dsl_version, current_dsl_version } = response
const { id, status, app_id, imported_dsl_version, current_dsl_version, leaked_dependencies } = response
if (leaked_dependencies?.length) {
const { setDependencies } = usePluginDependencyStore.getState()
setDependencies(leaked_dependencies)
}
if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) {
if (onSuccess)
onSuccess()
@ -272,7 +274,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
>
<div className='flex pb-4 flex-col items-start gap-2 self-stretch'>
<div className='text-text-primary title-2xl-semi-bold'>{t('app.newApp.appCreateDSLErrorTitle')}</div>
<div className='flex flex-grow flex-col text-text-secondary system-md-regular'>
<div className='flex grow flex-col text-text-secondary system-md-regular'>
<div>{t('app.newApp.appCreateDSLErrorPart1')}</div>
<div>{t('app.newApp.appCreateDSLErrorPart2')}</div>
<br />
@ -280,6 +282,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
<div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions?.systemVersion}</span></div>
</div>
</div>
<PluginDependency />
<div className='flex pt-6 justify-end items-start gap-2 self-stretch'>
<Button variant='secondary' onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button>
<Button variant='primary' destructive onClick={onDSLConfirm}>{t('app.newApp.Confirm')}</Button>

View File

@ -263,7 +263,7 @@ describe('build chat item tree and get thread messages', () => {
expect(tree7).toMatchSnapshot()
})
const partialMessages2 = (partialMessages as ChatItemInTree[])
const partialMessages2 = partialMessages as ChatItemInTree[]
const tree8 = buildChatItemTree(partialMessages2)
it('should work with partial messages 2', () => {
expect(tree8).toMatchSnapshot()

View File

@ -63,7 +63,7 @@ const Toast = ({
{type === 'warning' && <RiAlertFill className={`${size === 'md' ? 'w-5 h-5' : 'w-4 h-4'} text-text-warning-secondary`} aria-hidden="true" />}
{type === 'info' && <RiInformation2Fill className={`${size === 'md' ? 'w-5 h-5' : 'w-4 h-4'} text-text-accent`} aria-hidden="true" />}
</div>
<div className={`flex py-1 ${size === 'md' ? 'px-1' : 'px-0.5'} flex-col items-start gap-1 flex-grow`}>
<div className={`flex py-1 ${size === 'md' ? 'px-1' : 'px-0.5'} flex-col items-start gap-1 grow`}>
<div className='text-text-primary system-sm-semibold'>{message}</div>
{children && <div className='text-text-secondary system-xs-regular'>
{children}
@ -71,7 +71,7 @@ const Toast = ({
}
</div>
<ActionButton className='z-[1000]' onClick={close}>
<RiCloseLine className='w-4 h-4 flex-shrink-0 text-text-tertiary' />
<RiCloseLine className='w-4 h-4 shrink-0 text-text-tertiary' />
</ActionButton>
</div>
</div>

View File

@ -135,7 +135,7 @@ const NewSegmentModal: FC<NewSegmentModalProps> = ({
<div className='mb-8'>
<TagInput items={keywords} onChange={newKeywords => setKeywords(newKeywords)} />
</div>
<div className='flex justify-end'>
<div className='flex justify-end space-x-2'>
<Button
onClick={handleCancel}>
{t('common.operation.cancel')}

View File

@ -11,12 +11,14 @@ type Props = {
checked: boolean
onCheckedChange: (plugin: Plugin) => void
payload: PackageDependency
isFromMarketPlace?: boolean
}
const PackageItem: FC<Props> = ({
payload,
checked,
onCheckedChange,
isFromMarketPlace,
}) => {
if (!payload.value?.manifest)
return <LoadingError />
@ -27,6 +29,7 @@ const PackageItem: FC<Props> = ({
payload={plugin}
checked={checked}
onCheckedChange={onCheckedChange}
isFromMarketPlace={isFromMarketPlace}
/>
)
}

View File

@ -11,6 +11,7 @@ type Props = {
onStepChange: (step: InstallStep) => void,
allPlugins: Dependency[]
onClose: () => void
isFromMarketPlace?: boolean
}
const ReadyToInstall: FC<Props> = ({
@ -18,6 +19,7 @@ const ReadyToInstall: FC<Props> = ({
onStepChange,
allPlugins,
onClose,
isFromMarketPlace,
}) => {
const [installedPlugins, setInstalledPlugins] = useState<Plugin[]>([])
const [installStatus, setInstallStatus] = useState<InstallStatusResponse[]>([])
@ -33,6 +35,7 @@ const ReadyToInstall: FC<Props> = ({
allPlugins={allPlugins}
onCancel={onClose}
onInstalled={handleInstalled}
isFromMarketPlace={isFromMarketPlace}
/>
)}
{step === InstallStep.installed && (

View File

@ -14,6 +14,7 @@ type Props = {
selectedPlugins: Plugin[]
onSelect: (plugin: Plugin, selectedIndex: number) => void
onLoadedAllPlugin: () => void
isFromMarketPlace?: boolean
}
const InstallByDSLList: FC<Props> = ({
@ -21,9 +22,12 @@ const InstallByDSLList: FC<Props> = ({
selectedPlugins,
onSelect,
onLoadedAllPlugin,
isFromMarketPlace,
}) => {
const { isLoading: isFetchingMarketplaceDataFromDSL, data: marketplaceFromDSLRes } = useFetchPluginsInMarketPlaceByIds(allPlugins.filter(d => d.type === 'marketplace').map(d => (d as GitHubItemAndMarketPlaceDependency).value.plugin_unique_identifier!))
const { isLoading: isFetchingMarketplaceDataFromLocal, data: marketplaceResFromLocalRes } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map(d => (d as GitHubItemAndMarketPlaceDependency).value!))
// DSL has id, to get plugin info to show more info
const { isLoading: isFetchingMarketplaceDataById, data: infoGetById, error: infoByIdError } = useFetchPluginsInMarketPlaceByIds(allPlugins.filter(d => d.type === 'marketplace').map(d => (d as GitHubItemAndMarketPlaceDependency).value.plugin_unique_identifier!))
// has meta(org,name,version), to get id
const { isLoading: isFetchingDataByMeta, data: infoByMeta, error: infoByMetaError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map(d => (d as GitHubItemAndMarketPlaceDependency).value!))
const [plugins, doSetPlugins] = useState<(Plugin | undefined)[]>((() => {
const hasLocalPackage = allPlugins.some(d => d.type === 'package')
if (!hasLocalPackage)
@ -75,8 +79,8 @@ const InstallByDSLList: FC<Props> = ({
}, [allPlugins])
useEffect(() => {
if (!isFetchingMarketplaceDataFromDSL && marketplaceFromDSLRes?.data.plugins) {
const payloads = marketplaceFromDSLRes?.data.plugins
if (!isFetchingMarketplaceDataById && infoGetById?.data.plugins) {
const payloads = infoGetById?.data.plugins
const failedIndex: number[] = []
const nextPlugins = produce(pluginsRef.current, (draft) => {
marketPlaceInDSLIndex.forEach((index, i) => {
@ -92,11 +96,11 @@ const InstallByDSLList: FC<Props> = ({
setErrorIndexes([...errorIndexes, ...failedIndex])
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isFetchingMarketplaceDataFromDSL])
}, [isFetchingMarketplaceDataById])
useEffect(() => {
if (!isFetchingMarketplaceDataFromLocal && marketplaceResFromLocalRes?.data.list) {
const payloads = marketplaceResFromLocalRes?.data.list
if (!isFetchingDataByMeta && infoByMeta?.data.list) {
const payloads = infoByMeta?.data.list
const failedIndex: number[] = []
const nextPlugins = produce(pluginsRef.current, (draft) => {
marketPlaceInDSLIndex.forEach((index, i) => {
@ -117,7 +121,15 @@ const InstallByDSLList: FC<Props> = ({
setErrorIndexes([...errorIndexes, ...failedIndex])
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isFetchingMarketplaceDataFromLocal])
}, [isFetchingDataByMeta])
useEffect(() => {
// get info all failed
if (infoByMetaError || infoByIdError)
setErrorIndexes([...errorIndexes, ...marketPlaceInDSLIndex])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [infoByMetaError, infoByIdError])
const isLoadedAllData = (plugins.filter(p => !!p).length + errorIndexes.length) === allPlugins.length
useEffect(() => {
@ -161,12 +173,14 @@ const InstallByDSLList: FC<Props> = ({
)
}
// Local package
return (
<PackageItem
key={index}
checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
onCheckedChange={handleSelect(index)}
payload={d as PackageDependency}
isFromMarketPlace={isFromMarketPlace}
/>
)
})

View File

@ -14,12 +14,14 @@ type Props = {
allPlugins: Dependency[]
onInstalled: (plugins: Plugin[], installStatus: InstallStatusResponse[]) => void
onCancel: () => void
isFromMarketPlace?: boolean
}
const Install: FC<Props> = ({
allPlugins,
onInstalled,
onCancel,
isFromMarketPlace,
}) => {
const { t } = useTranslation()
const [selectedPlugins, setSelectedPlugins] = React.useState<Plugin[]>([])
@ -75,6 +77,7 @@ const Install: FC<Props> = ({
selectedPlugins={selectedPlugins}
onSelect={handleSelect}
onLoadedAllPlugin={handleLoadedAllPlugin}
isFromMarketPlace={isFromMarketPlace}
/>
</div>
</div>

View File

@ -41,12 +41,14 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
// TODO: check installed in beta version.
const getTitle = useCallback(() => {
if (isBundle && step === InstallStep.installed)
return t(`${i18nPrefix}.installComplete`)
if (step === InstallStep.installed)
return t(`${i18nPrefix}.installedSuccessfully`)
if (step === InstallStep.installFailed)
return t(`${i18nPrefix}.installFailed`)
return t(`${i18nPrefix}.installPlugin`)
}, [step, t])
}, [isBundle, step, t])
const handleInstalled = useCallback(() => {
setStep(InstallStep.installed)
@ -55,7 +57,7 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
updateModelProviders()
if (PluginType.tool.includes(manifest.category))
invalidateAllToolProviders()
}, [invalidateAllToolProviders, invalidateInstalledPluginList, manifest.category, updateModelProviders])
}, [invalidateAllToolProviders, invalidateInstalledPluginList, manifest, updateModelProviders])
const handleFailed = useCallback((errorMsg?: string) => {
setStep(InstallStep.installFailed)
@ -76,17 +78,6 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
{getTitle()}
</div>
</div>
{
step === InstallStep.readyToInstall && (
<Install
uniqueIdentifier={uniqueIdentifier}
payload={manifest!}
onCancel={onClose}
onInstalled={handleInstalled}
onFailed={handleFailed}
/>
)
}
{
isBundle ? (
<ReadyToInstallBundle
@ -94,18 +85,34 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
onStepChange={setStep}
onClose={onClose}
allPlugins={dependencies!}
isFromMarketPlace
/>
) : ([InstallStep.installed, InstallStep.installFailed].includes(step)) && (
<Installed
payload={manifest!}
isMarketPayload
isFailed={step === InstallStep.installFailed}
errMsg={errorMsg}
onCancel={onSuccess}
/>
) : (<>
{
step === InstallStep.readyToInstall && (
<Install
uniqueIdentifier={uniqueIdentifier}
payload={manifest!}
onCancel={onClose}
onInstalled={handleInstalled}
onFailed={handleFailed}
/>
)}
{
[InstallStep.installed, InstallStep.installFailed].includes(step) && (
<Installed
payload={manifest!}
isMarketPayload
isFailed={step === InstallStep.installFailed}
errMsg={errorMsg}
onCancel={onSuccess}
/>
)
}
</>
)
}
</Modal>
</Modal >
)
}

View File

@ -94,7 +94,7 @@ const Installed: FC<Props> = ({
</>
)
}</>)
}, [payload.latest_version, supportCheckInstalled])
}, [payload.latest_version, payload.version, supportCheckInstalled])
return (
<>

View File

@ -6,6 +6,7 @@ import type {
import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
@ -30,6 +31,7 @@ import {
useMarketplacePlugins,
} from './hooks'
import { getMarketplaceListCondition } from './utils'
import { useInstalledPluginList } from '@/service/use-plugins'
export type MarketplaceContextValue = {
intersected: boolean
@ -74,6 +76,7 @@ export const MarketplaceContext = createContext<MarketplaceContextValue>({
type MarketplaceContextProviderProps = {
children: ReactNode
searchParams?: SearchParams
shouldExclude?: boolean
}
export function useMarketplaceContext(selector: (value: MarketplaceContextValue) => any) {
@ -83,7 +86,13 @@ export function useMarketplaceContext(selector: (value: MarketplaceContextValue)
export const MarketplaceContextProvider = ({
children,
searchParams,
shouldExclude,
}: MarketplaceContextProviderProps) => {
const { data, isSuccess } = useInstalledPluginList(!shouldExclude)
const exclude = useMemo(() => {
if (shouldExclude)
return data?.plugins.map(plugin => plugin.plugin_id)
}, [data?.plugins, shouldExclude])
const queryFromSearchParams = searchParams?.q || ''
const tagsFromSearchParams = searchParams?.tags ? getValidTagKeys(searchParams.tags.split(',')) : []
const hasValidTags = !!tagsFromSearchParams.length
@ -125,8 +134,15 @@ export const MarketplaceContextProvider = ({
})
history.pushState({}, '', `/${searchParams?.language ? `?language=${searchParams?.language}` : ''}`)
}
else {
if (shouldExclude && isSuccess) {
queryMarketplaceCollectionsAndPlugins({
exclude,
})
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [queryPlugins])
}, [queryPlugins, queryMarketplaceCollectionsAndPlugins, isSuccess, exclude])
const handleSearchPluginTextChange = useCallback((text: string) => {
setSearchPluginText(text)
@ -136,6 +152,7 @@ export const MarketplaceContextProvider = ({
queryMarketplaceCollectionsAndPlugins({
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
condition: getMarketplaceListCondition(activePluginTypeRef.current),
exclude,
})
resetPlugins()
@ -148,8 +165,9 @@ export const MarketplaceContextProvider = ({
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
})
}, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins])
}, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins, exclude])
const handleFilterPluginTagsChange = useCallback((tags: string[]) => {
setFilterPluginTags(tags)
@ -159,6 +177,7 @@ export const MarketplaceContextProvider = ({
queryMarketplaceCollectionsAndPlugins({
category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current,
condition: getMarketplaceListCondition(activePluginTypeRef.current),
exclude,
})
resetPlugins()
@ -171,8 +190,9 @@ export const MarketplaceContextProvider = ({
tags,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
})
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins])
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude])
const handleActivePluginTypeChange = useCallback((type: string) => {
setActivePluginType(type)
@ -182,6 +202,7 @@ export const MarketplaceContextProvider = ({
queryMarketplaceCollectionsAndPlugins({
category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type,
condition: getMarketplaceListCondition(type),
exclude,
})
resetPlugins()
@ -194,8 +215,9 @@ export const MarketplaceContextProvider = ({
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
})
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins])
}, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude])
const handleSortChange = useCallback((sort: PluginsSort) => {
setSort(sort)
@ -207,8 +229,9 @@ export const MarketplaceContextProvider = ({
tags: filterPluginTagsRef.current,
sortBy: sortRef.current.sortBy,
sortOrder: sortRef.current.sortOrder,
exclude,
})
}, [queryPlugins])
}, [queryPlugins, exclude])
return (
<MarketplaceContext.Provider

View File

@ -17,7 +17,9 @@ import {
getPluginIconInMarketplace,
} from './utils'
import i18n from '@/i18n/i18next-config'
import { useMutationPluginsFromMarketplace } from '@/service/use-plugins'
import {
useMutationPluginsFromMarketplace,
} from '@/service/use-plugins'
export const useMarketplaceCollectionsAndPlugins = () => {
const [isLoading, setIsLoading] = useState(false)
@ -55,7 +57,7 @@ export const useMarketplacePlugins = () => {
mutate(pluginsSearchParams)
}, [mutate])
const { run: queryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams) => {
const { run: queryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams: PluginsSearchParams) => {
mutate(pluginsSearchParams)
}, {
wait: 500,

View File

@ -11,18 +11,26 @@ import { TanstackQueryIniter } from '@/context/query-client'
type MarketplaceProps = {
locale: string
showInstallButton?: boolean
shouldExclude?: boolean
searchParams?: SearchParams
}
const Marketplace = async ({
locale,
showInstallButton = true,
shouldExclude,
searchParams,
}: MarketplaceProps) => {
const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins()
let marketplaceCollections: any = []
let marketplaceCollectionPluginsMap = {}
if (!shouldExclude) {
const marketplaceCollectionsAndPluginsData = await getMarketplaceCollectionsAndPlugins()
marketplaceCollections = marketplaceCollectionsAndPluginsData.marketplaceCollections
marketplaceCollectionPluginsMap = marketplaceCollectionsAndPluginsData.marketplaceCollectionPluginsMap
}
return (
<TanstackQueryIniter>
<MarketplaceContextProvider searchParams={searchParams}>
<MarketplaceContextProvider searchParams={searchParams} shouldExclude={shouldExclude}>
<Description locale={locale} />
<IntersectionLine />
<SearchBoxWrapper locale={locale} />

View File

@ -27,6 +27,7 @@ export type PluginsSearchParams = {
sortOrder?: string
category?: string
tags?: string[]
exclude?: string[]
}
export type PluginsSort = {
@ -37,6 +38,7 @@ export type PluginsSort = {
export type CollectionsAndPluginsSearchParams = {
category?: string
condition?: string
exclude?: string[]
}
export type SearchParams = {

View File

@ -3,7 +3,6 @@ import { PluginType } from '@/app/components/plugins/types'
import type {
CollectionsAndPluginsSearchParams,
MarketplaceCollection,
PluginsSearchParams,
} from '@/app/components/plugins/marketplace/types'
import { MARKETPLACE_API_PREFIX } from '@/config'
@ -22,10 +21,18 @@ export const getMarketplaceCollectionsAndPlugins = async (query?: CollectionsAnd
const marketplaceCollectionsDataJson = await marketplaceCollectionsData.json()
marketplaceCollections = marketplaceCollectionsDataJson.data.collections
await Promise.all(marketplaceCollections.map(async (collection: MarketplaceCollection) => {
let url = `${MARKETPLACE_API_PREFIX}/collections/${collection.name}/plugins?page=1&page_size=100`
if (query?.category)
url += `&category=${query.category}`
const marketplaceCollectionPluginsData = await globalThis.fetch(url, { cache: 'no-store' })
const url = `${MARKETPLACE_API_PREFIX}/collections/${collection.name}/plugins`
const marketplaceCollectionPluginsData = await globalThis.fetch(
url,
{
cache: 'no-store',
method: 'POST',
body: JSON.stringify({
category: query?.category,
exclude: query?.exclude,
}),
},
)
const marketplaceCollectionPluginsDataJson = await marketplaceCollectionPluginsData.json()
const plugins = marketplaceCollectionPluginsDataJson.data.plugins.map((plugin: Plugin) => {
return {
@ -49,45 +56,6 @@ export const getMarketplaceCollectionsAndPlugins = async (query?: CollectionsAnd
}
}
export const getMarketplacePlugins = async (query: PluginsSearchParams) => {
let marketplacePlugins = [] as Plugin[]
try {
const marketplacePluginsData = await globalThis.fetch(
`${MARKETPLACE_API_PREFIX}/plugins/search/basic`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
page: 1,
page_size: 10,
query: query.query,
sort_by: query.sortBy,
sort_order: query.sortOrder,
category: query.category,
tags: query.tags,
}),
},
)
const marketplacePluginsDataJson = await marketplacePluginsData.json()
marketplacePlugins = marketplacePluginsDataJson.data.plugins.map((plugin: Plugin) => {
return {
...plugin,
icon: getPluginIconInMarketplace(plugin),
}
})
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (e) {
marketplacePlugins = []
}
return {
marketplacePlugins,
}
}
export const getMarketplaceListCondition = (pluginType: string) => {
if (pluginType === PluginType.tool)
return 'category=tool'

View File

@ -201,7 +201,15 @@ const DetailHeader = ({
}
/>
{(hasNewVersion || isFromGitHub) && (
<Button variant='secondary-accent' size='small' className='!h-5' onClick={handleUpdate}>{t('plugin.detailPanel.operation.update')}</Button>
<Button variant='secondary-accent' size='small' className='!h-5' onClick={() => {
if (isFromMarketplace) {
setTargetVersion({
version: latest_version,
unique_identifier: latest_unique_identifier,
})
}
handleUpdate()
}}>{t('plugin.detailPanel.operation.update')}</Button>
)}
</div>
<div className='mb-1 flex justify-between items-center h-4'>

View File

@ -28,13 +28,15 @@ import {
useRouter,
useSearchParams,
} from 'next/navigation'
import type { Dependency } from '../types'
import type { PluginDeclaration, PluginManifestInMarket } from '../types'
import { sleep } from '@/utils'
import { fetchManifestFromMarketPlace } from '@/service/plugins'
import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins'
import { marketplaceApiPrefix } from '@/config'
import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config'
const PACKAGE_IDS_KEY = 'package-ids'
const BUNDLE_INFO_KEY = 'bundle-info'
export type PluginPageProps = {
plugins: React.ReactNode
@ -58,6 +60,18 @@ const PluginPage = ({
return ''
}
}, [searchParams])
const [dependencies, setDependencies] = useState<Dependency[]>([])
const bundleInfo = useMemo(() => {
const info = searchParams.get(BUNDLE_INFO_KEY)
try {
return info ? JSON.parse(info) : undefined
}
catch (e) {
return undefined
}
}, [searchParams])
const [isShowInstallFromMarketplace, {
setTrue: showInstallFromMarketplace,
setFalse: doHideInstallFromMarketplace,
@ -67,6 +81,7 @@ const PluginPage = ({
doHideInstallFromMarketplace()
const url = new URL(window.location.href)
url.searchParams.delete(PACKAGE_IDS_KEY)
url.searchParams.delete(BUNDLE_INFO_KEY)
replace(url.toString())
}
const [manifest, setManifest] = useState<PluginDeclaration | PluginManifestInMarket | null>(null)
@ -76,16 +91,23 @@ const PluginPage = ({
await sleep(100)
if (packageId) {
const { data } = await fetchManifestFromMarketPlace(encodeURIComponent(packageId))
const { plugin } = data
const { plugin, version } = data
setManifest({
...plugin,
version: version.version,
icon: `${marketplaceApiPrefix}/plugins/${plugin.org}/${plugin.name}/icon`,
})
showInstallFromMarketplace()
return
}
if (bundleInfo) {
const { data } = await fetchBundleInfoFromMarketPlace(bundleInfo)
setDependencies(data.version.dependencies)
showInstallFromMarketplace()
}
})()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [packageId])
}, [packageId, bundleInfo])
const {
canManagement,
@ -210,6 +232,8 @@ const PluginPage = ({
<InstallFromMarketplace
manifest={manifest! as PluginManifestInMarket}
uniqueIdentifier={packageId}
isBundle={!!bundleInfo}
dependencies={dependencies}
onClose={hideInstallFromMarketplace}
onSuccess={hideInstallFromMarketplace}
/>

View File

@ -79,7 +79,7 @@ export type PluginManifestInMarket = {
icon: string
label: Record<Locale, string>
category: PluginType
version: string // TODO: wait api return current plugin version
version: string // conbine the other place to it
latest_version: string
brief: Record<Locale, string>
introduction: string

View File

@ -89,6 +89,7 @@ const UpdatePluginModal: FC<Props> = ({
})
onSave()
}
// eslint-disable-next-line unused-imports/no-unused-vars
catch (e) {
setUploadStep(UploadStep.notStarted)
}

View File

@ -1,5 +1,6 @@
import {
useEffect,
useMemo,
} from 'react'
import {
useMarketplaceCollectionsAndPlugins,
@ -7,8 +8,14 @@ import {
} from '@/app/components/plugins/marketplace/hooks'
import { PluginType } from '@/app/components/plugins/types'
import { getMarketplaceListCondition } from '@/app/components/plugins/marketplace/utils'
import { useAllToolProviders } from '@/service/use-tools'
export const useMarketplace = (searchPluginText: string, filterPluginTags: string[]) => {
const { data: toolProvidersData, isSuccess } = useAllToolProviders()
const exclude = useMemo(() => {
if (isSuccess)
return toolProvidersData?.filter(toolProvider => !!toolProvider.plugin_id).map(toolProvider => toolProvider.plugin_id!)
}, [isSuccess, toolProvidersData])
const {
isLoading,
marketplaceCollections,
@ -24,12 +31,13 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
} = useMarketplacePlugins()
useEffect(() => {
if (searchPluginText || filterPluginTags.length) {
if ((searchPluginText || filterPluginTags.length) && isSuccess) {
if (searchPluginText) {
queryPluginsWithDebounced({
category: PluginType.tool,
query: searchPluginText,
tags: filterPluginTags,
exclude,
})
return
}
@ -37,16 +45,20 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin
category: PluginType.tool,
query: searchPluginText,
tags: filterPluginTags,
exclude,
})
}
else {
queryMarketplaceCollectionsAndPlugins({
category: PluginType.tool,
condition: getMarketplaceListCondition(PluginType.tool),
})
resetPlugins()
if (isSuccess) {
queryMarketplaceCollectionsAndPlugins({
category: PluginType.tool,
condition: getMarketplaceListCondition(PluginType.tool),
exclude,
})
resetPlugins()
}
}
}, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins])
}, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins, exclude, isSuccess])
return {
isLoading: isLoading || isPluginsLoading,

View File

@ -72,7 +72,6 @@ import SyncingDataModal from './syncing-data-modal'
import UpdateDSLModal from './update-dsl-modal'
import DSLExportConfirmModal from './dsl-export-confirm-modal'
import LimitTips from './limit-tips'
import PluginDependency from './plugin-dependency'
import {
useStore,
useWorkflowStore,
@ -327,7 +326,6 @@ const Workflow: FC<WorkflowProps> = memo(({
/>
)
}
<PluginDependency />
<LimitTips />
<ReactFlow
nodeTypes={nodeTypes}

View File

@ -32,7 +32,7 @@ type BeforeRunFormProps = {
function formatValue(value: string | any, type: InputVarType) {
if (type === InputVarType.number)
return parseFloat(value)
return Number.parseFloat(value)
if (type === InputVarType.json)
return JSON.parse(value)
if (type === InputVarType.contexts) {

View File

@ -88,7 +88,7 @@ const CodeEditor: FC<Props> = ({
const index = (() => {
if (match)
return parseInt(match[1]!) + 1
return Number.parseInt(match[1]!) + 1
return 1
})()

View File

@ -13,7 +13,7 @@ import Field from '@/app/components/workflow/nodes/_base/components/field'
import Split from '@/app/components/workflow/nodes/_base/components/split'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector'
import { type NodePanelProps } from '@/app/components/workflow/types'
import type { NodePanelProps } from '@/app/components/workflow/types'
import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form'
import ResultPanel from '@/app/components/workflow/run/result-panel'
const i18nPrefix = 'workflow.nodes.code'

View File

@ -1,23 +1,43 @@
import { useCallback } from 'react'
import {
useCallback,
useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import { useStore } from './store'
import InstallBundle from '@/app/components/plugins/install-plugin/install-bundle'
import ReadyToInstall from '@/app/components/plugins/install-plugin/install-bundle/ready-to-install'
import { InstallStep } from '@/app/components/plugins/types'
const i18nPrefix = 'plugin.installModal'
const PluginDependency = () => {
const dependencies = useStore(s => s.dependencies)
const handleCancelInstallBundle = useCallback(() => {
const { setDependencies } = useStore.getState()
setDependencies([])
}, [])
const [step, setStep] = useState<InstallStep>(InstallStep.readyToInstall)
const { t } = useTranslation()
const getTitle = useCallback(() => {
if (step === InstallStep.uploadFailed)
return t(`${i18nPrefix}.uploadFailed`)
if (step === InstallStep.installed)
return t(`${i18nPrefix}.installComplete`)
return t(`${i18nPrefix}.installPlugin`)
}, [step, t])
if (!dependencies.length)
return null
return (
<div>
<InstallBundle
fromDSLPayload={dependencies}
onClose={handleCancelInstallBundle}
<div className='flex pt-6 pl-6 pb-3 pr-14 items-start gap-2 self-stretch'>
<div className='self-stretch text-text-primary title-2xl-semi-bold'>
{getTitle()}
</div>
</div>
<ReadyToInstall
step={step}
onStepChange={setStep}
allPlugins={dependencies}
onClose={() => {}}
/>
</div>
)

View File

@ -38,7 +38,8 @@ import { ToastContext } from '@/app/components/base/toast'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useStore as useAppStore } from '@/app/components/app/store'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import { useMutationCheckDependenciesBeforeImportDSL } from '@/service/use-plugins'
import { useStore as usePluginDependencyStore } from '@/app/components/workflow/plugin-dependency/store'
import PluginDependency from '@/app/components/workflow/plugin-dependency'
type UpdateDSLModalProps = {
onCancel: () => void
@ -58,7 +59,6 @@ const UpdateDSLModal = ({
const [fileContent, setFileContent] = useState<string>()
const [loading, setLoading] = useState(false)
const { eventEmitter } = useEventEmitterContextContext()
const { mutateAsync, mutate } = useMutationCheckDependenciesBeforeImportDSL()
const [show, setShow] = useState(true)
const [showErrorModal, setShowErrorModal] = useState(false)
const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>()
@ -81,7 +81,7 @@ const UpdateDSLModal = ({
setFileContent('')
}
const handleWorkflowUpdate = async (app_id: string) => {
const handleWorkflowUpdate = useCallback(async (app_id: string) => {
const {
graph,
features,
@ -124,7 +124,7 @@ const UpdateDSLModal = ({
hash,
},
} as any)
}
}, [eventEmitter])
const isCreatingRef = useRef(false)
const handleImport: MouseEventHandler = useCallback(async () => {
@ -136,36 +136,42 @@ const UpdateDSLModal = ({
try {
if (appDetail && fileContent) {
setLoading(true)
const {
graph,
features,
hash,
} = await updateWorkflowDraftFromDSL(appDetail.id, fileContent)
await mutateAsync({ dslString: fileContent })
const { nodes, edges, viewport } = graph
const newFeatures = {
file: {
image: {
enabled: !!features.file_upload?.image?.enabled,
number_limits: features.file_upload?.image?.number_limits || 3,
transfer_methods: features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
},
enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled),
allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image],
allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`),
allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'],
number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3,
},
opening: {
enabled: !!features.opening_statement,
opening_statement: features.opening_statement,
suggested_questions: features.suggested_questions,
},
suggested: features.suggested_questions_after_answer || { enabled: false },
speech2text: features.speech_to_text || { enabled: false },
text2speech: features.text_to_speech || { enabled: false },
citation: features.retriever_resource || { enabled: false },
moderation: features.sensitive_word_avoidance || { enabled: false },
const response = await importDSL({ mode: DSLImportMode.YAML_CONTENT, yaml_content: fileContent, app_id: appDetail.id })
const { id, status, app_id, imported_dsl_version, current_dsl_version, leaked_dependencies } = response
if (leaked_dependencies?.length) {
const { setDependencies } = usePluginDependencyStore.getState()
setDependencies(leaked_dependencies)
}
if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) {
if (!app_id) {
notify({ type: 'error', message: t('workflow.common.importFailure') })
return
}
handleWorkflowUpdate(app_id)
if (onImport)
onImport()
notify({
type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning',
message: t(status === DSLImportStatus.COMPLETED ? 'workflow.common.importSuccess' : 'workflow.common.importWarning'),
children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('workflow.common.importWarningDetails'),
})
setLoading(false)
onCancel()
}
else if (status === DSLImportStatus.PENDING) {
setShow(false)
setTimeout(() => {
setShowErrorModal(true)
}, 300)
setVersions({
importedVersion: imported_dsl_version ?? '',
systemVersion: current_dsl_version ?? '',
})
setImportId(id)
}
else {
setLoading(false)
notify({ type: 'error', message: t('workflow.common.importFailure') })
}
}
}
@ -174,7 +180,7 @@ const UpdateDSLModal = ({
notify({ type: 'error', message: t('workflow.common.importFailure') })
}
isCreatingRef.current = false
}, [currentFile, fileContent, onCancel, notify, t, eventEmitter, appDetail, onImport, mutateAsync])
}, [currentFile, fileContent, onCancel, notify, t, appDetail, onImport, handleWorkflowUpdate])
const onUpdateDSLConfirm: MouseEventHandler = async () => {
try {
@ -222,12 +228,12 @@ const UpdateDSLModal = ({
<RiCloseLine className='w-[18px] h-[18px] text-text-tertiary' />
</div>
</div>
<div className='flex relative p-2 mb-2 gap-0.5 flex-grow rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xs overflow-hidden'>
<div className='flex relative p-2 mb-2 gap-0.5 grow rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-xs overflow-hidden'>
<div className='absolute top-0 left-0 w-full h-full opacity-40 bg-[linear-gradient(92deg,rgba(247,144,9,0.25)_0%,rgba(255,255,255,0.00)_100%)]' />
<div className='flex p-1 justify-center items-start'>
<RiAlertFill className='w-4 h-4 flex-shrink-0 text-text-warning-secondary' />
<RiAlertFill className='w-4 h-4 shrink-0 text-text-warning-secondary' />
</div>
<div className='flex py-1 flex-col items-start gap-0.5 flex-grow'>
<div className='flex py-1 flex-col items-start gap-0.5 grow'>
<div className='text-text-primary system-xs-medium whitespace-pre-line'>{t('workflow.common.importDSLTip')}</div>
<div className='flex pt-1 pb-0.5 items-start gap-1 self-stretch'>
<Button
@ -275,7 +281,7 @@ const UpdateDSLModal = ({
>
<div className='flex pb-4 flex-col items-start gap-2 self-stretch'>
<div className='text-text-primary title-2xl-semi-bold'>{t('app.newApp.appCreateDSLErrorTitle')}</div>
<div className='flex flex-grow flex-col text-text-secondary system-md-regular'>
<div className='flex grow flex-col text-text-secondary system-md-regular'>
<div>{t('app.newApp.appCreateDSLErrorPart1')}</div>
<div>{t('app.newApp.appCreateDSLErrorPart2')}</div>
<br />
@ -283,6 +289,7 @@ const UpdateDSLModal = ({
<div>{t('app.newApp.appCreateDSLErrorPart4')}<span className='system-md-medium'>{versions?.systemVersion}</span></div>
</div>
</div>
<PluginDependency />
<div className='flex pt-6 justify-end items-start gap-2 self-stretch'>
<Button variant='secondary' onClick={() => setShowErrorModal(false)}>{t('app.newApp.Cancel')}</Button>
<Button variant='primary' destructive onClick={onUpdateDSLConfirm}>{t('app.newApp.Confirm')}</Button>

View File

@ -1,5 +1,6 @@
import type { LangFuseConfig, LangSmithConfig, TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type'
import type { App, AppSSO, AppTemplate, SiteConfig } from '@/types/app'
import type { Dependency } from '@/app/components/plugins/types'
/* export type App = {
id: string
@ -87,6 +88,7 @@ export type DSLImportResponse = {
current_dsl_version?: string
imported_dsl_version?: string
error: string
leaked_dependencies: Dependency[]
}
export type AppSSOResponse = { enabled: AppSSO['enable_sso'] }

View File

@ -1,6 +1,7 @@
import type { Fetcher } from 'swr'
import { get, getMarketplace, post, upload } from './base'
import type {
Dependency,
InstallPackageResponse,
Permissions,
PluginDeclaration,
@ -63,7 +64,15 @@ export const fetchManifest = async (uniqueIdentifier: string) => {
}
export const fetchManifestFromMarketPlace = async (uniqueIdentifier: string) => {
return getMarketplace<{ data: { plugin: PluginManifestInMarket } }>(`/plugins/identifier?unique_identifier=${uniqueIdentifier}`)
return getMarketplace<{ data: { plugin: PluginManifestInMarket, version: { version: string } } }>(`/plugins/identifier?unique_identifier=${uniqueIdentifier}`)
}
export const fetchBundleInfoFromMarketPlace = async ({
org,
name,
version,
}: Record<string, string>) => {
return getMarketplace<{ data: { version: { dependencies: Dependency[] } } }>(`/bundles/${org}/${name}/${version}`)
}
export const fetchMarketplaceCollections: Fetcher<MarketplaceCollectionsResponse, { url: string; }> = ({ url }) => {

View File

@ -24,16 +24,17 @@ import {
useQuery,
useQueryClient,
} from '@tanstack/react-query'
import { useStore as usePluginDependencyStore } from '@/app/components/workflow/plugin-dependency/store'
import { useInvalidateAllBuiltInTools } from './use-tools'
const NAME_SPACE = 'plugins'
const useInstalledPluginListKey = [NAME_SPACE, 'installedPluginList']
export const useInstalledPluginList = () => {
export const useInstalledPluginList = (disable?: boolean) => {
return useQuery<InstalledPluginListResponse>({
queryKey: useInstalledPluginListKey,
queryFn: () => get<InstalledPluginListResponse>('/workspaces/current/plugin/list'),
enabled: !disable,
initialData: !disable ? undefined : { plugins: [] },
})
}
@ -110,6 +111,7 @@ export const useUploadGitHub = (payload: {
queryFn: () => post<uploadGitHubResponse>('/workspaces/current/plugin/upload/github', {
body: payload,
}),
retry: 0,
})
}
@ -225,6 +227,7 @@ export const useMutationPluginsFromMarketplace = () => {
sortOrder,
category,
tags,
exclude,
} = pluginsSearchParams
return postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/search/basic', {
body: {
@ -235,6 +238,7 @@ export const useMutationPluginsFromMarketplace = () => {
sort_order: sortOrder,
category: category !== 'all' ? category : '',
tags,
exclude,
},
})
},
@ -323,36 +327,6 @@ export const useMutationClearAllTaskPlugin = () => {
})
}
export const useMutationCheckDependenciesBeforeImportDSL = () => {
const mutation = useMutation({
mutationFn: ({ dslString, url }: { dslString?: string, url?: string }) => {
if (url) {
return post<{ leaked: Dependency[] }>(
'/apps/import/url/dependencies/check',
{
body: {
url,
},
},
)
}
return post<{ leaked: Dependency[] }>(
'/apps/import/dependencies/check',
{
body: {
data: dslString,
},
})
},
onSuccess: (data) => {
const { setDependencies } = usePluginDependencyStore.getState()
setDependencies(data.leaked || [])
},
})
return mutation
}
export const useDownloadPlugin = (info: { organization: string; pluginName: string; version: string }, needDownload: boolean) => {
return useQuery({
queryKey: [NAME_SPACE, 'downloadPlugin', info],