diff --git a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx index 6ade065af2..b7a9a69870 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-modal/Form.tsx @@ -45,7 +45,7 @@ type FormProps< props: Omit, 'override' | 'customRenderField'> ) => ReactNode // If return falsy value, this field will fallback to default render - override?: [Array, (formSchema: CredentialFormSchema) => ReactNode] + override?: [Array, (formSchema: CredentialFormSchema, props: Omit, 'override' | 'customRenderField'>) => ReactNode] } function Form< @@ -71,6 +71,22 @@ function Form< }: FormProps) { const language = useLanguage() const [changeKey, setChangeKey] = useState('') + const filteredProps: Omit, '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 ( diff --git a/web/app/components/plugins/marketplace/empty/index.tsx b/web/app/components/plugins/marketplace/empty/index.tsx index ec78d6a669..c190f0affe 100644 --- a/web/app/components/plugins/marketplace/empty/index.tsx +++ b/web/app/components/plugins/marketplace/empty/index.tsx @@ -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 (
+ ) } diff --git a/web/app/components/plugins/marketplace/list/list-with-collection.tsx b/web/app/components/plugins/marketplace/list/list-with-collection.tsx index 4195280351..aed84d77dd 100644 --- a/web/app/components/plugins/marketplace/list/list-with-collection.tsx +++ b/web/app/components/plugins/marketplace/list/list-with-collection.tsx @@ -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 ( <> diff --git a/web/app/components/plugins/marketplace/list/list-wrapper.tsx b/web/app/components/plugins/marketplace/list/list-wrapper.tsx index 08b50ecc32..155dfe397a 100644 --- a/web/app/components/plugins/marketplace/list/list-wrapper.tsx +++ b/web/app/components/plugins/marketplace/list/list-wrapper.tsx @@ -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 = ({
{t('plugin.marketplace.pluginsResult', { num: pluginsTotal })}
- +
) } diff --git a/web/app/components/plugins/marketplace/plugin-type-switch.tsx b/web/app/components/plugins/marketplace/plugin-type-switch.tsx index 3755a7bf2b..e4fef4aaea 100644 --- a/web/app/components/plugins/marketplace/plugin-type-switch.tsx +++ b/web/app/components/plugins/marketplace/plugin-type-switch.tsx @@ -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: , + icon: , }, { value: PLUGIN_TYPE_SEARCH_MAP.extension, diff --git a/web/app/components/plugins/marketplace/sort-dropdown/index.tsx b/web/app/components/plugins/marketplace/sort-dropdown/index.tsx index a71cb249d5..b39cbe86ce 100644 --- a/web/app/components/plugins/marketplace/sort-dropdown/index.tsx +++ b/web/app/components/plugins/marketplace/sort-dropdown/index.tsx @@ -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', diff --git a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx index 5e86a09c46..c8aa80431b 100644 --- a/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx +++ b/web/app/components/workflow/nodes/_base/components/agent-strategy.tsx @@ -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 = Omit & { 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>['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 +
+ + +
+
+ } + } + }, + ] const renderField: ComponentProps>['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 -
- - -
-
- } 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} />
: = ({ hideNodeProcessDetail = false, }) => { const treeNodes = list - console.log(treeNodes) const [collapsedNodes, setCollapsedNodes] = useState>(new Set()) const [hoveredParallel, setHoveredParallel] = useState(null) @@ -84,7 +83,6 @@ const TracingPanel: FC = ({ setAgentResultList, } = useLogs() - const renderNode = (node: NodeTracing) => { const isParallelFirstNode = !!node.parallelDetail?.isParallelStartNode if (isParallelFirstNode) { diff --git a/web/app/components/workflow/run/utils/format-log/parallel/index.ts b/web/app/components/workflow/run/utils/format-log/parallel/index.ts index a5ec9412f4..6c276a1e05 100644 --- a/web/app/components/workflow/run/utils/format-log/parallel/index.ts +++ b/web/app/components/workflow/run/utils/format-log/parallel/index.ts @@ -65,10 +65,11 @@ const format = (list: NodeTracing[], t: any): NodeTracing[] => { // console.log(list) const result: NodeTracing[] = [...list] const parallelFirstNodeMap: Record = {} - // 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) => { diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts index 4d74c700b2..8185f37b40 100644 --- a/web/i18n/zh-Hans/plugin.ts +++ b/web/i18n/zh-Hans/plugin.ts @@ -3,7 +3,7 @@ const translation = { all: '全部', models: '模型', tools: '工具', - agents: 'Agent Strategy', + agents: 'Agent 策略', extensions: '扩展', bundles: '插件集', },