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

This commit is contained in:
Yi
2024-12-27 13:58:05 +08:00
11 changed files with 126 additions and 66 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
'use client'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import type { Plugin } from '../../types'
import type { MarketplaceCollection } from '../types'
import { useMarketplaceContext } from '../context'
import List from './index'
import SortDropdown from '../sort-dropdown'
import Loading from '@/app/components/base/loading'
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
type ListWrapperProps = {
marketplaceCollections: MarketplaceCollection[]
@@ -20,7 +20,7 @@ const ListWrapper = ({
showInstallButton,
locale,
}: ListWrapperProps) => {
const { t } = useTranslation()
const { t } = useMixedTranslation(locale)
const plugins = useMarketplaceContext(v => v.plugins)
const pluginsTotal = useMarketplaceContext(v => v.pluginsTotal)
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='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>
<SortDropdown />
<SortDropdown locale={locale} />
</div>
)
}

View File

@@ -4,7 +4,7 @@ import {
RiBrain2Line,
RiHammerLine,
RiPuzzle2Line,
RiUmbrellaLine,
RiSpeakAiLine,
} from '@remixicon/react'
import { PluginType } from '../types'
import { useMarketplaceContext } from './context'
@@ -50,7 +50,7 @@ const PluginTypeSwitch = ({
{
value: PLUGIN_TYPE_SEARCH_MAP.agent,
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,

View File

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

View File

@@ -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 ListEmpty from '@/app/components/base/list-empty'
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 Field from './field'
import type { ComponentProps } from 'react'
import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks'
export type Strategy = {
agent_strategy_provider_name: string
@@ -29,11 +31,10 @@ export type AgentStrategyProps = {
type CustomSchema<Type, Field = {}> = Omit<CredentialFormSchema, 'type'> & { type: Type } & Field
type MaxIterFormSchema = CustomSchema<'max-iter'>
type ToolSelectorSchema = CustomSchema<'tool-selector'>
type MultipleToolSelectorSchema = CustomSchema<'array[tools]'>
type CustomField = MaxIterFormSchema | ToolSelectorSchema | MultipleToolSelectorSchema
type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema
const devMockForm = [{
name: 'model',
@@ -114,36 +115,77 @@ const devMockForm = [{
max: null,
options: [],
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) => {
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange } = props
const { t } = useTranslation()
const language = useLanguage()
const override: ComponentProps<typeof Form<CustomField>>['override'] = [
[FormTypeEnum.textNumber],
(schema, props) => {
switch (schema.type) {
case FormTypeEnum.textNumber: {
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>
}
}
},
]
const renderField: ComponentProps<typeof Form<CustomField>>['customRenderField'] = (schema, props) => {
switch (schema.type) {
case 'max-iter': {
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={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>
}
case 'tool-selector': {
const value = props.value[schema.variable]
const onChange = (value: any) => {
@@ -183,6 +225,7 @@ export const AgentStrategy = (props: AgentStrategyProps) => {
isAgentStrategy={true}
fieldLabelClassName='uppercase'
customRenderField={renderField}
override={override}
/>
</div>
: <ListEmpty

View File

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

View File

@@ -65,10 +65,11 @@ const format = (list: NodeTracing[], t: any): NodeTracing[] => {
// console.log(list)
const result: NodeTracing[] = [...list]
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) => {
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 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 isNotInParallel = !parallel_id || node.node_type === BlockEnum.End
if (isNotInParallel)
@@ -100,10 +101,21 @@ const format = (list: NodeTracing[], t: any): NodeTracing[] => {
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])
if (parallelStartNode && parallelStartNode.parallelDetail && parallelStartNode!.parallelDetail!.children)
parallelStartNode!.parallelDetail!.children.push(node)
if (parallelStartNode && parallelStartNode.parallelDetail && parallelStartNode!.parallelDetail!.children) {
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) => {

View File

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