mirror of
https://github.com/langgenius/dify.git
synced 2026-01-07 23:04:12 +00:00
Merge branch 'feat/plugins' of https://github.com/langgenius/dify into feat/plugins
This commit is contained in:
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -69,7 +69,7 @@ const List = ({
|
||||
}
|
||||
{
|
||||
plugins && !plugins.length && (
|
||||
<Empty className={emptyClassName} />
|
||||
<Empty className={emptyClassName} locale={locale} />
|
||||
)
|
||||
}
|
||||
</>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ const translation = {
|
||||
all: '全部',
|
||||
models: '模型',
|
||||
tools: '工具',
|
||||
agents: 'Agent Strategy',
|
||||
agents: 'Agent 策略',
|
||||
extensions: '扩展',
|
||||
bundles: '插件集',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user