mirror of
https://github.com/langgenius/dify.git
synced 2026-02-28 04:15:10 +00:00
Merge remote-tracking branch 'origin/main' into feat/trigger
This commit is contained in:
@@ -51,7 +51,7 @@ const Card = ({
|
||||
const { t } = useMixedTranslation(localeFromProps)
|
||||
const { categoriesMap } = useSingleCategories(t)
|
||||
const { category, type, name, org, label, brief, icon, verified, badges = [] } = payload
|
||||
const isBundle = !['plugin', 'model', 'tool', 'extension', 'agent-strategy'].includes(type)
|
||||
const isBundle = !['plugin', 'model', 'tool', 'datasource', 'extension', 'agent-strategy'].includes(type)
|
||||
const cornerMark = isBundle ? categoriesMap.bundle?.label : categoriesMap[category]?.label
|
||||
const getLocalizedText = (obj: Record<string, string> | undefined) =>
|
||||
obj ? renderI18nObject(obj, locale) : ''
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const tagKeys = [
|
||||
'agent',
|
||||
'rag',
|
||||
'search',
|
||||
'image',
|
||||
'videos',
|
||||
@@ -21,6 +22,7 @@ export const tagKeys = [
|
||||
export const categoryKeys = [
|
||||
'model',
|
||||
'tool',
|
||||
'datasource',
|
||||
'agent-strategy',
|
||||
'extension',
|
||||
'bundle',
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
tagKeys,
|
||||
} from './constants'
|
||||
|
||||
type Tag = {
|
||||
export type Tag = {
|
||||
name: string
|
||||
label: string
|
||||
}
|
||||
@@ -26,9 +26,16 @@ export const useTags = (translateFromOut?: TFunction) => {
|
||||
return acc
|
||||
}, {} as Record<string, Tag>)
|
||||
|
||||
const getTagLabel = (name: string) => {
|
||||
if (!tagsMap[name])
|
||||
return name
|
||||
return tagsMap[name].label
|
||||
}
|
||||
|
||||
return {
|
||||
tags,
|
||||
tagsMap,
|
||||
getTagLabel,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useInvalidateAllBuiltInTools, useInvalidateAllToolProviders } from '@/s
|
||||
import { useInvalidateStrategyProviders } from '@/service/use-strategy'
|
||||
import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../../types'
|
||||
import { PluginType } from '../../types'
|
||||
import { useInvalidDataSourceList } from '@/service/use-pipeline'
|
||||
|
||||
const useRefreshPluginList = () => {
|
||||
const invalidateInstalledPluginList = useInvalidateInstalledPluginList()
|
||||
@@ -16,6 +17,7 @@ const useRefreshPluginList = () => {
|
||||
|
||||
const invalidateAllToolProviders = useInvalidateAllToolProviders()
|
||||
const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools()
|
||||
const invalidateAllDataSources = useInvalidDataSourceList()
|
||||
|
||||
const invalidateStrategyProviders = useInvalidateStrategyProviders()
|
||||
return {
|
||||
@@ -30,6 +32,9 @@ const useRefreshPluginList = () => {
|
||||
// TODO: update suggested tools. It's a function in hook useMarketplacePlugins,handleUpdatePlugins
|
||||
}
|
||||
|
||||
if ((manifest && PluginType.datasource.includes(manifest.category)) || refreshAllType)
|
||||
invalidateAllDataSources()
|
||||
|
||||
// model select
|
||||
if ((manifest && PluginType.model.includes(manifest.category)) || refreshAllType) {
|
||||
refreshModelProviders()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import type { ForwardRefRenderFunction } from 'react'
|
||||
import { useImperativeHandle } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import type { Dependency, GitHubItemAndMarketPlaceDependency, PackageDependency, Plugin, VersionInfo } from '../../../types'
|
||||
@@ -21,6 +20,7 @@ type Props = {
|
||||
onDeSelectAll: () => void
|
||||
onLoadedAllPlugin: (installedInfo: Record<string, VersionInfo>) => void
|
||||
isFromMarketPlace?: boolean
|
||||
ref?: React.Ref<ExposeRefs>
|
||||
}
|
||||
|
||||
export type ExposeRefs = {
|
||||
@@ -28,7 +28,7 @@ export type ExposeRefs = {
|
||||
deSelectAllPlugins: () => void
|
||||
}
|
||||
|
||||
const InstallByDSLList: ForwardRefRenderFunction<ExposeRefs, Props> = ({
|
||||
const InstallByDSLList = ({
|
||||
allPlugins,
|
||||
selectedPlugins,
|
||||
onSelect,
|
||||
@@ -36,7 +36,8 @@ const InstallByDSLList: ForwardRefRenderFunction<ExposeRefs, Props> = ({
|
||||
onDeSelectAll,
|
||||
onLoadedAllPlugin,
|
||||
isFromMarketPlace,
|
||||
}, ref) => {
|
||||
ref,
|
||||
}: Props) => {
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
// DSL has id, to get plugin info to show more info
|
||||
const { isLoading: isFetchingMarketplaceDataById, data: infoGetById, error: infoByIdError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map((d) => {
|
||||
@@ -268,4 +269,4 @@ const InstallByDSLList: ForwardRefRenderFunction<ExposeRefs, Props> = ({
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.forwardRef(InstallByDSLList)
|
||||
export default InstallByDSLList
|
||||
|
||||
@@ -39,11 +39,18 @@ const Install: FC<Props> = ({
|
||||
const selectedPluginsNum = selectedPlugins.length
|
||||
const installMultiRef = useRef<ExposeRefs>(null)
|
||||
const { refreshPluginList } = useRefreshPluginList()
|
||||
|
||||
const [isSelectAll, setIsSelectAll] = useState(false)
|
||||
const handleClickSelectAll = useCallback(() => {
|
||||
if (isSelectAll)
|
||||
installMultiRef.current?.deSelectAllPlugins()
|
||||
else
|
||||
installMultiRef.current?.selectAllPlugins()
|
||||
}, [isSelectAll])
|
||||
const [canInstall, setCanInstall] = React.useState(false)
|
||||
const [installedInfo, setInstalledInfo] = useState<Record<string, VersionInfo> | undefined>(undefined)
|
||||
|
||||
const handleLoadedAllPlugin = useCallback((installedInfo: Record<string, VersionInfo> | undefined) => {
|
||||
handleClickSelectAll()
|
||||
setInstalledInfo(installedInfo)
|
||||
setCanInstall(true)
|
||||
}, [])
|
||||
@@ -74,14 +81,7 @@ const Install: FC<Props> = ({
|
||||
installedInfo: installedInfo!,
|
||||
})
|
||||
}
|
||||
const [isSelectAll, setIsSelectAll] = useState(false)
|
||||
const [isIndeterminate, setIsIndeterminate] = useState(false)
|
||||
const handleClickSelectAll = useCallback(() => {
|
||||
if (isSelectAll)
|
||||
installMultiRef.current?.deSelectAllPlugins()
|
||||
else
|
||||
installMultiRef.current?.selectAllPlugins()
|
||||
}, [isSelectAll])
|
||||
const handleSelectAll = useCallback((plugins: Plugin[], selectedIndexes: number[]) => {
|
||||
setSelectedPlugins(plugins)
|
||||
setSelectedIndexes(selectedIndexes)
|
||||
|
||||
@@ -53,7 +53,7 @@ export const pluginManifestInMarketToPluginProps = (pluginManifest: PluginManife
|
||||
}
|
||||
|
||||
export const parseGitHubUrl = (url: string): GitHubUrlInfo => {
|
||||
const match = url.match(/^https:\/\/github\.com\/([^\/]+)\/([^\/]+)\/?$/)
|
||||
const match = url.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/?$/)
|
||||
return match ? { isValid: true, owner: match[1], repo: match[2] } : { isValid: false }
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,10 @@ const Description = async ({
|
||||
<span className='relative z-[2] lowercase'>{t('category.tools')}</span>
|
||||
</span>
|
||||
,
|
||||
<span className="body-md-medium relative z-[1] ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
<span className='relative z-[2] lowercase'>{t('category.datasources')}</span>
|
||||
</span>
|
||||
,
|
||||
<span className="body-md-medium relative z-[1] ml-1 text-text-secondary after:absolute after:bottom-[1.5px] after:left-0 after:h-2 after:w-full after:bg-text-text-selected after:content-['']">
|
||||
<span className='relative z-[2] lowercase'>{t('category.agents')}</span>
|
||||
</span>
|
||||
|
||||
@@ -106,10 +106,13 @@ export const useMarketplacePlugins = () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ! Support zh-Hans, pt-BR, ja-JP and en-US for Marketplace page
|
||||
* ! For other languages, use en-US as fallback
|
||||
*/
|
||||
export const useMixedTranslation = (localeFromOuter?: string) => {
|
||||
let t = useTranslation().t
|
||||
|
||||
// !localeFromOuter only support zh-Hans and en-US for now
|
||||
if (localeFromOuter)
|
||||
t = i18n.getFixedT(localeFromOuter)
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ const CardWrapper = ({
|
||||
setFalse: hideInstallFromMarketplace,
|
||||
}] = useBoolean(false)
|
||||
const { locale: localeFromLocale } = useI18N()
|
||||
const { tagsMap } = useTags(t)
|
||||
const { getTagLabel } = useTags(t)
|
||||
|
||||
if (showInstallButton) {
|
||||
return (
|
||||
@@ -43,7 +43,7 @@ const CardWrapper = ({
|
||||
footer={
|
||||
<CardMoreInfo
|
||||
downloadCount={plugin.install_count}
|
||||
tags={plugin.tags.map(tag => tagsMap[tag.name].label)}
|
||||
tags={plugin.tags.map(tag => getTagLabel(tag.name))}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -92,7 +92,7 @@ const CardWrapper = ({
|
||||
footer={
|
||||
<CardMoreInfo
|
||||
downloadCount={plugin.install_count}
|
||||
tags={plugin.tags.map(tag => tagsMap[tag.name].label)}
|
||||
tags={plugin.tags.map(tag => getTagLabel(tag.name))}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import {
|
||||
RiArchive2Line,
|
||||
RiBrain2Line,
|
||||
RiDatabase2Line,
|
||||
RiHammerLine,
|
||||
RiPuzzle2Line,
|
||||
RiSpeakAiLine,
|
||||
@@ -21,6 +22,7 @@ export const PLUGIN_TYPE_SEARCH_MAP = {
|
||||
tool: PluginType.tool,
|
||||
agent: PluginType.agent,
|
||||
extension: PluginType.extension,
|
||||
datasource: PluginType.datasource,
|
||||
bundle: 'bundle',
|
||||
}
|
||||
type PluginTypeSwitchProps = {
|
||||
@@ -56,6 +58,11 @@ const PluginTypeSwitch = ({
|
||||
text: t('plugin.category.tools'),
|
||||
icon: <RiHammerLine className='mr-1.5 h-4 w-4' />,
|
||||
},
|
||||
{
|
||||
value: PLUGIN_TYPE_SEARCH_MAP.datasource,
|
||||
text: t('plugin.category.datasources'),
|
||||
icon: <RiDatabase2Line className='mr-1.5 h-4 w-4' />,
|
||||
},
|
||||
{
|
||||
value: PLUGIN_TYPE_SEARCH_MAP.agent,
|
||||
text: t('plugin.category.agents'),
|
||||
@@ -82,9 +89,7 @@ const PluginTypeSwitch = ({
|
||||
}, [showSearchParams, handleActivePluginTypeChange])
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('popstate', () => {
|
||||
handlePopState()
|
||||
})
|
||||
window.addEventListener('popstate', handlePopState)
|
||||
return () => {
|
||||
window.removeEventListener('popstate', handlePopState)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
'use client'
|
||||
import { RiCloseCircleFill, RiSearchLine } from '@remixicon/react'
|
||||
import { RiCloseLine, RiSearchLine } from '@remixicon/react'
|
||||
import TagsFilter from './tags-filter'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
|
||||
type SearchBoxProps = {
|
||||
search: string
|
||||
@@ -12,10 +13,10 @@ type SearchBoxProps = {
|
||||
inputClassName?: string
|
||||
tags: string[]
|
||||
onTagsChange: (tags: string[]) => void
|
||||
size?: 'small' | 'large'
|
||||
placeholder?: string
|
||||
locale?: string
|
||||
supportAddCustomTool?: boolean
|
||||
usedInMarketplace?: boolean
|
||||
onShowAddCustomCollectionModal?: () => void
|
||||
onAddedCustomTool?: () => void
|
||||
}
|
||||
@@ -26,9 +27,9 @@ const SearchBox = ({
|
||||
inputClassName,
|
||||
tags,
|
||||
onTagsChange,
|
||||
size = 'small',
|
||||
placeholder = '',
|
||||
locale,
|
||||
usedInMarketplace = false,
|
||||
supportAddCustomTool,
|
||||
onShowAddCustomCollectionModal,
|
||||
}: SearchBoxProps) => {
|
||||
@@ -38,40 +39,82 @@ const SearchBox = ({
|
||||
>
|
||||
<div className={
|
||||
cn('flex items-center',
|
||||
size === 'large' && 'rounded-xl border border-components-chat-input-border bg-components-panel-bg-blur p-1.5 shadow-md',
|
||||
size === 'small' && 'rounded-lg bg-components-input-bg-normal p-0.5',
|
||||
usedInMarketplace && 'rounded-xl border border-components-chat-input-border bg-components-panel-bg-blur p-1.5 shadow-md',
|
||||
!usedInMarketplace && 'rounded-lg bg-components-input-bg-normal p-0.5',
|
||||
inputClassName,
|
||||
)
|
||||
}>
|
||||
<div className='relative flex grow items-center p-1 pl-2'>
|
||||
<div className='mr-2 flex w-full items-center'>
|
||||
<RiSearchLine className='mr-1.5 size-4 text-text-placeholder' />
|
||||
<input
|
||||
className={cn(
|
||||
'system-sm-regular block grow appearance-none bg-transparent text-text-secondary outline-none',
|
||||
)}
|
||||
value={search}
|
||||
onChange={(e) => {
|
||||
onSearchChange(e.target.value)
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
{
|
||||
search && (
|
||||
<div className={cn('group absolute right-2 top-1/2 -translate-y-1/2 cursor-pointer p-[1px]')} onClick={() => onSearchChange('')}>
|
||||
<RiCloseCircleFill className='h-3.5 w-3.5 cursor-pointer text-text-quaternary group-hover:text-text-tertiary' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className='mx-1 h-3.5 w-[1px] bg-divider-regular'></div>
|
||||
<TagsFilter
|
||||
tags={tags}
|
||||
onTagsChange={onTagsChange}
|
||||
size={size}
|
||||
locale={locale}
|
||||
/>
|
||||
{
|
||||
usedInMarketplace && (
|
||||
<>
|
||||
<TagsFilter
|
||||
tags={tags}
|
||||
onTagsChange={onTagsChange}
|
||||
usedInMarketplace
|
||||
locale={locale}
|
||||
/>
|
||||
<Divider type='vertical' className='mx-1 h-3.5' />
|
||||
<div className='flex grow items-center gap-x-2 p-1'>
|
||||
<input
|
||||
className={cn(
|
||||
'body-md-medium inline-block grow appearance-none bg-transparent text-text-secondary outline-none',
|
||||
)}
|
||||
value={search}
|
||||
onChange={(e) => {
|
||||
onSearchChange(e.target.value)
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
{
|
||||
search && (
|
||||
<ActionButton
|
||||
onClick={() => onSearchChange('')}
|
||||
className='shrink-0'
|
||||
>
|
||||
<RiCloseLine className='size-4' />
|
||||
</ActionButton>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{
|
||||
!usedInMarketplace && (
|
||||
<>
|
||||
<div className='flex grow items-center p-2'>
|
||||
<RiSearchLine className='size-4 text-components-input-text-placeholder' />
|
||||
<input
|
||||
className={cn(
|
||||
'body-md-medium ml-1.5 mr-1 inline-block grow appearance-none bg-transparent text-text-secondary outline-none',
|
||||
search && 'mr-2',
|
||||
)}
|
||||
value={search}
|
||||
onChange={(e) => {
|
||||
onSearchChange(e.target.value)
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
{
|
||||
search && (
|
||||
<ActionButton
|
||||
onClick={() => onSearchChange('')}
|
||||
className='shrink-0'
|
||||
>
|
||||
<RiCloseLine className='size-4' />
|
||||
</ActionButton>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<Divider type='vertical' className='mx-0 mr-0.5 h-3.5' />
|
||||
<TagsFilter
|
||||
tags={tags}
|
||||
onTagsChange={onTagsChange}
|
||||
locale={locale}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{supportAddCustomTool && (
|
||||
<div className='flex shrink-0 items-center'>
|
||||
|
||||
@@ -36,9 +36,9 @@ const SearchBoxWrapper = ({
|
||||
onSearchChange={handleSearchPluginTextChange}
|
||||
tags={filterPluginTags}
|
||||
onTagsChange={handleFilterPluginTagsChange}
|
||||
size='large'
|
||||
locale={locale}
|
||||
placeholder={t('plugin.searchPlugins')}
|
||||
usedInMarketplace
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
RiPriceTag3Line,
|
||||
} 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'
|
||||
import { useTags } from '@/app/components/plugins/hooks'
|
||||
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
|
||||
import MarketplaceTrigger from './trigger/marketplace'
|
||||
import ToolSelectorTrigger from './trigger/tool-selector'
|
||||
|
||||
type TagsFilterProps = {
|
||||
tags: string[]
|
||||
onTagsChange: (tags: string[]) => void
|
||||
size: 'small' | 'large'
|
||||
usedInMarketplace?: boolean
|
||||
locale?: string
|
||||
}
|
||||
const TagsFilter = ({
|
||||
tags,
|
||||
onTagsChange,
|
||||
size,
|
||||
usedInMarketplace = false,
|
||||
locale,
|
||||
}: TagsFilterProps) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const { tags: options } = useTags(t)
|
||||
const { tags: options, tagsMap } = useTags(t)
|
||||
const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchText.toLowerCase()))
|
||||
const handleCheck = (id: string) => {
|
||||
if (tags.includes(id))
|
||||
@@ -38,6 +36,7 @@ const TagsFilter = ({
|
||||
else
|
||||
onTagsChange([...tags, id])
|
||||
}
|
||||
const selectedTagsLength = tags.length
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
@@ -53,17 +52,29 @@ const TagsFilter = ({
|
||||
className='shrink-0'
|
||||
onClick={() => setOpen(v => !v)}
|
||||
>
|
||||
<div className={cn(
|
||||
'ml-0.5 mr-1.5 flex select-none items-center text-text-tertiary',
|
||||
size === 'large' && 'h-8 py-1',
|
||||
size === 'small' && 'h-7 py-0.5 ',
|
||||
// selectedTagsLength && 'text-text-secondary',
|
||||
// open && 'bg-state-base-hover',
|
||||
)}>
|
||||
<div className='cursor-pointer rounded-md p-0.5 hover:bg-state-base-hover'>
|
||||
<RiPriceTag3Line className='h-4 w-4 text-text-tertiary' />
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
usedInMarketplace && (
|
||||
<MarketplaceTrigger
|
||||
selectedTagsLength={selectedTagsLength}
|
||||
open={open}
|
||||
tags={tags}
|
||||
tagsMap={tagsMap}
|
||||
locale={locale}
|
||||
onTagsChange={onTagsChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!usedInMarketplace && (
|
||||
<ToolSelectorTrigger
|
||||
selectedTagsLength={selectedTagsLength}
|
||||
open={open}
|
||||
tags={tags}
|
||||
tagsMap={tagsMap}
|
||||
onTagsChange={onTagsChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[1000]'>
|
||||
<div className='w-[240px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm'>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import React from 'react'
|
||||
import { RiArrowDownSLine, RiCloseCircleFill, RiFilter3Line } from '@remixicon/react'
|
||||
import type { Tag } from '../../../hooks'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useMixedTranslation } from '../../hooks'
|
||||
|
||||
type MarketplaceTriggerProps = {
|
||||
selectedTagsLength: number
|
||||
open: boolean
|
||||
tags: string[]
|
||||
tagsMap: Record<string, Tag>
|
||||
locale?: string
|
||||
onTagsChange: (tags: string[]) => void
|
||||
}
|
||||
|
||||
const MarketplaceTrigger = ({
|
||||
selectedTagsLength,
|
||||
open,
|
||||
tags,
|
||||
tagsMap,
|
||||
locale,
|
||||
onTagsChange,
|
||||
}: MarketplaceTriggerProps) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex h-8 cursor-pointer select-none items-center rounded-lg px-2 py-1 text-text-tertiary',
|
||||
!!selectedTagsLength && 'border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs shadow-shadow-shadow-3',
|
||||
open && !selectedTagsLength && 'bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<div className='p-0.5'>
|
||||
<RiFilter3Line className={cn('size-4', !!selectedTagsLength && 'text-text-secondary')} />
|
||||
</div>
|
||||
<div className='system-sm-medium flex items-center gap-x-1 p-1'>
|
||||
{
|
||||
!selectedTagsLength && <span>{t('pluginTags.allTags')}</span>
|
||||
}
|
||||
{
|
||||
!!selectedTagsLength && (
|
||||
<span className='text-text-secondary'>
|
||||
{tags.map(tag => tagsMap[tag].label).slice(0, 2).join(',')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{
|
||||
selectedTagsLength > 2 && (
|
||||
<div className='system-xs-medium text-text-tertiary'>
|
||||
+{selectedTagsLength - 2}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
!!selectedTagsLength && (
|
||||
<RiCloseCircleFill
|
||||
className='size-4 text-text-quaternary'
|
||||
onClick={() => onTagsChange([])}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!selectedTagsLength && (
|
||||
<div className='p-0.5'>
|
||||
<RiArrowDownSLine className='size-4 text-text-tertiary' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(MarketplaceTrigger)
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from 'react'
|
||||
import type { Tag } from '../../../hooks'
|
||||
import cn from '@/utils/classnames'
|
||||
import { RiCloseCircleFill, RiPriceTag3Line } from '@remixicon/react'
|
||||
|
||||
type ToolSelectorTriggerProps = {
|
||||
selectedTagsLength: number
|
||||
open: boolean
|
||||
tags: string[]
|
||||
tagsMap: Record<string, Tag>
|
||||
onTagsChange: (tags: string[]) => void
|
||||
}
|
||||
|
||||
const ToolSelectorTrigger = ({
|
||||
selectedTagsLength,
|
||||
open,
|
||||
tags,
|
||||
tagsMap,
|
||||
onTagsChange,
|
||||
}: ToolSelectorTriggerProps) => {
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex h-7 cursor-pointer select-none items-center rounded-md p-0.5 text-text-tertiary',
|
||||
!selectedTagsLength && 'py-1 pl-1.5 pr-2',
|
||||
!!selectedTagsLength && 'border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg py-0.5 pl-1 pr-1.5 shadow-xs shadow-shadow-shadow-3',
|
||||
open && !selectedTagsLength && 'bg-state-base-hover',
|
||||
)}
|
||||
>
|
||||
<div className='p-0.5'>
|
||||
<RiPriceTag3Line className={cn('size-4', !!selectedTagsLength && 'text-text-secondary')} />
|
||||
</div>
|
||||
{
|
||||
!!selectedTagsLength && (
|
||||
<div className='system-sm-medium flex items-center gap-x-0.5 px-0.5 py-1'>
|
||||
<span className='text-text-secondary'>
|
||||
{tags.map(tag => tagsMap[tag].label).slice(0, 2).join(',')}
|
||||
</span>
|
||||
{
|
||||
selectedTagsLength > 2 && (
|
||||
<div className='system-xs-medium text-text-tertiary'>
|
||||
+{selectedTagsLength - 2}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!selectedTagsLength && (
|
||||
<RiCloseCircleFill
|
||||
className='size-4 text-text-quaternary'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onTagsChange([])
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ToolSelectorTrigger)
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
PluginsSearchParams,
|
||||
} from '@/app/components/plugins/marketplace/types'
|
||||
import {
|
||||
APP_VERSION,
|
||||
MARKETPLACE_API_PREFIX,
|
||||
} from '@/config'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
@@ -49,11 +50,15 @@ export const getMarketplacePluginsByCollectionId = async (collectionId: string,
|
||||
|
||||
try {
|
||||
const url = `${MARKETPLACE_API_PREFIX}/collections/${collectionId}/plugins`
|
||||
const headers = new Headers({
|
||||
'X-Dify-Version': APP_VERSION,
|
||||
})
|
||||
const marketplaceCollectionPluginsData = await globalThis.fetch(
|
||||
url,
|
||||
{
|
||||
cache: 'no-store',
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
category: query?.category,
|
||||
exclude: query?.exclude,
|
||||
@@ -83,7 +88,10 @@ export const getMarketplaceCollectionsAndPlugins = async (query?: CollectionsAnd
|
||||
marketplaceUrl += `&condition=${query.condition}`
|
||||
if (query?.type)
|
||||
marketplaceUrl += `&type=${query.type}`
|
||||
const marketplaceCollectionsData = await globalThis.fetch(marketplaceUrl, { cache: 'no-store' })
|
||||
const headers = new Headers({
|
||||
'X-Dify-Version': APP_VERSION,
|
||||
})
|
||||
const marketplaceCollectionsData = await globalThis.fetch(marketplaceUrl, { headers, cache: 'no-store' })
|
||||
const marketplaceCollectionsDataJson = await marketplaceCollectionsData.json()
|
||||
marketplaceCollections = marketplaceCollectionsDataJson.data.collections
|
||||
await Promise.all(marketplaceCollections.map(async (collection: MarketplaceCollection) => {
|
||||
@@ -117,6 +125,9 @@ export const getMarketplaceListCondition = (pluginType: string) => {
|
||||
if (pluginType === PluginType.extension)
|
||||
return 'category=endpoint'
|
||||
|
||||
if (pluginType === PluginType.datasource)
|
||||
return 'category=datasource'
|
||||
|
||||
if (pluginType === 'bundle')
|
||||
return 'type=bundle'
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import Button from '@/app/components/base/button'
|
||||
import type { ButtonProps } from '@/app/components/base/button'
|
||||
import ApiKeyModal from './api-key-modal'
|
||||
import type { PluginPayload } from '../types'
|
||||
import type { FormSchema } from '@/app/components/base/form/types'
|
||||
|
||||
export type AddApiKeyButtonProps = {
|
||||
pluginPayload: PluginPayload
|
||||
@@ -13,13 +14,15 @@ export type AddApiKeyButtonProps = {
|
||||
buttonText?: string
|
||||
disabled?: boolean
|
||||
onUpdate?: () => void
|
||||
formSchemas?: FormSchema[]
|
||||
}
|
||||
const AddApiKeyButton = ({
|
||||
pluginPayload,
|
||||
buttonVariant = 'secondary-accent',
|
||||
buttonText = 'use api key',
|
||||
buttonText = 'Use Api Key',
|
||||
disabled,
|
||||
onUpdate,
|
||||
formSchemas = [],
|
||||
}: AddApiKeyButtonProps) => {
|
||||
const [isApiKeyModalOpen, setIsApiKeyModalOpen] = useState(false)
|
||||
|
||||
@@ -39,6 +42,7 @@ const AddApiKeyButton = ({
|
||||
pluginPayload={pluginPayload}
|
||||
onClose={() => setIsApiKeyModalOpen(false)}
|
||||
onUpdate={onUpdate}
|
||||
formSchemas={formSchemas}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,13 @@ export type AddOAuthButtonProps = {
|
||||
dividerClassName?: string
|
||||
disabled?: boolean
|
||||
onUpdate?: () => void
|
||||
oAuthData?: {
|
||||
schema?: FormSchema[]
|
||||
is_oauth_custom_client_enabled?: boolean
|
||||
is_system_oauth_params_exists?: boolean
|
||||
client_params?: Record<string, any>
|
||||
redirect_uri?: string
|
||||
}
|
||||
}
|
||||
const AddOAuthButton = ({
|
||||
pluginPayload,
|
||||
@@ -47,19 +54,26 @@ const AddOAuthButton = ({
|
||||
dividerClassName,
|
||||
disabled,
|
||||
onUpdate,
|
||||
oAuthData,
|
||||
}: AddOAuthButtonProps) => {
|
||||
const { t } = useTranslation()
|
||||
const renderI18nObject = useRenderI18nObject()
|
||||
const [isOAuthSettingsOpen, setIsOAuthSettingsOpen] = useState(false)
|
||||
const { mutateAsync: getPluginOAuthUrl } = useGetPluginOAuthUrlHook(pluginPayload)
|
||||
const { data, isLoading } = useGetPluginOAuthClientSchemaHook(pluginPayload)
|
||||
const mergedOAuthData = useMemo(() => {
|
||||
if (oAuthData)
|
||||
return oAuthData
|
||||
|
||||
return data
|
||||
}, [oAuthData, data])
|
||||
const {
|
||||
schema = [],
|
||||
is_oauth_custom_client_enabled,
|
||||
is_system_oauth_params_exists,
|
||||
client_params,
|
||||
redirect_uri,
|
||||
} = data || {}
|
||||
} = mergedOAuthData as any || {}
|
||||
const isConfigured = is_system_oauth_params_exists || is_oauth_custom_client_enabled
|
||||
const handleOAuth = useCallback(async () => {
|
||||
const { authorization_url } = await getPluginOAuthUrl()
|
||||
@@ -86,7 +100,7 @@ const AddOAuthButton = ({
|
||||
{
|
||||
redirect_uri && (
|
||||
<div className='system-sm-medium flex w-full py-0.5'>
|
||||
<div className='w-0 grow break-words'>{redirect_uri}</div>
|
||||
<div className='w-0 grow break-words break-all'>{redirect_uri}</div>
|
||||
<ActionButton
|
||||
className='shrink-0'
|
||||
onClick={() => {
|
||||
@@ -112,7 +126,7 @@ const AddOAuthButton = ({
|
||||
)
|
||||
}, [t, redirect_uri, renderI18nObject])
|
||||
const memorizedSchemas = useMemo(() => {
|
||||
const result: FormSchema[] = schema.map((item, index) => {
|
||||
const result: FormSchema[] = (schema as FormSchema[]).map((item, index) => {
|
||||
return {
|
||||
...item,
|
||||
label: index === 0 ? renderCustomLabel(item) : item.label,
|
||||
|
||||
@@ -10,7 +10,10 @@ import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
|
||||
import Modal from '@/app/components/base/modal/modal'
|
||||
import { CredentialTypeEnum } from '../types'
|
||||
import AuthForm from '@/app/components/base/form/form-scenarios/auth'
|
||||
import type { FormRefObject } from '@/app/components/base/form/types'
|
||||
import type {
|
||||
FormRefObject,
|
||||
FormSchema,
|
||||
} from '@/app/components/base/form/types'
|
||||
import { FormTypeEnum } from '@/app/components/base/form/types'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
@@ -28,6 +31,7 @@ export type ApiKeyModalProps = {
|
||||
onRemove?: () => void
|
||||
disabled?: boolean
|
||||
onUpdate?: () => void
|
||||
formSchemas?: FormSchema[]
|
||||
}
|
||||
const ApiKeyModal = ({
|
||||
pluginPayload,
|
||||
@@ -36,6 +40,7 @@ const ApiKeyModal = ({
|
||||
onRemove,
|
||||
disabled,
|
||||
onUpdate,
|
||||
formSchemas: formSchemasFromProps = [],
|
||||
}: ApiKeyModalProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
@@ -46,6 +51,12 @@ const ApiKeyModal = ({
|
||||
setDoingAction(value)
|
||||
}, [])
|
||||
const { data = [], isLoading } = useGetPluginCredentialSchemaHook(pluginPayload, CredentialTypeEnum.API_KEY)
|
||||
const mergedData = useMemo(() => {
|
||||
if (formSchemasFromProps?.length)
|
||||
return formSchemasFromProps
|
||||
|
||||
return data
|
||||
}, [formSchemasFromProps, data])
|
||||
const formSchemas = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@@ -54,9 +65,9 @@ const ApiKeyModal = ({
|
||||
label: t('plugin.auth.authorizationName'),
|
||||
required: false,
|
||||
},
|
||||
...data,
|
||||
...mergedData,
|
||||
]
|
||||
}, [data, t])
|
||||
}, [mergedData, t])
|
||||
const defaultValues = formSchemas.reduce((acc, schema) => {
|
||||
if (schema.default)
|
||||
acc[schema.name] = schema.default
|
||||
@@ -150,7 +161,7 @@ const ApiKeyModal = ({
|
||||
)
|
||||
}
|
||||
{
|
||||
!isLoading && !!data.length && (
|
||||
!isLoading && !!mergedData.length && (
|
||||
<AuthForm
|
||||
ref={formRef}
|
||||
formSchemas={formSchemas}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
memo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiEqualizer2Line } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type AuthorizedInDataSourceNodeProps = {
|
||||
authorizationsNum: number
|
||||
onJumpToDataSourcePage: () => void
|
||||
}
|
||||
const AuthorizedInDataSourceNode = ({
|
||||
authorizationsNum,
|
||||
onJumpToDataSourcePage,
|
||||
}: AuthorizedInDataSourceNodeProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Button
|
||||
size='small'
|
||||
onClick={onJumpToDataSourcePage}
|
||||
>
|
||||
<Indicator
|
||||
className='mr-1.5'
|
||||
color='green'
|
||||
/>
|
||||
{
|
||||
authorizationsNum > 1
|
||||
? t('plugin.auth.authorizations')
|
||||
: t('plugin.auth.authorization')
|
||||
}
|
||||
<RiEqualizer2Line
|
||||
className={cn(
|
||||
'h-3.5 w-3.5 text-components-button-ghost-text',
|
||||
)}
|
||||
/>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(AuthorizedInDataSourceNode)
|
||||
@@ -36,14 +36,22 @@ const AuthorizedInNode = ({
|
||||
disabled,
|
||||
invalidPluginCredentialInfo,
|
||||
notAllowCustomCredential,
|
||||
} = usePluginAuth(pluginPayload, isOpen || !!credentialId)
|
||||
} = usePluginAuth(pluginPayload, true)
|
||||
const renderTrigger = useCallback((open?: boolean) => {
|
||||
let label = ''
|
||||
let removed = false
|
||||
let unavailable = false
|
||||
let color = 'green'
|
||||
let defaultUnavailable = false
|
||||
if (!credentialId) {
|
||||
label = t('plugin.auth.workspaceDefault')
|
||||
|
||||
const defaultCredential = credentials.find(c => c.is_default)
|
||||
|
||||
if (defaultCredential?.not_allowed_to_use) {
|
||||
color = 'gray'
|
||||
defaultUnavailable = true
|
||||
}
|
||||
}
|
||||
else {
|
||||
const credential = credentials.find(c => c.id === credentialId)
|
||||
@@ -63,6 +71,7 @@ const AuthorizedInNode = ({
|
||||
open && !removed && 'bg-components-button-ghost-bg-hover',
|
||||
removed && 'bg-transparent text-text-destructive',
|
||||
)}
|
||||
variant={(defaultUnavailable || unavailable) ? 'ghost' : 'secondary'}
|
||||
>
|
||||
<Indicator
|
||||
className='mr-1.5'
|
||||
@@ -70,7 +79,12 @@ const AuthorizedInNode = ({
|
||||
/>
|
||||
{label}
|
||||
{
|
||||
unavailable && t('plugin.auth.unavailable')
|
||||
(unavailable || defaultUnavailable) && (
|
||||
<>
|
||||
|
||||
{t('plugin.auth.unavailable')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
<RiArrowDownSLine
|
||||
className={cn(
|
||||
@@ -81,6 +95,7 @@ const AuthorizedInNode = ({
|
||||
</Button>
|
||||
)
|
||||
}, [credentialId, credentials, t])
|
||||
const defaultUnavailable = credentials.find(c => c.is_default)?.not_allowed_to_use
|
||||
const extraAuthorizationItems: Credential[] = [
|
||||
{
|
||||
id: '__workspace_default__',
|
||||
@@ -88,6 +103,7 @@ const AuthorizedInNode = ({
|
||||
provider: '',
|
||||
is_default: !credentialId,
|
||||
isWorkspaceDefault: true,
|
||||
not_allowed_to_use: defaultUnavailable,
|
||||
},
|
||||
]
|
||||
const handleAuthorizationItemClick = useCallback((id: string) => {
|
||||
|
||||
@@ -174,6 +174,7 @@ const Authorized = ({
|
||||
}
|
||||
}, [updatePluginCredential, notify, t, handleSetDoingAction, onUpdate])
|
||||
const unavailableCredentials = credentials.filter(credential => credential.not_allowed_to_use)
|
||||
const unavailableCredential = credentials.find(credential => credential.not_allowed_to_use && credential.is_default)
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -197,7 +198,7 @@ const Authorized = ({
|
||||
'w-full',
|
||||
isOpen && 'bg-components-button-secondary-bg-hover',
|
||||
)}>
|
||||
<Indicator className='mr-2' />
|
||||
<Indicator className='mr-2' color={unavailableCredential ? 'gray' : 'green'} />
|
||||
{credentials.length}
|
||||
{
|
||||
credentials.length > 1
|
||||
|
||||
@@ -24,6 +24,22 @@ export const useGetApi = ({ category = AuthCategory.tool, provider }: PluginPayl
|
||||
}
|
||||
}
|
||||
|
||||
if (category === AuthCategory.datasource) {
|
||||
return {
|
||||
getCredentialInfo: '',
|
||||
setDefaultCredential: `/auth/plugin/datasource/${provider}/default`,
|
||||
getCredentials: `/auth/plugin/datasource/${provider}`,
|
||||
addCredential: `/auth/plugin/datasource/${provider}`,
|
||||
updateCredential: `/auth/plugin/datasource/${provider}/update`,
|
||||
deleteCredential: `/auth/plugin/datasource/${provider}/delete`,
|
||||
getCredentialSchema: () => '',
|
||||
getOauthUrl: `/oauth/plugin/${provider}/datasource/get-authorization-url`,
|
||||
getOauthClientSchema: '',
|
||||
setCustomOauthClient: `/auth/plugin/datasource/${provider}/custom-client`,
|
||||
deleteCustomOAuthClient: `/auth/plugin/datasource/${provider}/custom-client`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getCredentialInfo: '',
|
||||
setDefaultCredential: '',
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import type { PluginPayload } from '@/app/components/plugins/plugin-auth/types'
|
||||
import type { PluginPayload } from '../types'
|
||||
import {
|
||||
useDeletePluginCredentialHook,
|
||||
useSetPluginDefaultCredentialHook,
|
||||
|
||||
@@ -3,4 +3,10 @@ export { default as Authorized } from './authorized'
|
||||
export { default as AuthorizedInNode } from './authorized-in-node'
|
||||
export { default as PluginAuthInAgent } from './plugin-auth-in-agent'
|
||||
export { usePluginAuth } from './hooks/use-plugin-auth'
|
||||
export { default as PluginAuthInDataSourceNode } from './plugin-auth-in-datasource-node'
|
||||
export { default as AuthorizedInDataSourceNode } from './authorized-in-data-source-node'
|
||||
export { default as AddOAuthButton } from './authorize/add-oauth-button'
|
||||
export { default as AddApiKeyButton } from './authorize/add-api-key-button'
|
||||
export { default as ApiKeyModal } from './authorize/api-key-modal'
|
||||
export * from './hooks/use-plugin-auth-action'
|
||||
export * from './types'
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { memo } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type PluginAuthInDataSourceNodeProps = {
|
||||
children?: ReactNode
|
||||
isAuthorized?: boolean
|
||||
onJumpToDataSourcePage: () => void
|
||||
}
|
||||
const PluginAuthInDataSourceNode = ({
|
||||
children,
|
||||
isAuthorized,
|
||||
onJumpToDataSourcePage,
|
||||
}: PluginAuthInDataSourceNodeProps) => {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<>
|
||||
{
|
||||
!isAuthorized && (
|
||||
<div className='px-4 pb-2'>
|
||||
<Button
|
||||
className='w-full'
|
||||
variant='primary'
|
||||
onClick={onJumpToDataSourcePage}
|
||||
>
|
||||
<RiAddLine className='mr-1 h-4 w-4' />
|
||||
{t('common.integrations.connect')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{isAuthorized && children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(PluginAuthInDataSourceNode)
|
||||
@@ -1,3 +1,6 @@
|
||||
export type { AddApiKeyButtonProps } from './authorize/add-api-key-button'
|
||||
export type { AddOAuthButtonProps } from './authorize/add-oauth-button'
|
||||
|
||||
export enum AuthCategory {
|
||||
tool = 'tool',
|
||||
datasource = 'datasource',
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
// import { useAppContext } from '@/context/app-context'
|
||||
// import Button from '@/app/components/base/button'
|
||||
// import Toast from '@/app/components/base/toast'
|
||||
// import Indicator from '@/app/components/header/indicator'
|
||||
// import ToolItem from '@/app/components/tools/provider/tool-item'
|
||||
// import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
|
||||
import type { PluginDetail } from '@/app/components/plugins/types'
|
||||
import { useDataSourceList } from '@/service/use-pipeline'
|
||||
import { transformDataSourceToTool } from '@/app/components/workflow/block-selector/utils'
|
||||
|
||||
type Props = {
|
||||
detail: PluginDetail
|
||||
}
|
||||
|
||||
const ActionList = ({
|
||||
detail,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
// const { isCurrentWorkspaceManager } = useAppContext()
|
||||
// const providerBriefInfo = detail.declaration.datasource?.identity
|
||||
// const providerKey = `${detail.plugin_id}/${providerBriefInfo?.name}`
|
||||
const { data: dataSourceList } = useDataSourceList(true)
|
||||
const provider = useMemo(() => {
|
||||
const result = dataSourceList?.find(collection => collection.plugin_id === detail.plugin_id)
|
||||
|
||||
if (result)
|
||||
return transformDataSourceToTool(result)
|
||||
}, [detail.plugin_id, dataSourceList])
|
||||
const data: any = []
|
||||
// const { data } = useBuiltinTools(providerKey)
|
||||
|
||||
// const [showSettingAuth, setShowSettingAuth] = useState(false)
|
||||
|
||||
// const handleCredentialSettingUpdate = () => {
|
||||
// Toast.notify({
|
||||
// type: 'success',
|
||||
// message: t('common.api.actionSuccess'),
|
||||
// })
|
||||
// setShowSettingAuth(false)
|
||||
// }
|
||||
|
||||
// const { mutate: updatePermission, isPending } = useUpdateProviderCredentials({
|
||||
// onSuccess: handleCredentialSettingUpdate,
|
||||
// })
|
||||
|
||||
// const { mutate: removePermission } = useRemoveProviderCredentials({
|
||||
// onSuccess: handleCredentialSettingUpdate,
|
||||
// })
|
||||
|
||||
if (!data || !provider)
|
||||
return null
|
||||
|
||||
return (
|
||||
<div className='px-4 pb-4 pt-2'>
|
||||
<div className='mb-1 py-1'>
|
||||
<div className='system-sm-semibold-uppercase mb-1 flex h-6 items-center justify-between text-text-secondary'>
|
||||
{t('plugin.detailPanel.actionNum', { num: data.length, action: data.length > 1 ? 'actions' : 'action' })}
|
||||
{/* {provider.is_team_authorization && provider.allow_delete && (
|
||||
<Button
|
||||
variant='secondary'
|
||||
size='small'
|
||||
onClick={() => setShowSettingAuth(true)}
|
||||
disabled={!isCurrentWorkspaceManager}
|
||||
>
|
||||
<Indicator className='mr-2' color={'green'} />
|
||||
{t('tools.auth.authorized')}
|
||||
</Button>
|
||||
)} */}
|
||||
</div>
|
||||
{/* {!provider.is_team_authorization && provider.allow_delete && (
|
||||
<Button
|
||||
variant='primary'
|
||||
className='w-full'
|
||||
onClick={() => setShowSettingAuth(true)}
|
||||
disabled={!isCurrentWorkspaceManager}
|
||||
>{t('workflow.nodes.tool.authorize')}</Button>
|
||||
)} */}
|
||||
</div>
|
||||
{/* <div className='flex flex-col gap-2'>
|
||||
{data.map(tool => (
|
||||
<ToolItem
|
||||
key={`${detail.plugin_id}${tool.name}`}
|
||||
disabled={false}
|
||||
collection={provider}
|
||||
tool={tool}
|
||||
isBuiltIn={true}
|
||||
isModel={false}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{showSettingAuth && (
|
||||
<ConfigCredential
|
||||
collection={provider}
|
||||
onCancel={() => setShowSettingAuth(false)}
|
||||
onSaved={async value => updatePermission({
|
||||
providerName: provider.name,
|
||||
credentials: value,
|
||||
})}
|
||||
onRemove={async () => removePermission(provider.name)}
|
||||
isSaving={isPending}
|
||||
/>
|
||||
)} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ActionList
|
||||
@@ -4,6 +4,7 @@ import type { FC } from 'react'
|
||||
import DetailHeader from './detail-header'
|
||||
import EndpointList from './endpoint-list'
|
||||
import ActionList from './action-list'
|
||||
import DatasourceActionList from './datasource-action-list'
|
||||
import ModelList from './model-list'
|
||||
import AgentStrategyList from './agent-strategy-list'
|
||||
import { SubscriptionList } from './subscription-list'
|
||||
@@ -67,6 +68,7 @@ const PluginDetailPanel: FC<Props> = ({
|
||||
{!!detail.declaration.agent_strategy && <AgentStrategyList detail={detail} />}
|
||||
{!!detail.declaration.endpoint && <EndpointList detail={detail} />}
|
||||
{!!detail.declaration.model && <ModelList detail={detail} />}
|
||||
{!!detail.declaration.datasource && <DatasourceActionList detail={detail} />}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -136,6 +136,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
provider,
|
||||
model,
|
||||
value?.completion_params,
|
||||
isAdvancedMode,
|
||||
)
|
||||
nextCompletionParams = filtered
|
||||
|
||||
@@ -165,7 +166,7 @@ const ModelParameterModal: FC<ModelParameterModalProps> = ({
|
||||
|
||||
const handleLLMParamsChange = (newParams: FormValue) => {
|
||||
const newValue = {
|
||||
...(value?.completionParams || {}),
|
||||
...value?.completionParams,
|
||||
completion_params: newParams,
|
||||
}
|
||||
setModel({
|
||||
|
||||
@@ -10,29 +10,9 @@ import type {
|
||||
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { ParameterValue } from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal/parameter-item'
|
||||
import { fetchModelParameterRules } from '@/service/common'
|
||||
import { TONE_LIST } from '@/config'
|
||||
import { PROVIDER_WITH_PRESET_TONE, STOP_PARAMETER_RULE, TONE_LIST } from '@/config'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const PROVIDER_WITH_PRESET_TONE = ['langgenius/openai/openai', 'langgenius/azure_openai/azure_openai']
|
||||
const stopParameterRule: ModelParameterRule = {
|
||||
default: [],
|
||||
help: {
|
||||
en_US: 'Up to four sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence.',
|
||||
zh_Hans: '最多四个序列,API 将停止生成更多的 token。返回的文本将不包含停止序列。',
|
||||
},
|
||||
label: {
|
||||
en_US: 'Stop sequences',
|
||||
zh_Hans: '停止序列',
|
||||
},
|
||||
name: 'stop',
|
||||
required: false,
|
||||
type: 'tag',
|
||||
tagPlaceholder: {
|
||||
en_US: 'Enter sequence and press Tab',
|
||||
zh_Hans: '输入序列并按 Tab 键',
|
||||
},
|
||||
}
|
||||
|
||||
type Props = {
|
||||
isAdvancedMode: boolean
|
||||
provider: string
|
||||
@@ -108,7 +88,7 @@ const LLMParamsPanel = ({
|
||||
{!!parameterRules.length && (
|
||||
[
|
||||
...parameterRules,
|
||||
...(isAdvancedMode ? [stopParameterRule] : []),
|
||||
...(isAdvancedMode ? [STOP_PARAMETER_RULE] : []),
|
||||
].map(parameter => (
|
||||
<ParameterItem
|
||||
key={`${modelId}-${parameter.name}`}
|
||||
|
||||
@@ -43,15 +43,15 @@ const StrategyDetail: FC<Props> = ({
|
||||
|
||||
const outputSchema = useMemo(() => {
|
||||
const res: any[] = []
|
||||
if (!detail.output_schema)
|
||||
if (!detail.output_schema || !detail.output_schema.properties)
|
||||
return []
|
||||
Object.keys(detail.output_schema.properties).forEach((outputKey) => {
|
||||
const output = detail.output_schema.properties[outputKey]
|
||||
res.push({
|
||||
name: outputKey,
|
||||
type: output.type === 'array'
|
||||
? `Array[${output.items?.type.slice(0, 1).toLocaleUpperCase()}${output.items?.type.slice(1)}]`
|
||||
: `${output.type.slice(0, 1).toLocaleUpperCase()}${output.type.slice(1)}`,
|
||||
? `Array[${output.items?.type ? output.items.type.slice(0, 1).toLocaleUpperCase() + output.items.type.slice(1) : 'Unknown'}]`
|
||||
: `${output.type ? output.type.slice(0, 1).toLocaleUpperCase() + output.type.slice(1) : 'Unknown'}`,
|
||||
description: output.description,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ const TagsFilter = ({
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const { tags: options, tagsMap } = useTags()
|
||||
const { tags: options, getTagLabel } = useTags()
|
||||
const filteredOptions = options.filter(option => option.name.toLowerCase().includes(searchText.toLowerCase()))
|
||||
const handleCheck = (id: string) => {
|
||||
if (value.includes(id))
|
||||
@@ -59,7 +59,7 @@ const TagsFilter = ({
|
||||
!selectedTagsLength && t('pluginTags.allTags')
|
||||
}
|
||||
{
|
||||
!!selectedTagsLength && value.map(val => tagsMap[val].label).slice(0, 2).join(',')
|
||||
!!selectedTagsLength && value.map(val => getTagLabel(val)).slice(0, 2).join(',')
|
||||
}
|
||||
{
|
||||
selectedTagsLength > 2 && (
|
||||
|
||||
@@ -131,7 +131,6 @@ const ToolPicker: FC<Props> = ({
|
||||
onSearchChange={setQuery}
|
||||
tags={tags}
|
||||
onTagsChange={setTags}
|
||||
size='small'
|
||||
placeholder={t('plugin.searchTools')!}
|
||||
inputClassName='w-full'
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,7 @@ export enum PluginType {
|
||||
model = 'model',
|
||||
extension = 'extension',
|
||||
agent = 'agent-strategy',
|
||||
datasource = 'datasource',
|
||||
trigger = 'trigger',
|
||||
}
|
||||
|
||||
@@ -78,7 +79,8 @@ export type PluginDeclaration = {
|
||||
plugins: any // useless in frontend
|
||||
verified: boolean
|
||||
endpoint: PluginEndpointDeclaration
|
||||
tool: PluginToolDeclaration
|
||||
tool?: PluginToolDeclaration
|
||||
datasource?: PluginToolDeclaration
|
||||
model: any
|
||||
tags: string[]
|
||||
agent_strategy: any
|
||||
|
||||
Reference in New Issue
Block a user