Merge branch 'feat/plugins' of https://github.com/langgenius/dify into feat/plugins

pull/12372/head
Yi 1 year ago
commit e34eebfb0b

@ -45,7 +45,7 @@ type FormProps<
props: Omit<FormProps<CustomFormSchema>, 'override' | 'customRenderField'> props: Omit<FormProps<CustomFormSchema>, 'override' | 'customRenderField'>
) => ReactNode ) => ReactNode
// If return falsy value, this field will fallback to default render // If return falsy value, this field will fallback to default render
override?: [Array<FormTypeEnum>, (formSchema: CredentialFormSchema) => ReactNode] override?: [Array<FormTypeEnum>, (formSchema: CredentialFormSchema, props: Omit<FormProps<CustomFormSchema>, 'override' | 'customRenderField'>) => ReactNode]
} }
function Form< function Form<
@ -71,6 +71,22 @@ function Form<
}: FormProps<CustomFormSchema>) { }: FormProps<CustomFormSchema>) {
const language = useLanguage() const language = useLanguage()
const [changeKey, setChangeKey] = useState('') const [changeKey, setChangeKey] = useState('')
const filteredProps: Omit<FormProps<CustomFormSchema>, 'override' | 'customRenderField'> = {
className,
itemClassName,
fieldLabelClassName,
value,
onChange,
formSchemas,
validating,
validatedSuccess,
showOnVariableMap,
isEditMode,
readonly,
inputClassName,
isShowDefaultValue,
fieldMoreInfo,
}
const handleFormChange = (key: string, val: string | boolean) => { const handleFormChange = (key: string, val: string | boolean) => {
if (isEditMode && (key === '__model_type' || key === '__model_name')) if (isEditMode && (key === '__model_type' || key === '__model_name'))
@ -108,7 +124,7 @@ function Form<
if (override) { if (override) {
const [overrideTypes, overrideRender] = override const [overrideTypes, overrideRender] = override
if (overrideTypes.includes(formSchema.type as FormTypeEnum)) { if (overrideTypes.includes(formSchema.type as FormTypeEnum)) {
const node = overrideRender(formSchema as CredentialFormSchema) const node = overrideRender(formSchema as CredentialFormSchema, filteredProps)
if (node) if (node)
return node return node
} }
@ -345,24 +361,8 @@ function Form<
} }
// @ts-expect-error it work // @ts-expect-error it work
if (!Object.values(FormTypeEnum).includes(formSchema.type)) { if (!Object.values(FormTypeEnum).includes(formSchema.type))
return customRenderField?.(formSchema as CustomFormSchema, { return customRenderField?.(formSchema as CustomFormSchema, filteredProps)
className,
itemClassName,
fieldLabelClassName,
value,
onChange,
formSchemas,
validating,
validatedSuccess,
showOnVariableMap,
isEditMode,
readonly,
inputClassName,
isShowDefaultValue,
fieldMoreInfo,
})
}
} }
return ( return (

@ -1,21 +1,23 @@
'use client' 'use client'
import { useTranslation } from 'react-i18next'
import { Group } from '@/app/components/base/icons/src/vender/other' import { Group } from '@/app/components/base/icons/src/vender/other'
import Line from './line' import Line from './line'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
type Props = { type Props = {
text?: string text?: string
lightCard?: boolean lightCard?: boolean
className?: string className?: string
locale?: string
} }
const Empty = ({ const Empty = ({
text, text,
lightCard, lightCard,
className, className,
locale,
}: Props) => { }: Props) => {
const { t } = useTranslation() const { t } = useMixedTranslation(locale)
return ( return (
<div <div

@ -69,7 +69,7 @@ const List = ({
} }
{ {
plugins && !plugins.length && ( plugins && !plugins.length && (
<Empty className={emptyClassName} /> <Empty className={emptyClassName} locale={locale} />
) )
} }
</> </>

@ -1,6 +1,5 @@
'use client' 'use client'
import { useTranslation } from 'react-i18next'
import { RiArrowRightSLine } from '@remixicon/react' import { RiArrowRightSLine } from '@remixicon/react'
import type { MarketplaceCollection } from '../types' import type { MarketplaceCollection } from '../types'
import CardWrapper from './card-wrapper' import CardWrapper from './card-wrapper'
@ -8,6 +7,7 @@ import type { Plugin } from '@/app/components/plugins/types'
import { getLanguage } from '@/i18n/language' import { getLanguage } from '@/i18n/language'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import type { SearchParamsFromCollection } from '@/app/components/plugins/marketplace/types' import type { SearchParamsFromCollection } from '@/app/components/plugins/marketplace/types'
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
type ListWithCollectionProps = { type ListWithCollectionProps = {
marketplaceCollections: MarketplaceCollection[] marketplaceCollections: MarketplaceCollection[]
@ -27,7 +27,7 @@ const ListWithCollection = ({
cardRender, cardRender,
onMoreClick, onMoreClick,
}: ListWithCollectionProps) => { }: ListWithCollectionProps) => {
const { t } = useTranslation() const { t } = useMixedTranslation(locale)
return ( return (
<> <>

@ -1,12 +1,12 @@
'use client' 'use client'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import type { Plugin } from '../../types' import type { Plugin } from '../../types'
import type { MarketplaceCollection } from '../types' import type { MarketplaceCollection } from '../types'
import { useMarketplaceContext } from '../context' import { useMarketplaceContext } from '../context'
import List from './index' import List from './index'
import SortDropdown from '../sort-dropdown' import SortDropdown from '../sort-dropdown'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
type ListWrapperProps = { type ListWrapperProps = {
marketplaceCollections: MarketplaceCollection[] marketplaceCollections: MarketplaceCollection[]
@ -20,7 +20,7 @@ const ListWrapper = ({
showInstallButton, showInstallButton,
locale, locale,
}: ListWrapperProps) => { }: ListWrapperProps) => {
const { t } = useTranslation() const { t } = useMixedTranslation(locale)
const plugins = useMarketplaceContext(v => v.plugins) const plugins = useMarketplaceContext(v => v.plugins)
const pluginsTotal = useMarketplaceContext(v => v.pluginsTotal) const pluginsTotal = useMarketplaceContext(v => v.pluginsTotal)
const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient) const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient)
@ -43,7 +43,7 @@ const ListWrapper = ({
<div className='top-5 flex items-center mb-4 pt-3'> <div className='top-5 flex items-center mb-4 pt-3'>
<div className='title-xl-semi-bold text-text-primary'>{t('plugin.marketplace.pluginsResult', { num: pluginsTotal })}</div> <div className='title-xl-semi-bold text-text-primary'>{t('plugin.marketplace.pluginsResult', { num: pluginsTotal })}</div>
<div className='mx-3 w-[1px] h-3.5 bg-divider-regular'></div> <div className='mx-3 w-[1px] h-3.5 bg-divider-regular'></div>
<SortDropdown /> <SortDropdown locale={locale} />
</div> </div>
) )
} }

@ -4,7 +4,7 @@ import {
RiBrain2Line, RiBrain2Line,
RiHammerLine, RiHammerLine,
RiPuzzle2Line, RiPuzzle2Line,
RiUmbrellaLine, RiSpeakAiLine,
} from '@remixicon/react' } from '@remixicon/react'
import { PluginType } from '../types' import { PluginType } from '../types'
import { useMarketplaceContext } from './context' import { useMarketplaceContext } from './context'
@ -50,7 +50,7 @@ const PluginTypeSwitch = ({
{ {
value: PLUGIN_TYPE_SEARCH_MAP.agent, value: PLUGIN_TYPE_SEARCH_MAP.agent,
text: t('plugin.category.agents'), text: t('plugin.category.agents'),
icon: <RiUmbrellaLine className='mr-1.5 w-4 h-4' />, icon: <RiSpeakAiLine className='mr-1.5 w-4 h-4' />,
}, },
{ {
value: PLUGIN_TYPE_SEARCH_MAP.extension, value: PLUGIN_TYPE_SEARCH_MAP.extension,

@ -4,16 +4,21 @@ import {
RiArrowDownSLine, RiArrowDownSLine,
RiCheckLine, RiCheckLine,
} from '@remixicon/react' } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { useMarketplaceContext } from '../context' import { useMarketplaceContext } from '../context'
import { import {
PortalToFollowElem, PortalToFollowElem,
PortalToFollowElemContent, PortalToFollowElemContent,
PortalToFollowElemTrigger, PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem' } from '@/app/components/base/portal-to-follow-elem'
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
const SortDropdown = () => { type SortDropdownProps = {
const { t } = useTranslation() locale?: string
}
const SortDropdown = ({
locale,
}: SortDropdownProps) => {
const { t } = useMixedTranslation(locale)
const options = [ const options = [
{ {
value: 'install_count', value: 'install_count',

@ -1,4 +1,5 @@
import type { CredentialFormSchema } from '@/app/components/header/account-setting/model-provider-page/declarations' import type { CredentialFormSchemaNumberInput } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { type CredentialFormSchema, FormTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { ToolVarInputs } from '../../tool/types' import type { ToolVarInputs } from '../../tool/types'
import ListEmpty from '@/app/components/base/list-empty' import ListEmpty from '@/app/components/base/list-empty'
import { AgentStrategySelector } from './agent-strategy-selector' import { AgentStrategySelector } from './agent-strategy-selector'
@ -11,6 +12,7 @@ import Slider from '@/app/components/base/slider'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector' import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import Field from './field' import Field from './field'
import type { ComponentProps } from 'react' import type { ComponentProps } from 'react'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
export type Strategy = { export type Strategy = {
agent_strategy_provider_name: string agent_strategy_provider_name: string
@ -29,11 +31,10 @@ export type AgentStrategyProps = {
type CustomSchema<Type, Field = {}> = Omit<CredentialFormSchema, 'type'> & { type: Type } & Field type CustomSchema<Type, Field = {}> = Omit<CredentialFormSchema, 'type'> & { type: Type } & Field
type MaxIterFormSchema = CustomSchema<'max-iter'>
type ToolSelectorSchema = CustomSchema<'tool-selector'> type ToolSelectorSchema = CustomSchema<'tool-selector'>
type MultipleToolSelectorSchema = CustomSchema<'array[tools]'> type MultipleToolSelectorSchema = CustomSchema<'array[tools]'>
type CustomField = MaxIterFormSchema | ToolSelectorSchema | MultipleToolSelectorSchema type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema
const devMockForm = [{ const devMockForm = [{
name: 'model', name: 'model',
@ -114,36 +115,77 @@ const devMockForm = [{
max: null, max: null,
options: [], options: [],
type: 'string', type: 'string',
},
{
name: 'max iterations',
label: {
en_US: 'Max Iterations',
zh_Hans: '最大迭代次数',
pt_BR: 'Max Iterations',
ja_JP: 'Max Iterations',
},
placeholder: null,
scope: null,
auto_generate: null,
template: null,
required: true,
default: '1',
min: 1,
max: 10,
type: FormTypeEnum.textNumber,
tooltip: {
en_US: 'The maximum number of iterations to run',
zh_Hans: '运行的最大迭代次数',
pt_BR: 'The maximum number of iterations to run',
ja_JP: 'The maximum number of iterations to run',
},
}] }]
export const AgentStrategy = (props: AgentStrategyProps) => { export const AgentStrategy = (props: AgentStrategyProps) => {
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange } = props const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange } = props
const { t } = useTranslation() const { t } = useTranslation()
const renderField: ComponentProps<typeof Form<CustomField>>['customRenderField'] = (schema, props) => { const language = useLanguage()
switch (schema.type) { const override: ComponentProps<typeof Form<CustomField>>['override'] = [
case 'max-iter': { [FormTypeEnum.textNumber],
const defaultValue = schema.default ? Number.parseInt(schema.default) : 1 (schema, props) => {
const value = props.value[schema.variable] || defaultValue switch (schema.type) {
const onChange = (value: number) => { case FormTypeEnum.textNumber: {
props.onChange({ ...props.value, [schema.variable]: value }) const def = schema as CredentialFormSchemaNumberInput
if (!def.max || !def.min)
return false
const defaultValue = schema.default ? Number.parseInt(schema.default) : 1
const value = props.value[schema.variable] || defaultValue
const onChange = (value: number) => {
props.onChange({ ...props.value, [schema.variable]: value })
}
return <Field title={def.label[language]} tooltip={def.tooltip?.[language]} inline>
<div className='flex w-[200px] items-center gap-3'>
<Slider
value={value}
onChange={onChange}
className='w-full'
min={def.min}
max={def.max}
/>
<InputNumber
value={value}
// TODO: maybe empty, handle this
onChange={onChange as any}
defaultValue={defaultValue}
size='sm'
min={def.min}
max={def.max}
className='w-12'
/>
</div>
</Field>
} }
return <Field title={t('workflow.nodes.agent.maxIterations')} tooltip={'max iter'} inline>
<div className='flex w-[200px] items-center gap-3'>
<Slider value={value} onChange={onChange} className='w-full' min={1} max={10} />
<InputNumber
value={value}
// TODO: maybe empty, handle this
onChange={onChange as any}
defaultValue={defaultValue}
size='sm'
min={1}
max={10}
className='w-12'
placeholder=''
/>
</div>
</Field>
} }
},
]
const renderField: ComponentProps<typeof Form<CustomField>>['customRenderField'] = (schema, props) => {
switch (schema.type) {
case 'tool-selector': { case 'tool-selector': {
const value = props.value[schema.variable] const value = props.value[schema.variable]
const onChange = (value: any) => { const onChange = (value: any) => {
@ -183,6 +225,7 @@ export const AgentStrategy = (props: AgentStrategyProps) => {
isAgentStrategy={true} isAgentStrategy={true}
fieldLabelClassName='uppercase' fieldLabelClassName='uppercase'
customRenderField={renderField} customRenderField={renderField}
override={override}
/> />
</div> </div>
: <ListEmpty : <ListEmpty

@ -30,7 +30,6 @@ const TracingPanel: FC<TracingPanelProps> = ({
hideNodeProcessDetail = false, hideNodeProcessDetail = false,
}) => { }) => {
const treeNodes = list const treeNodes = list
console.log(treeNodes)
const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(new Set()) const [collapsedNodes, setCollapsedNodes] = useState<Set<string>>(new Set())
const [hoveredParallel, setHoveredParallel] = useState<string | null>(null) const [hoveredParallel, setHoveredParallel] = useState<string | null>(null)
@ -84,7 +83,6 @@ const TracingPanel: FC<TracingPanelProps> = ({
setAgentResultList, setAgentResultList,
} = useLogs() } = useLogs()
const renderNode = (node: NodeTracing) => { const renderNode = (node: NodeTracing) => {
const isParallelFirstNode = !!node.parallelDetail?.isParallelStartNode const isParallelFirstNode = !!node.parallelDetail?.isParallelStartNode
if (isParallelFirstNode) { if (isParallelFirstNode) {

@ -65,10 +65,11 @@ const format = (list: NodeTracing[], t: any): NodeTracing[] => {
// console.log(list) // console.log(list)
const result: NodeTracing[] = [...list] const result: NodeTracing[] = [...list]
const parallelFirstNodeMap: Record<string, string> = {} const parallelFirstNodeMap: Record<string, string> = {}
// list to tree by parent_parallel_start_node_id and parallel_start_node_id // list to tree by parent_parallel_start_node_id and branch by parallel_start_node_id. Each parallel may has more than one branch.
result.forEach((node) => { result.forEach((node) => {
const parallel_id = node.parallel_id ?? node.execution_metadata?.parallel_id ?? null const parallel_id = node.parallel_id ?? node.execution_metadata?.parallel_id ?? null
const parent_parallel_id = node.parent_parallel_id ?? node.execution_metadata?.parent_parallel_id ?? null const parent_parallel_id = node.parent_parallel_id ?? node.execution_metadata?.parent_parallel_id ?? null
const branchStartNodeId = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null
const parent_parallel_start_node_id = node.parent_parallel_start_node_id ?? node.execution_metadata?.parent_parallel_start_node_id ?? null const parent_parallel_start_node_id = node.parent_parallel_start_node_id ?? node.execution_metadata?.parent_parallel_start_node_id ?? null
const isNotInParallel = !parallel_id || node.node_type === BlockEnum.End const isNotInParallel = !parallel_id || node.node_type === BlockEnum.End
if (isNotInParallel) if (isNotInParallel)
@ -100,10 +101,21 @@ const format = (list: NodeTracing[], t: any): NodeTracing[] => {
return return
} }
// append to parallel start node // append to parallel start node and after the same branch
const parallelStartNode = result.find(item => item.node_id === parallelFirstNodeMap[parallel_id]) const parallelStartNode = result.find(item => item.node_id === parallelFirstNodeMap[parallel_id])
if (parallelStartNode && parallelStartNode.parallelDetail && parallelStartNode!.parallelDetail!.children) if (parallelStartNode && parallelStartNode.parallelDetail && parallelStartNode!.parallelDetail!.children) {
parallelStartNode!.parallelDetail!.children.push(node) const sameBranchNodesLastIndex = parallelStartNode.parallelDetail.children.findLastIndex((node) => {
const currStartNodeId = node.parallel_start_node_id ?? node.execution_metadata?.parallel_start_node_id ?? null
return currStartNodeId === branchStartNodeId
})
if (sameBranchNodesLastIndex !== -1) {
parallelStartNode.parallelDetail.children.splice(sameBranchNodesLastIndex + 1, 0, node)
}
else { // new branch
parallelStartNode.parallelDetail.children.push(node)
}
}
// parallelStartNode!.parallelDetail!.children.push(node)
}) })
const filteredInParallelSubNodes = result.filter((node) => { const filteredInParallelSubNodes = result.filter((node) => {

@ -3,7 +3,7 @@ const translation = {
all: '全部', all: '全部',
models: '模型', models: '模型',
tools: '工具', tools: '工具',
agents: 'Agent Strategy', agents: 'Agent 策略',
extensions: '扩展', extensions: '扩展',
bundles: '插件集', bundles: '插件集',
}, },

Loading…
Cancel
Save