Introduce Plugins (#13836)
Signed-off-by: yihong0618 <zouzou0208@gmail.com> Signed-off-by: -LAN- <laipz8200@outlook.com> Signed-off-by: xhe <xw897002528@gmail.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: takatost <takatost@gmail.com> Co-authored-by: kurokobo <kuro664@gmail.com> Co-authored-by: Novice Lee <novicelee@NoviPro.local> Co-authored-by: zxhlyh <jasonapring2015@outlook.com> Co-authored-by: AkaraChen <akarachen@outlook.com> Co-authored-by: Yi <yxiaoisme@gmail.com> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: JzoNg <jzongcode@gmail.com> Co-authored-by: twwu <twwu@dify.ai> Co-authored-by: Hiroshi Fujita <fujita-h@users.noreply.github.com> Co-authored-by: AkaraChen <85140972+AkaraChen@users.noreply.github.com> Co-authored-by: NFish <douxc512@gmail.com> Co-authored-by: Wu Tianwei <30284043+WTW0313@users.noreply.github.com> Co-authored-by: 非法操作 <hjlarry@163.com> Co-authored-by: Novice <857526207@qq.com> Co-authored-by: Hiroki Nagai <82458324+nagaihiroki-git@users.noreply.github.com> Co-authored-by: Gen Sato <52241300+halogen22@users.noreply.github.com> Co-authored-by: eux <euxuuu@gmail.com> Co-authored-by: huangzhuo1949 <167434202+huangzhuo1949@users.noreply.github.com> Co-authored-by: huangzhuo <huangzhuo1@xiaomi.com> Co-authored-by: lotsik <lotsik@mail.ru> Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com> Co-authored-by: nite-knite <nkCoding@gmail.com> Co-authored-by: Jyong <76649700+JohnJyong@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: gakkiyomi <gakkiyomi@aliyun.com> Co-authored-by: CN-P5 <heibai2006@gmail.com> Co-authored-by: CN-P5 <heibai2006@qq.com> Co-authored-by: Chuehnone <1897025+chuehnone@users.noreply.github.com> Co-authored-by: yihong <zouzou0208@gmail.com> Co-authored-by: Kevin9703 <51311316+Kevin9703@users.noreply.github.com> Co-authored-by: -LAN- <laipz8200@outlook.com> Co-authored-by: Boris Feld <lothiraldan@gmail.com> Co-authored-by: mbo <himabo@gmail.com> Co-authored-by: mabo <mabo@aeyes.ai> Co-authored-by: Warren Chen <warren.chen830@gmail.com> Co-authored-by: JzoNgKVO <27049666+JzoNgKVO@users.noreply.github.com> Co-authored-by: jiandanfeng <chenjh3@wangsu.com> Co-authored-by: zhu-an <70234959+xhdd123321@users.noreply.github.com> Co-authored-by: zhaoqingyu.1075 <zhaoqingyu.1075@bytedance.com> Co-authored-by: 海狸大師 <86974027+yenslife@users.noreply.github.com> Co-authored-by: Xu Song <xusong.vip@gmail.com> Co-authored-by: rayshaw001 <396301947@163.com> Co-authored-by: Ding Jiatong <dingjiatong@gmail.com> Co-authored-by: Bowen Liang <liangbowen@gf.com.cn> Co-authored-by: JasonVV <jasonwangiii@outlook.com> Co-authored-by: le0zh <newlight@qq.com> Co-authored-by: zhuxinliang <zhuxinliang@didiglobal.com> Co-authored-by: k-zaku <zaku99@outlook.jp> Co-authored-by: luckylhb90 <luckylhb90@gmail.com> Co-authored-by: hobo.l <hobo.l@binance.com> Co-authored-by: jiangbo721 <365065261@qq.com> Co-authored-by: 刘江波 <jiangbo721@163.com> Co-authored-by: Shun Miyazawa <34241526+miya@users.noreply.github.com> Co-authored-by: EricPan <30651140+Egfly@users.noreply.github.com> Co-authored-by: crazywoola <427733928@qq.com> Co-authored-by: sino <sino2322@gmail.com> Co-authored-by: Jhvcc <37662342+Jhvcc@users.noreply.github.com> Co-authored-by: lowell <lowell.hu@zkteco.in> Co-authored-by: Boris Polonsky <BorisPolonsky@users.noreply.github.com> Co-authored-by: Ademílson Tonato <ademilsonft@outlook.com> Co-authored-by: Ademílson Tonato <ademilson.tonato@refurbed.com> Co-authored-by: IWAI, Masaharu <iwaim.sub@gmail.com> Co-authored-by: Yueh-Po Peng (Yabi) <94939112+y10ab1@users.noreply.github.com> Co-authored-by: Jason <ggbbddjm@gmail.com> Co-authored-by: Xin Zhang <sjhpzx@gmail.com> Co-authored-by: yjc980121 <3898524+yjc980121@users.noreply.github.com> Co-authored-by: heyszt <36215648+hieheihei@users.noreply.github.com> Co-authored-by: Abdullah AlOsaimi <osaimiacc@gmail.com> Co-authored-by: Abdullah AlOsaimi <189027247+osaimi@users.noreply.github.com> Co-authored-by: Yingchun Lai <laiyingchun@apache.org> Co-authored-by: Hash Brown <hi@xzd.me> Co-authored-by: zuodongxu <192560071+zuodongxu@users.noreply.github.com> Co-authored-by: Masashi Tomooka <tmokmss@users.noreply.github.com> Co-authored-by: aplio <ryo.091219@gmail.com> Co-authored-by: Obada Khalili <54270856+obadakhalili@users.noreply.github.com> Co-authored-by: Nam Vu <zuzoovn@gmail.com> Co-authored-by: Kei YAMAZAKI <1715090+kei-yamazaki@users.noreply.github.com> Co-authored-by: TechnoHouse <13776377+deephbz@users.noreply.github.com> Co-authored-by: Riddhimaan-Senapati <114703025+Riddhimaan-Senapati@users.noreply.github.com> Co-authored-by: MaFee921 <31881301+2284730142@users.noreply.github.com> Co-authored-by: te-chan <t-nakanome@sakura-is.co.jp> Co-authored-by: HQidea <HQidea@users.noreply.github.com> Co-authored-by: Joshbly <36315710+Joshbly@users.noreply.github.com> Co-authored-by: xhe <xw897002528@gmail.com> Co-authored-by: weiwenyan-dev <154779315+weiwenyan-dev@users.noreply.github.com> Co-authored-by: ex_wenyan.wei <ex_wenyan.wei@tcl.com> Co-authored-by: engchina <12236799+engchina@users.noreply.github.com> Co-authored-by: engchina <atjapan2015@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: 呆萌闷油瓶 <253605712@qq.com> Co-authored-by: Kemal <kemalmeler@outlook.com> Co-authored-by: Lazy_Frog <4590648+lazyFrogLOL@users.noreply.github.com> Co-authored-by: Yi Xiao <54782454+YIXIAO0@users.noreply.github.com> Co-authored-by: Steven sun <98230804+Tuyohai@users.noreply.github.com> Co-authored-by: steven <sunzwj@digitalchina.com> Co-authored-by: Kalo Chin <91766386+fdb02983rhy@users.noreply.github.com> Co-authored-by: Katy Tao <34019945+KatyTao@users.noreply.github.com> Co-authored-by: depy <42985524+h4ckdepy@users.noreply.github.com> Co-authored-by: 胡春东 <gycm520@gmail.com> Co-authored-by: Junjie.M <118170653@qq.com> Co-authored-by: MuYu <mr.muzea@gmail.com> Co-authored-by: Naoki Takashima <39912547+takatea@users.noreply.github.com> Co-authored-by: Summer-Gu <37869445+gubinjie@users.noreply.github.com> Co-authored-by: Fei He <droxer.he@gmail.com> Co-authored-by: ybalbert001 <120714773+ybalbert001@users.noreply.github.com> Co-authored-by: Yuanbo Li <ybalbert@amazon.com> Co-authored-by: douxc <7553076+douxc@users.noreply.github.com> Co-authored-by: liuzhenghua <1090179900@qq.com> Co-authored-by: Wu Jiayang <62842862+Wu-Jiayang@users.noreply.github.com> Co-authored-by: Your Name <you@example.com> Co-authored-by: kimjion <45935338+kimjion@users.noreply.github.com> Co-authored-by: AugNSo <song.tiankai@icloud.com> Co-authored-by: llinvokerl <38915183+llinvokerl@users.noreply.github.com> Co-authored-by: liusurong.lsr <liusurong.lsr@alibaba-inc.com> Co-authored-by: Vasu Negi <vasu-negi@users.noreply.github.com> Co-authored-by: Hundredwz <1808096180@qq.com> Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
||||
import cn from '@/utils/classnames'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
className?: string
|
||||
text: string
|
||||
onClick: () => void
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger } from '@/app/components/base/portal-to-follow-elem'
|
||||
import type { ReactNode } from 'react'
|
||||
import { memo, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { Strategy } from './agent-strategy'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { RiArrowDownSLine, RiErrorWarningFill } from '@remixicon/react'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Link from 'next/link'
|
||||
import { InstallPluginButton } from './install-plugin-button'
|
||||
import ViewTypeSelect, { ViewType } from '../../../block-selector/view-type-select'
|
||||
import SearchInput from '@/app/components/base/search-input'
|
||||
import Tools from '../../../block-selector/tools'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStrategyProviders } from '@/service/use-strategy'
|
||||
import { PluginType, type StrategyPluginDetail } from '@/app/components/plugins/types'
|
||||
import type { ToolWithProvider } from '../../../types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
|
||||
import { useStrategyInfo } from '../../agent/use-config'
|
||||
import { SwitchPluginVersion } from './switch-plugin-version'
|
||||
import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list'
|
||||
import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hooks'
|
||||
import { ToolTipContent } from '@/app/components/base/tooltip/content'
|
||||
|
||||
const NotFoundWarn = (props: {
|
||||
title: ReactNode,
|
||||
description: ReactNode
|
||||
}) => {
|
||||
const { title, description } = props
|
||||
|
||||
const { t } = useTranslation()
|
||||
return <Tooltip
|
||||
popupContent={
|
||||
<div className='space-y-1 text-xs'>
|
||||
<h3 className='text-text-primary font-semibold'>
|
||||
{title}
|
||||
</h3>
|
||||
<p className='text-text-secondary tracking-tight'>
|
||||
{description}
|
||||
</p>
|
||||
<p>
|
||||
<Link href={'/plugins'} className='text-text-accent tracking-tight'>
|
||||
{t('workflow.nodes.agent.linkToPlugin')}
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
needsDelay
|
||||
>
|
||||
<div>
|
||||
<RiErrorWarningFill className='text-text-destructive size-4' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
|
||||
function formatStrategy(input: StrategyPluginDetail[], getIcon: (i: string) => string): ToolWithProvider[] {
|
||||
return input.map((item) => {
|
||||
const res: ToolWithProvider = {
|
||||
id: item.plugin_unique_identifier,
|
||||
author: item.declaration.identity.author,
|
||||
name: item.declaration.identity.name,
|
||||
description: item.declaration.identity.description as any,
|
||||
plugin_id: item.plugin_id,
|
||||
icon: getIcon(item.declaration.identity.icon),
|
||||
label: item.declaration.identity.label as any,
|
||||
type: CollectionType.all,
|
||||
tools: item.declaration.strategies.map(strategy => ({
|
||||
name: strategy.identity.name,
|
||||
author: strategy.identity.author,
|
||||
label: strategy.identity.label as any,
|
||||
description: strategy.description,
|
||||
parameters: strategy.parameters as any,
|
||||
output_schema: strategy.output_schema,
|
||||
labels: [],
|
||||
})),
|
||||
team_credentials: {},
|
||||
is_team_authorization: true,
|
||||
allow_delete: false,
|
||||
labels: [],
|
||||
}
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
export type AgentStrategySelectorProps = {
|
||||
value?: Strategy,
|
||||
onChange: (value?: Strategy) => void,
|
||||
}
|
||||
|
||||
export const AgentStrategySelector = memo((props: AgentStrategySelectorProps) => {
|
||||
const { value, onChange } = props
|
||||
const [open, setOpen] = useState(false)
|
||||
const [viewType, setViewType] = useState<ViewType>(ViewType.flat)
|
||||
const [query, setQuery] = useState('')
|
||||
const stra = useStrategyProviders()
|
||||
const { getIconUrl } = useGetIcon()
|
||||
const list = stra.data ? formatStrategy(stra.data, getIconUrl) : undefined
|
||||
const filteredTools = useMemo(() => {
|
||||
if (!list) return []
|
||||
return list.filter(tool => tool.name.toLowerCase().includes(query.toLowerCase()))
|
||||
}, [query, list])
|
||||
const { strategyStatus, refetch: refetchStrategyInfo } = useStrategyInfo(
|
||||
value?.agent_strategy_provider_name,
|
||||
value?.agent_strategy_name,
|
||||
)
|
||||
|
||||
const showPluginNotInstalledWarn = strategyStatus?.plugin?.source === 'external'
|
||||
&& !strategyStatus.plugin.installed && !!value
|
||||
|
||||
const showUnsupportedStrategy = strategyStatus?.plugin.source === 'external'
|
||||
&& !strategyStatus?.isExistInPlugin && !!value
|
||||
|
||||
const showSwitchVersion = !strategyStatus?.isExistInPlugin
|
||||
&& strategyStatus?.plugin.source === 'marketplace' && strategyStatus.plugin.installed && !!value
|
||||
|
||||
const showInstallButton = !strategyStatus?.isExistInPlugin
|
||||
&& strategyStatus?.plugin.source === 'marketplace' && !strategyStatus.plugin.installed && !!value
|
||||
|
||||
const icon = list?.find(
|
||||
coll => coll.tools?.find(tool => tool.name === value?.agent_strategy_name),
|
||||
)?.icon as string | undefined
|
||||
const { t } = useTranslation()
|
||||
|
||||
const wrapElemRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const {
|
||||
queryPluginsWithDebounced: fetchPlugins,
|
||||
plugins: notInstalledPlugins = [],
|
||||
} = useMarketplacePlugins()
|
||||
|
||||
useEffect(() => {
|
||||
if (query) {
|
||||
fetchPlugins({
|
||||
query,
|
||||
category: PluginType.agent,
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [query])
|
||||
|
||||
const pluginRef = useRef(null)
|
||||
|
||||
return <PortalToFollowElem open={open} onOpenChange={setOpen} placement='bottom'>
|
||||
<PortalToFollowElemTrigger className='w-full'>
|
||||
<div
|
||||
className='h-8 p-1 gap-0.5 flex items-center rounded-lg bg-components-input-bg-normal w-full hover:bg-state-base-hover-alt select-none'
|
||||
onClick={() => setOpen(o => !o)}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
{icon && <div className='flex items-center justify-center w-6 h-6'><img
|
||||
src={icon}
|
||||
width={20}
|
||||
height={20}
|
||||
className='rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge'
|
||||
alt='icon'
|
||||
/></div>}
|
||||
<p
|
||||
className={classNames(value ? 'text-components-input-text-filled' : 'text-components-input-text-placeholder', 'text-xs px-1')}
|
||||
>
|
||||
{value?.agent_strategy_label || t('workflow.nodes.agent.strategy.selectTip')}
|
||||
</p>
|
||||
<div className='ml-auto flex items-center gap-1'>
|
||||
{showInstallButton && value && <InstallPluginButton
|
||||
onClick={e => e.stopPropagation()}
|
||||
size={'small'}
|
||||
uniqueIdentifier={value.plugin_unique_identifier}
|
||||
/>}
|
||||
{showPluginNotInstalledWarn
|
||||
? <NotFoundWarn
|
||||
title={t('workflow.nodes.agent.pluginNotInstalled')}
|
||||
description={t('workflow.nodes.agent.pluginNotInstalledDesc')}
|
||||
/>
|
||||
: showUnsupportedStrategy
|
||||
? <NotFoundWarn
|
||||
title={t('workflow.nodes.agent.unsupportedStrategy')}
|
||||
description={t('workflow.nodes.agent.strategyNotFoundDesc')}
|
||||
/>
|
||||
: <RiArrowDownSLine className='size-4 text-text-tertiary' />
|
||||
}
|
||||
{showSwitchVersion && <SwitchPluginVersion
|
||||
uniqueIdentifier={value.plugin_unique_identifier}
|
||||
tooltip={<ToolTipContent
|
||||
title={t('workflow.nodes.agent.unsupportedStrategy')}>
|
||||
{t('workflow.nodes.agent.strategyNotFoundDescAndSwitchVersion')}
|
||||
</ToolTipContent>}
|
||||
onChange={() => {
|
||||
refetchStrategyInfo()
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-10'>
|
||||
<div className='bg-components-panel-bg-blur border-components-panel-border border-[0.5px] rounded-md shadow overflow-hidden w-[388px]'>
|
||||
<header className='p-2 gap-1 flex'>
|
||||
<SearchInput placeholder={t('workflow.nodes.agent.strategy.searchPlaceholder')} value={query} onChange={setQuery} className={'w-full'} />
|
||||
<ViewTypeSelect viewType={viewType} onChange={setViewType} />
|
||||
</header>
|
||||
<main className="md:max-h-[300px] xl:max-h-[400px] 2xl:max-h-[564px] relative overflow-hidden flex flex-col w-full" ref={wrapElemRef}>
|
||||
<Tools
|
||||
tools={filteredTools}
|
||||
viewType={viewType}
|
||||
onSelect={(_, tool) => {
|
||||
onChange({
|
||||
agent_strategy_name: tool!.tool_name,
|
||||
agent_strategy_provider_name: tool!.provider_name,
|
||||
agent_strategy_label: tool!.tool_label,
|
||||
agent_output_schema: tool!.output_schema,
|
||||
plugin_unique_identifier: tool!.provider_id,
|
||||
})
|
||||
setOpen(false)
|
||||
} }
|
||||
className='max-w-none max-h-full h-full overflow-y-auto'
|
||||
indexBarClassName='top-0 xl:top-36' showWorkflowEmpty={false} hasSearchText={false} />
|
||||
<PluginList
|
||||
wrapElemRef={wrapElemRef}
|
||||
list={notInstalledPlugins as any} ref={pluginRef}
|
||||
searchText={query}
|
||||
tags={[]}
|
||||
disableMaxWidth
|
||||
/>
|
||||
</main>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
})
|
||||
|
||||
AgentStrategySelector.displayName = 'AgentStrategySelector'
|
||||
@@ -0,0 +1,228 @@
|
||||
import type { CredentialFormSchemaNumberInput, CredentialFormSchemaTextInput } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import { type CredentialFormSchema, FormTypeEnum, ModelTypeEnum } 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'
|
||||
import Link from 'next/link'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form'
|
||||
import { Agent } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import { InputNumber } from '@/app/components/base/input-number'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
|
||||
import MultipleToolSelector from '@/app/components/plugins/plugin-detail-panel/multiple-tool-selector'
|
||||
import Field from './field'
|
||||
import { type ComponentProps, memo } from 'react'
|
||||
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
|
||||
import Editor from './prompt/editor'
|
||||
import { useWorkflowStore } from '../../../store'
|
||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||
import type { NodeOutPutVar } from '../../../types'
|
||||
import type { Node } from 'reactflow'
|
||||
|
||||
export type Strategy = {
|
||||
agent_strategy_provider_name: string
|
||||
agent_strategy_name: string
|
||||
agent_strategy_label: string
|
||||
agent_output_schema: Record<string, any>
|
||||
plugin_unique_identifier: string
|
||||
}
|
||||
|
||||
export type AgentStrategyProps = {
|
||||
strategy?: Strategy
|
||||
onStrategyChange: (strategy?: Strategy) => void
|
||||
formSchema: CredentialFormSchema[]
|
||||
formValue: ToolVarInputs
|
||||
onFormValueChange: (value: ToolVarInputs) => void
|
||||
nodeOutputVars?: NodeOutPutVar[],
|
||||
availableNodes?: Node[],
|
||||
nodeId?: string
|
||||
}
|
||||
|
||||
type CustomSchema<Type, Field = {}> = Omit<CredentialFormSchema, 'type'> & { type: Type } & Field
|
||||
|
||||
type ToolSelectorSchema = CustomSchema<'tool-selector'>
|
||||
type MultipleToolSelectorSchema = CustomSchema<'array[tools]'>
|
||||
|
||||
type CustomField = ToolSelectorSchema | MultipleToolSelectorSchema
|
||||
|
||||
export const AgentStrategy = memo((props: AgentStrategyProps) => {
|
||||
const { strategy, onStrategyChange, formSchema, formValue, onFormValueChange, nodeOutputVars, availableNodes, nodeId } = props
|
||||
const { t } = useTranslation()
|
||||
const defaultModel = useDefaultModel(ModelTypeEnum.textGeneration)
|
||||
const renderI18nObject = useRenderI18nObject()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const {
|
||||
setControlPromptEditorRerenderKey,
|
||||
} = workflowStore.getState()
|
||||
const override: ComponentProps<typeof Form<CustomField>>['override'] = [
|
||||
[FormTypeEnum.textNumber, FormTypeEnum.textInput],
|
||||
(schema, props) => {
|
||||
switch (schema.type) {
|
||||
case FormTypeEnum.textInput: {
|
||||
const def = schema as CredentialFormSchemaTextInput
|
||||
const value = props.value[schema.variable]
|
||||
const onChange = (value: string) => {
|
||||
props.onChange({ ...props.value, [schema.variable]: value })
|
||||
}
|
||||
const handleGenerated = (value: string) => {
|
||||
onChange(value)
|
||||
setControlPromptEditorRerenderKey(Math.random())
|
||||
}
|
||||
return <Editor
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onGenerated={handleGenerated}
|
||||
title={renderI18nObject(schema.label)}
|
||||
headerClassName='bg-transparent px-0 text-text-secondary system-sm-semibold-uppercase'
|
||||
containerBackgroundClassName='bg-transparent'
|
||||
gradientBorder={false}
|
||||
isSupportPromptGenerator={!!def.auto_generate?.type}
|
||||
titleTooltip={schema.tooltip && renderI18nObject(schema.tooltip)}
|
||||
editorContainerClassName='px-0'
|
||||
availableNodes={availableNodes}
|
||||
nodesOutputVars={nodeOutputVars}
|
||||
isSupportJinja={def.template?.enabled}
|
||||
required={def.required}
|
||||
varList={[]}
|
||||
modelConfig={
|
||||
defaultModel.data
|
||||
? {
|
||||
mode: 'chat',
|
||||
name: defaultModel.data.model,
|
||||
provider: defaultModel.data.provider.provider,
|
||||
completion_params: {},
|
||||
} : undefined
|
||||
}
|
||||
placeholderClassName='px-2 py-1'
|
||||
titleClassName='system-sm-semibold-uppercase text-text-secondary text-[13px]'
|
||||
inputClassName='px-2 py-1 bg-components-input-bg-normal focus:bg-components-input-bg-active focus:border-components-input-border-active focus:border rounded-lg'
|
||||
/>
|
||||
}
|
||||
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={<>
|
||||
{renderI18nObject(def.label)} {def.required && <span className='text-red-500'>*</span>}
|
||||
</>}
|
||||
tooltip={def.tooltip && renderI18nObject(def.tooltip)}
|
||||
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 FormTypeEnum.toolSelector: {
|
||||
const value = props.value[schema.variable]
|
||||
const onChange = (value: any) => {
|
||||
props.onChange({ ...props.value, [schema.variable]: value })
|
||||
}
|
||||
return (
|
||||
<Field
|
||||
title={<>
|
||||
{renderI18nObject(schema.label)} {schema.required && <span className='text-red-500'>*</span>}
|
||||
</>}
|
||||
tooltip={schema.tooltip && renderI18nObject(schema.tooltip)}
|
||||
>
|
||||
<ToolSelector
|
||||
nodeId={props.nodeId || ''}
|
||||
nodeOutputVars={props.nodeOutputVars || []}
|
||||
availableNodes={props.availableNodes || []}
|
||||
scope={schema.scope}
|
||||
value={value}
|
||||
onSelect={item => onChange(item)}
|
||||
onDelete={() => onChange(null)}
|
||||
/>
|
||||
</Field>
|
||||
)
|
||||
}
|
||||
case FormTypeEnum.multiToolSelector: {
|
||||
const value = props.value[schema.variable]
|
||||
const onChange = (value: any) => {
|
||||
props.onChange({ ...props.value, [schema.variable]: value })
|
||||
}
|
||||
return (
|
||||
<MultipleToolSelector
|
||||
nodeId={props.nodeId || ''}
|
||||
nodeOutputVars={props.nodeOutputVars || []}
|
||||
availableNodes={props.availableNodes || []}
|
||||
scope={schema.scope}
|
||||
value={value || []}
|
||||
label={renderI18nObject(schema.label)}
|
||||
tooltip={schema.tooltip && renderI18nObject(schema.tooltip)}
|
||||
onChange={onChange}
|
||||
supportCollapse
|
||||
required={schema.required}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return <div className='space-y-2'>
|
||||
<AgentStrategySelector value={strategy} onChange={onStrategyChange} />
|
||||
{
|
||||
strategy
|
||||
? <div>
|
||||
<Form<CustomField>
|
||||
formSchemas={[
|
||||
...formSchema,
|
||||
]}
|
||||
value={formValue}
|
||||
onChange={onFormValueChange}
|
||||
validating={false}
|
||||
showOnVariableMap={{}}
|
||||
isEditMode={true}
|
||||
isAgentStrategy={true}
|
||||
fieldLabelClassName='uppercase'
|
||||
customRenderField={renderField}
|
||||
override={override}
|
||||
nodeId={nodeId}
|
||||
nodeOutputVars={nodeOutputVars || []}
|
||||
availableNodes={availableNodes || []}
|
||||
/>
|
||||
</div>
|
||||
: <ListEmpty
|
||||
icon={<Agent className='w-5 h-5 shrink-0 text-text-accent' />}
|
||||
title={t('workflow.nodes.agent.strategy.configureTip')}
|
||||
description={<div className='text-text-tertiary text-xs'>
|
||||
{t('workflow.nodes.agent.strategy.configureTipDesc')} <br />
|
||||
<Link href={'/'} className='text-text-accent-secondary'>
|
||||
{t('workflow.nodes.agent.learnMore')}
|
||||
</Link>
|
||||
</div>}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
})
|
||||
|
||||
AgentStrategy.displayName = 'AgentStrategy'
|
||||
@@ -25,7 +25,7 @@ import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
payload: InputVar
|
||||
value: any
|
||||
onChange: (value: any) => void
|
||||
|
||||
@@ -9,7 +9,7 @@ import { InputVarType } from '@/app/components/workflow/types'
|
||||
import AddButton from '@/app/components/base/button/add-button'
|
||||
import { RETRIEVAL_OUTPUT_STRUCT } from '@/app/components/workflow/constants'
|
||||
|
||||
export type Props = {
|
||||
export interface Props {
|
||||
className?: string
|
||||
label?: string
|
||||
inputs: InputVar[]
|
||||
|
||||
@@ -17,10 +17,10 @@ import ResultPanel from '@/app/components/workflow/run/result-panel'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
|
||||
import type { NodeTracing } from '@/types/workflow'
|
||||
import RetryResultPanel from '@/app/components/workflow/run/retry-result-panel'
|
||||
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { Emoji } from '@/app/components/tools/types'
|
||||
import type { SpecialResultPanelProps } from '@/app/components/workflow/run/special-result-panel'
|
||||
import SpecialResultPanel from '@/app/components/workflow/run/special-result-panel'
|
||||
|
||||
const i18nPrefix = 'workflow.singleRun'
|
||||
|
||||
@@ -34,13 +34,12 @@ type BeforeRunFormProps = {
|
||||
runningStatus: NodeRunningStatus
|
||||
result?: JSX.Element
|
||||
forms: FormProps[]
|
||||
retryDetails?: NodeTracing[]
|
||||
onRetryDetailBack?: any
|
||||
}
|
||||
showSpecialResultPanel?: boolean
|
||||
} & Partial<SpecialResultPanelProps>
|
||||
|
||||
function formatValue(value: string | any, type: InputVarType) {
|
||||
if (type === InputVarType.number)
|
||||
return parseFloat(value)
|
||||
return Number.parseFloat(value)
|
||||
if (type === InputVarType.json)
|
||||
return JSON.parse(value)
|
||||
if (type === InputVarType.contexts) {
|
||||
@@ -66,8 +65,8 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||
runningStatus,
|
||||
result,
|
||||
forms,
|
||||
retryDetails,
|
||||
onRetryDetailBack = () => { },
|
||||
showSpecialResultPanel,
|
||||
...restResultPanelParams
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -141,24 +140,14 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
retryDetails?.length && (
|
||||
showSpecialResultPanel && (
|
||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||
<RetryResultPanel
|
||||
list={retryDetails.map((item, index) => ({
|
||||
...item,
|
||||
title: `${t('workflow.nodes.common.retry.retry')} ${index + 1}`,
|
||||
node_type: nodeType!,
|
||||
extras: {
|
||||
icon: toolIcon!,
|
||||
},
|
||||
}))}
|
||||
onBack={onRetryDetailBack}
|
||||
/>
|
||||
<SpecialResultPanel {...restResultPanelParams} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!retryDetails?.length && (
|
||||
!showSpecialResultPanel && (
|
||||
<div className='h-0 grow overflow-y-auto pb-4'>
|
||||
<div className='mt-3 px-4 space-y-4'>
|
||||
{forms.map((form, index) => (
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import Collapse from '.'
|
||||
|
||||
type FieldCollapseProps = {
|
||||
title: string
|
||||
children: JSX.Element
|
||||
children: ReactNode
|
||||
}
|
||||
const FieldCollapse = ({
|
||||
title,
|
||||
|
||||
@@ -16,7 +16,7 @@ import useToggleExpend from '@/app/components/workflow/nodes/_base/hooks/use-tog
|
||||
import type { FileEntity } from '@/app/components/base/file-uploader/types'
|
||||
import FileListInLog from '@/app/components/base/file-uploader/file-list-in-log'
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
className?: string
|
||||
title: JSX.Element | string
|
||||
headerRight?: JSX.Element
|
||||
|
||||
@@ -88,7 +88,7 @@ const CodeEditor: FC<Props> = ({
|
||||
|
||||
const index = (() => {
|
||||
if (match)
|
||||
return parseInt(match[1]!) + 1
|
||||
return Number.parseInt(match[1]!) + 1
|
||||
|
||||
return 1
|
||||
})()
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import React from 'react'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
} from '@remixicon/react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import type { DefaultTFuncReturn } from 'i18next'
|
||||
import cn from '@/utils/classnames'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
title: JSX.Element | string | DefaultTFuncReturn
|
||||
tooltip?: React.ReactNode
|
||||
title: ReactNode
|
||||
tooltip?: ReactNode
|
||||
isSubTitle?: boolean
|
||||
supportFold?: boolean
|
||||
children?: JSX.Element | string | null
|
||||
@@ -51,7 +50,7 @@ const Field: FC<Props> = ({
|
||||
<div className='flex'>
|
||||
{operations && <div>{operations}</div>}
|
||||
{supportFold && (
|
||||
<RiArrowDownSLine className='w-4 h-4 text-text-tertiary cursor-pointer transform transition-transform' style={{ transform: fold ? 'rotate(-90deg)' : 'rotate(0deg)' }} />
|
||||
<RiArrowDownSLine className='w-4 h-4 text-text-tertiary cursor-pointer transition-transform' style={{ transform: fold ? 'rotate(-90deg)' : 'rotate(0deg)' }} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
25
web/app/components/workflow/nodes/_base/components/group.tsx
Normal file
25
web/app/components/workflow/nodes/_base/components/group.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import classNames from '@/utils/classnames'
|
||||
import type { ComponentProps, FC, PropsWithChildren, ReactNode } from 'react'
|
||||
|
||||
export type GroupLabelProps = ComponentProps<'div'>
|
||||
|
||||
export const GroupLabel: FC<GroupLabelProps> = (props) => {
|
||||
const { children, className, ...rest } = props
|
||||
return <div {...rest} className={classNames('mb-1 system-2xs-medium-uppercase text-text-tertiary', className)}>
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
|
||||
export type Group = PropsWithChildren<{
|
||||
label: ReactNode
|
||||
}>
|
||||
|
||||
export const Group: FC<Group> = (props) => {
|
||||
const { children, label } = props
|
||||
return <div className={classNames('py-1')}>
|
||||
{label}
|
||||
<div className='space-y-0.5'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
title: string
|
||||
content: string | JSX.Element
|
||||
content: ReactNode
|
||||
}
|
||||
|
||||
const InfoPanel: FC<Props> = ({
|
||||
|
||||
@@ -34,7 +34,7 @@ const InputNumberWithSlider: FC<Props> = ({
|
||||
}, [defaultValue, max, min, onChange, value])
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(parseFloat(e.target.value))
|
||||
onChange(Number.parseFloat(e.target.value))
|
||||
}, [onChange])
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiInstallLine, RiLoader2Line } from '@remixicon/react'
|
||||
import type { ComponentProps, MouseEventHandler } from 'react'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useCheckInstalled, useInstallPackageFromMarketPlace } from '@/service/use-plugins'
|
||||
|
||||
type InstallPluginButtonProps = Omit<ComponentProps<typeof Button>, 'children' | 'loading'> & {
|
||||
uniqueIdentifier: string
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const InstallPluginButton = (props: InstallPluginButtonProps) => {
|
||||
const { className, uniqueIdentifier, onSuccess, ...rest } = props
|
||||
const { t } = useTranslation()
|
||||
const manifest = useCheckInstalled({
|
||||
pluginIds: [uniqueIdentifier],
|
||||
enabled: !!uniqueIdentifier,
|
||||
})
|
||||
const install = useInstallPackageFromMarketPlace()
|
||||
const isLoading = manifest.isLoading || install.isPending
|
||||
// await for refetch to get the new installed plugin, when manifest refetch, this component will unmount
|
||||
|| install.isSuccess
|
||||
const handleInstall: MouseEventHandler = (e) => {
|
||||
e.stopPropagation()
|
||||
install.mutate(uniqueIdentifier, {
|
||||
onSuccess: async () => {
|
||||
await manifest.refetch()
|
||||
onSuccess?.()
|
||||
},
|
||||
})
|
||||
}
|
||||
if (!manifest.data) return null
|
||||
if (manifest.data.plugins.some(plugin => plugin.id === uniqueIdentifier)) return null
|
||||
return <Button
|
||||
variant={'secondary'}
|
||||
disabled={isLoading}
|
||||
{...rest}
|
||||
onClick={handleInstall}
|
||||
className={classNames('flex items-center', className)}
|
||||
>
|
||||
{!isLoading ? t('workflow.nodes.agent.pluginInstaller.install') : t('workflow.nodes.agent.pluginInstaller.installing')}
|
||||
{!isLoading ? <RiInstallLine className='size-3.5 ml-1' /> : <RiLoader2Line className='size-3.5 ml-1 animate-spin' />}
|
||||
</Button>
|
||||
}
|
||||
@@ -88,7 +88,7 @@ const MemoryConfig: FC<Props> = ({
|
||||
limitedSize = null
|
||||
}
|
||||
else {
|
||||
limitedSize = parseInt(limitedSize as string, 10)
|
||||
limitedSize = Number.parseInt(limitedSize as string, 10)
|
||||
if (isNaN(limitedSize))
|
||||
limitedSize = WINDOW_SIZE_DEFAULT
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
RiAlertFill,
|
||||
RiCheckboxCircleFill,
|
||||
RiErrorWarningLine,
|
||||
RiLoader2Line,
|
||||
} from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type NodeStatusIconProps = {
|
||||
status: string
|
||||
className?: string
|
||||
}
|
||||
const NodeStatusIcon = ({
|
||||
status,
|
||||
className,
|
||||
}: NodeStatusIconProps) => {
|
||||
return (
|
||||
<>
|
||||
{
|
||||
status === 'succeeded' && (
|
||||
<RiCheckboxCircleFill className={cn('shrink-0 w-4 h-4 text-text-success', className)} />
|
||||
)
|
||||
}
|
||||
{
|
||||
status === 'failed' && (
|
||||
<RiErrorWarningLine className={cn('shrink-0 w-4 h-4 text-text-warning', className)} />
|
||||
)
|
||||
}
|
||||
{
|
||||
(status === 'stopped' || status === 'exception') && (
|
||||
<RiAlertFill className={cn('shrink-0 w-4 h-4 text-text-warning-secondary', className)} />
|
||||
)
|
||||
}
|
||||
{
|
||||
status === 'running' && (
|
||||
<RiLoader2Line className={cn('shrink-0 w-4 h-4 text-text-accent animate-spin', className)} />
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default NodeStatusIcon
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||
@@ -7,7 +7,7 @@ import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/
|
||||
type Props = {
|
||||
className?: string
|
||||
title?: string
|
||||
children: JSX.Element
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const OutputVars: FC<Props> = ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import React, { useCallback, useRef } from 'react'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
@@ -68,6 +68,14 @@ type Props = {
|
||||
onEditionTypeChange?: (editionType: EditionType) => void
|
||||
varList?: Variable[]
|
||||
handleAddVariable?: (payload: any) => void
|
||||
containerBackgroundClassName?: string
|
||||
gradientBorder?: boolean
|
||||
titleTooltip?: ReactNode
|
||||
inputClassName?: string
|
||||
editorContainerClassName?: string
|
||||
placeholderClassName?: string
|
||||
titleClassName?: string
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
const Editor: FC<Props> = ({
|
||||
@@ -96,6 +104,14 @@ const Editor: FC<Props> = ({
|
||||
handleAddVariable,
|
||||
onGenerated,
|
||||
modelConfig,
|
||||
containerBackgroundClassName: containerClassName,
|
||||
gradientBorder = true,
|
||||
titleTooltip,
|
||||
inputClassName,
|
||||
placeholderClassName,
|
||||
titleClassName,
|
||||
editorContainerClassName,
|
||||
required,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
@@ -129,10 +145,13 @@ const Editor: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<Wrap className={cn(className, wrapClassName)} style={wrapStyle} isInNode isExpand={isExpand}>
|
||||
<div ref={ref} className={cn(isFocus ? s.gradientBorder : 'bg-gray-100', isExpand && 'h-full', '!rounded-[9px] p-0.5')}>
|
||||
<div className={cn(isFocus ? 'bg-gray-50' : 'bg-gray-100', isExpand && 'h-full flex flex-col', 'rounded-lg')}>
|
||||
<div className={cn(headerClassName, 'pt-1 pl-3 pr-2 flex justify-between items-center')}>
|
||||
<div className='leading-4 text-xs font-semibold text-gray-700 uppercase'>{title}</div>
|
||||
<div ref={ref} className={cn(isFocus ? (gradientBorder && s.gradientBorder) : 'bg-gray-100', isExpand && 'h-full', '!rounded-[9px] p-0.5', containerClassName)}>
|
||||
<div className={cn(isFocus ? 'bg-gray-50' : 'bg-gray-100', isExpand && 'h-full flex flex-col', 'rounded-lg', containerClassName)}>
|
||||
<div className={cn('pt-1 pl-3 pr-2 flex justify-between items-center', headerClassName)}>
|
||||
<div className='flex gap-2'>
|
||||
<div className={cn('leading-4 text-xs font-semibold text-gray-700 uppercase', titleClassName)}>{title} {required && <span className='text-red-500'>*</span>}</div>
|
||||
{titleTooltip && <Tooltip popupContent={titleTooltip} />}
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<div className='leading-[18px] text-xs font-medium text-gray-500'>{value?.length || 0}</div>
|
||||
{isSupportPromptGenerator && (
|
||||
@@ -201,12 +220,13 @@ const Editor: FC<Props> = ({
|
||||
<div className={cn('pb-2', isExpand && 'flex flex-col grow')}>
|
||||
{!(isSupportJinja && editionType === EditionType.jinja2)
|
||||
? (
|
||||
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative px-3 min-h-[56px] overflow-y-auto')}>
|
||||
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative px-3 min-h-[56px] overflow-y-auto', editorContainerClassName)}>
|
||||
<PromptEditor
|
||||
key={controlPromptEditorRerenderKey}
|
||||
placeholderClassName={placeholderClassName}
|
||||
instanceId={instanceId}
|
||||
compact
|
||||
className='min-h-[56px]'
|
||||
className={cn('min-h-[56px]', inputClassName)}
|
||||
style={isExpand ? { height: editorExpandHeight - 5 } : {}}
|
||||
value={value}
|
||||
contextBlock={{
|
||||
@@ -254,7 +274,7 @@ const Editor: FC<Props> = ({
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative px-3 min-h-[56px] overflow-y-auto')}>
|
||||
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative px-3 min-h-[56px] overflow-y-auto', editorContainerClassName)}>
|
||||
<CodeEditor
|
||||
availableVars={nodesOutputVars || []}
|
||||
varList={varList}
|
||||
@@ -266,6 +286,7 @@ const Editor: FC<Props> = ({
|
||||
onChange={onChange}
|
||||
noWrapper
|
||||
isExpand={isExpand}
|
||||
className={inputClassName}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { type ComponentProps, type PropsWithChildren, type ReactNode, memo } from 'react'
|
||||
|
||||
export type SettingItemProps = PropsWithChildren<{
|
||||
label: string
|
||||
status?: 'error' | 'warning'
|
||||
tooltip?: ReactNode
|
||||
}>
|
||||
|
||||
export const SettingItem = memo(({ label, children, status, tooltip }: SettingItemProps) => {
|
||||
const indicator: ComponentProps<typeof Indicator>['color'] = status === 'error' ? 'red' : status === 'warning' ? 'yellow' : undefined
|
||||
const needTooltip = ['error', 'warning'].includes(status as any)
|
||||
return <div className='flex items-center justify-between bg-workflow-block-parma-bg rounded-md py-1 px-1.5 space-x-1 text-xs font-normal relative'>
|
||||
<div className={classNames('shrink-0 truncate text-text-tertiary system-xs-medium-uppercase', !!children && 'max-w-[100px]')}>
|
||||
{label}
|
||||
</div>
|
||||
<Tooltip popupContent={tooltip} disabled={!needTooltip}>
|
||||
<div className='truncate text-right system-xs-medium text-text-secondary'>
|
||||
{children}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{indicator && <Indicator color={indicator} className='absolute -right-0.5 -top-0.5' />}
|
||||
</div>
|
||||
})
|
||||
|
||||
SettingItem.displayName = 'SettingItem'
|
||||
@@ -3,7 +3,7 @@ import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
className?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
'use client'
|
||||
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker'
|
||||
import { RiArrowLeftRightLine, RiExternalLinkLine } from '@remixicon/react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { type FC, useCallback, useState } from 'react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useCheckInstalled, useUpdatePackageFromMarketPlace } from '@/service/use-plugins'
|
||||
import cn from '@/utils/classnames'
|
||||
import PluginMutationModel from '@/app/components/plugins/plugin-mutation-model'
|
||||
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
|
||||
import { pluginManifestToCardPluginProps } from '@/app/components/plugins/install-plugin/utils'
|
||||
import { Badge as Badge2, BadgeState } from '@/app/components/base/badge/index'
|
||||
import Link from 'next/link'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { marketplaceUrlPrefix } from '@/config'
|
||||
|
||||
export type SwitchPluginVersionProps = {
|
||||
uniqueIdentifier: string
|
||||
tooltip?: ReactNode
|
||||
onChange?: (version: string) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const SwitchPluginVersion: FC<SwitchPluginVersionProps> = (props) => {
|
||||
const { uniqueIdentifier, tooltip, onChange, className } = props
|
||||
const [pluginId] = uniqueIdentifier.split(':')
|
||||
const [isShow, setIsShow] = useState(false)
|
||||
const [isShowUpdateModal, { setTrue: showUpdateModal, setFalse: hideUpdateModal }] = useBoolean(false)
|
||||
const [target, setTarget] = useState<{
|
||||
version: string,
|
||||
pluginUniqueIden: string;
|
||||
}>()
|
||||
const pluginDetails = useCheckInstalled({
|
||||
pluginIds: [pluginId],
|
||||
enabled: true,
|
||||
})
|
||||
const pluginDetail = pluginDetails.data?.plugins.at(0)
|
||||
|
||||
const handleUpdatedFromMarketplace = useCallback(() => {
|
||||
hideUpdateModal()
|
||||
pluginDetails.refetch()
|
||||
onChange?.(target!.version)
|
||||
}, [hideUpdateModal, onChange, pluginDetails, target])
|
||||
const { getIconUrl } = useGetIcon()
|
||||
const icon = pluginDetail?.declaration.icon ? getIconUrl(pluginDetail.declaration.icon) : undefined
|
||||
const mutation = useUpdatePackageFromMarketPlace()
|
||||
const install = () => {
|
||||
mutation.mutate(
|
||||
{
|
||||
new_plugin_unique_identifier: target!.pluginUniqueIden,
|
||||
original_plugin_unique_identifier: uniqueIdentifier,
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
handleUpdatedFromMarketplace()
|
||||
},
|
||||
})
|
||||
}
|
||||
const { t } = useTranslation()
|
||||
return <Tooltip popupContent={!isShow && !isShowUpdateModal && tooltip} triggerMethod='hover'>
|
||||
<div className={cn('w-fit flex items-center justify-center', className)} onClick={e => e.stopPropagation()}>
|
||||
{isShowUpdateModal && pluginDetail && <PluginMutationModel
|
||||
onCancel={hideUpdateModal}
|
||||
plugin={pluginManifestToCardPluginProps({
|
||||
...pluginDetail.declaration,
|
||||
icon: icon!,
|
||||
})}
|
||||
mutation={mutation}
|
||||
mutate={install}
|
||||
confirmButtonText={t('workflow.nodes.agent.installPlugin.install')}
|
||||
cancelButtonText={t('workflow.nodes.agent.installPlugin.cancel')}
|
||||
modelTitle={t('workflow.nodes.agent.installPlugin.title')}
|
||||
description={t('workflow.nodes.agent.installPlugin.desc')}
|
||||
cardTitleLeft={<>
|
||||
<Badge2 className='mx-1' size="s" state={BadgeState.Warning}>
|
||||
{`${pluginDetail.version} -> ${target!.version}`}
|
||||
</Badge2>
|
||||
</>}
|
||||
modalBottomLeft={
|
||||
<Link
|
||||
className='flex justify-center items-center gap-1'
|
||||
href={`${marketplaceUrlPrefix}/plugins/${pluginDetail.declaration.author}/${pluginDetail.declaration.name}`}
|
||||
target='_blank'
|
||||
>
|
||||
<span className='text-text-accent system-xs-regular text-xs'>
|
||||
{t('workflow.nodes.agent.installPlugin.changelog')}
|
||||
</span>
|
||||
<RiExternalLinkLine className='text-text-accent size-3' />
|
||||
</Link>
|
||||
}
|
||||
/>}
|
||||
{pluginDetail && <PluginVersionPicker
|
||||
isShow={isShow}
|
||||
onShowChange={setIsShow}
|
||||
pluginID={pluginId}
|
||||
currentVersion={pluginDetail.version}
|
||||
onSelect={(state) => {
|
||||
setTarget({
|
||||
pluginUniqueIden: state.unique_identifier,
|
||||
version: state.version,
|
||||
})
|
||||
showUpdateModal()
|
||||
}}
|
||||
trigger={
|
||||
<Badge
|
||||
className={cn(
|
||||
'mx-1 hover:bg-state-base-hover flex',
|
||||
isShow && 'bg-state-base-hover',
|
||||
)}
|
||||
uppercase={true}
|
||||
text={
|
||||
<>
|
||||
<div>{pluginDetail.version}</div>
|
||||
<RiArrowLeftRightLine className='ml-1 w-3 h-3 text-text-tertiary' />
|
||||
</>
|
||||
}
|
||||
hasRedCornerMark={true}
|
||||
/>
|
||||
}
|
||||
/>}
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import Tooltip from '@/app/components/base/tooltip'
|
||||
import cn from '@/utils/classnames'
|
||||
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||
|
||||
type VariableTagProps = {
|
||||
interface VariableTagProps {
|
||||
valueSelector: ValueSelector
|
||||
varType: VarType
|
||||
isShort?: boolean
|
||||
|
||||
@@ -24,7 +24,7 @@ const ConstantField: FC<Props> = ({
|
||||
const language = useLanguage()
|
||||
const placeholder = (schema as CredentialFormSchemaSelect).placeholder
|
||||
const handleStaticChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value === '' ? '' : parseFloat(e.target.value)
|
||||
const value = e.target.value === '' ? '' : Number.parseFloat(e.target.value)
|
||||
onChange(value, VarKindType.constant)
|
||||
}, [onChange])
|
||||
const handleSelectChange = useCallback((value: string | number) => {
|
||||
@@ -39,6 +39,7 @@ const ConstantField: FC<Props> = ({
|
||||
wrapperClassName='w-full !h-8'
|
||||
className='flex items-center'
|
||||
disabled={readonly}
|
||||
defaultValue={value}
|
||||
items={(schema as CredentialFormSchemaSelect).options.map(option => ({ value: option.value, name: option.label[language] || option.label.en_US }))}
|
||||
onSelect={item => handleSelectChange(item.value)}
|
||||
placeholder={placeholder?.[language] || placeholder?.en_US}
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { VarType } from '@/app/components/workflow/types'
|
||||
import { checkKeys } from '@/utils/var'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
readonly: boolean
|
||||
outputs: OutputVar
|
||||
outputKeyOrders: string[]
|
||||
|
||||
@@ -14,7 +14,7 @@ import type { ToolNodeType } from '../../../tool/types'
|
||||
import type { ParameterExtractorNodeType } from '../../../parameter-extractor/types'
|
||||
import type { IterationNodeType } from '../../../iteration/types'
|
||||
import type { ListFilterNodeType } from '../../../list-operator/types'
|
||||
import { OUTPUT_FILE_SUB_VARIABLES } from '../../../if-else/default'
|
||||
import { OUTPUT_FILE_SUB_VARIABLES } from '../../../constants'
|
||||
import type { DocExtractorNodeType } from '../../../document-extractor/types'
|
||||
import { BlockEnum, InputVarType, VarType } from '@/app/components/workflow/types'
|
||||
import type { StartNodeType } from '@/app/components/workflow/nodes/start/types'
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from '@/app/components/workflow/constants'
|
||||
import type { PromptItem } from '@/models/debug'
|
||||
import { VAR_REGEX } from '@/config'
|
||||
import type { AgentNodeType } from '../../../agent/types'
|
||||
|
||||
export const isSystemVar = (valueSelector: ValueSelector) => {
|
||||
return valueSelector[0] === 'sys' || valueSelector[1] === 'sys'
|
||||
@@ -235,7 +236,29 @@ const formatItem = (
|
||||
}
|
||||
|
||||
case BlockEnum.Tool: {
|
||||
res.vars = TOOL_OUTPUT_STRUCT
|
||||
const {
|
||||
output_schema,
|
||||
} = data as ToolNodeType
|
||||
if (!output_schema) {
|
||||
res.vars = TOOL_OUTPUT_STRUCT
|
||||
}
|
||||
else {
|
||||
const outputSchema: any[] = []
|
||||
Object.keys(output_schema.properties).forEach((outputKey) => {
|
||||
const output = output_schema.properties[outputKey]
|
||||
outputSchema.push({
|
||||
variable: 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)}`,
|
||||
description: output.description,
|
||||
})
|
||||
})
|
||||
res.vars = [
|
||||
...TOOL_OUTPUT_STRUCT,
|
||||
...outputSchema,
|
||||
]
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -293,6 +316,25 @@ const formatItem = (
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.Agent: {
|
||||
const payload = data as AgentNodeType
|
||||
const outputs: Var[] = []
|
||||
Object.keys(payload.output_schema?.properties || {}).forEach((outputKey) => {
|
||||
const output = payload.output_schema.properties[outputKey]
|
||||
outputs.push({
|
||||
variable: outputKey,
|
||||
type: output.type === 'array'
|
||||
? `Array[${output.items?.type.slice(0, 1).toLocaleUpperCase()}${output.items?.type.slice(1)}]` as VarType
|
||||
: `${output.type.slice(0, 1).toLocaleUpperCase()}${output.type.slice(1)}` as VarType,
|
||||
})
|
||||
})
|
||||
res.vars = [
|
||||
...outputs,
|
||||
...TOOL_OUTPUT_STRUCT,
|
||||
]
|
||||
break
|
||||
}
|
||||
|
||||
case 'env': {
|
||||
res.vars = data.envList.map((env: EnvironmentVariable) => {
|
||||
return {
|
||||
@@ -680,7 +722,7 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => {
|
||||
break
|
||||
}
|
||||
case BlockEnum.LLM: {
|
||||
const payload = (data as LLMNodeType)
|
||||
const payload = data as LLMNodeType
|
||||
const isChatModel = payload.model?.mode === 'chat'
|
||||
let prompts: string[] = []
|
||||
if (isChatModel) {
|
||||
@@ -718,19 +760,19 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => {
|
||||
break
|
||||
}
|
||||
case BlockEnum.QuestionClassifier: {
|
||||
const payload = (data as QuestionClassifierNodeType)
|
||||
const payload = data as QuestionClassifierNodeType
|
||||
res = [payload.query_variable_selector]
|
||||
const varInInstructions = matchNotSystemVars([payload.instruction || ''])
|
||||
res.push(...varInInstructions)
|
||||
break
|
||||
}
|
||||
case BlockEnum.HttpRequest: {
|
||||
const payload = (data as HttpNodeType)
|
||||
const payload = data as HttpNodeType
|
||||
res = matchNotSystemVars([payload.url, payload.headers, payload.params, typeof payload.body.data === 'string' ? payload.body.data : payload.body.data.map(d => d.value).join('')])
|
||||
break
|
||||
}
|
||||
case BlockEnum.Tool: {
|
||||
const payload = (data as ToolNodeType)
|
||||
const payload = data as ToolNodeType
|
||||
const mixVars = matchNotSystemVars(Object.keys(payload.tool_parameters)?.filter(key => payload.tool_parameters[key].type === ToolVarType.mixed).map(key => payload.tool_parameters[key].value) as string[])
|
||||
const vars = Object.keys(payload.tool_parameters).filter(key => payload.tool_parameters[key].type === ToolVarType.variable).map(key => payload.tool_parameters[key].value as string) || []
|
||||
res = [...(mixVars as ValueSelector[]), ...(vars as any)]
|
||||
@@ -748,7 +790,7 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => {
|
||||
}
|
||||
|
||||
case BlockEnum.ParameterExtractor: {
|
||||
const payload = (data as ParameterExtractorNodeType)
|
||||
const payload = data as ParameterExtractorNodeType
|
||||
res = [payload.query]
|
||||
const varInInstructions = matchNotSystemVars([payload.instruction || ''])
|
||||
res.push(...varInInstructions)
|
||||
@@ -764,6 +806,21 @@ export const getNodeUsedVars = (node: Node): ValueSelector[] => {
|
||||
res = [(data as ListFilterNodeType).variable]
|
||||
break
|
||||
}
|
||||
|
||||
case BlockEnum.Agent: {
|
||||
const payload = data as AgentNodeType
|
||||
const valueSelectors: ValueSelector[] = []
|
||||
if (!payload.agent_parameters)
|
||||
break
|
||||
|
||||
Object.keys(payload.agent_parameters || {}).forEach((key) => {
|
||||
const { value } = payload.agent_parameters![key]
|
||||
if (typeof value === 'string')
|
||||
valueSelectors.push(...matchNotSystemVars([value]))
|
||||
})
|
||||
res = valueSelectors
|
||||
break
|
||||
}
|
||||
}
|
||||
return res || []
|
||||
}
|
||||
@@ -775,7 +832,7 @@ export const getNodeUsedVarPassToServerKey = (node: Node, valueSelector: ValueSe
|
||||
let res: string | string[] = ''
|
||||
switch (type) {
|
||||
case BlockEnum.LLM: {
|
||||
const payload = (data as LLMNodeType)
|
||||
const payload = data as LLMNodeType
|
||||
res = [`#${valueSelector.join('.')}#`]
|
||||
if (payload.context?.variable_selector.join('.') === valueSelector.join('.'))
|
||||
res.push('#context#')
|
||||
|
||||
@@ -64,6 +64,7 @@ type Props = {
|
||||
placeholder?: string
|
||||
minWidth?: number
|
||||
popupFor?: 'assigned' | 'toAssigned'
|
||||
zIndex?: number
|
||||
}
|
||||
|
||||
const VarReferencePicker: FC<Props> = ({
|
||||
@@ -90,6 +91,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
placeholder,
|
||||
minWidth,
|
||||
popupFor,
|
||||
zIndex,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const store = useStoreApi()
|
||||
@@ -386,7 +388,7 @@ const VarReferencePicker: FC<Props> = ({
|
||||
</>
|
||||
</WrapElem>
|
||||
<PortalToFollowElemContent style={{
|
||||
zIndex: 100,
|
||||
zIndex: zIndex || 100,
|
||||
}} className='mt-1'>
|
||||
{!isConstant && (
|
||||
<VarReferencePopup
|
||||
|
||||
@@ -17,7 +17,7 @@ import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others
|
||||
import { checkKeys } from '@/utils/var'
|
||||
import { FILE_STRUCT } from '@/app/components/workflow/constants'
|
||||
|
||||
type ObjectChildrenProps = {
|
||||
interface ObjectChildrenProps {
|
||||
nodeId: string
|
||||
title: string
|
||||
data: Var[]
|
||||
@@ -28,7 +28,7 @@ type ObjectChildrenProps = {
|
||||
isSupportFileVar?: boolean
|
||||
}
|
||||
|
||||
type ItemProps = {
|
||||
interface ItemProps {
|
||||
nodeId: string
|
||||
title: string
|
||||
objPath: string[]
|
||||
@@ -134,7 +134,7 @@ const Item: FC<ItemProps> = ({
|
||||
zIndex: 100,
|
||||
}}>
|
||||
{(isObj && !isFile) && (
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
<ObjectChildren
|
||||
nodeId={nodeId}
|
||||
title={title}
|
||||
@@ -147,7 +147,7 @@ const Item: FC<ItemProps> = ({
|
||||
/>
|
||||
)}
|
||||
{isFile && (
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
// eslint-disable-next-line ts/no-use-before-define
|
||||
<ObjectChildren
|
||||
nodeId={nodeId}
|
||||
title={title}
|
||||
@@ -226,7 +226,7 @@ const ObjectChildren: FC<ObjectChildrenProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
hideSearch?: boolean
|
||||
searchBoxClassName?: string
|
||||
vars: NodeOutPutVar[]
|
||||
|
||||
@@ -46,6 +46,7 @@ const { checkValid: checkParameterExtractorValid } = ParameterExtractorDefault
|
||||
const { checkValid: checkIterationValid } = IterationDefault
|
||||
const { checkValid: checkDocumentExtractorValid } = DocumentExtractorDefault
|
||||
|
||||
// eslint-disable-next-line ts/no-unsafe-function-type
|
||||
const checkValidFns: Record<BlockEnum, Function> = {
|
||||
[BlockEnum.LLM]: checkLLMValid,
|
||||
[BlockEnum.KnowledgeRetrieval]: checkKnowledgeRetrievalValid,
|
||||
@@ -144,7 +145,7 @@ const useOneStepRun = <T>({
|
||||
const { handleNodeDataUpdate }: { handleNodeDataUpdate: (data: any) => void } = useNodeDataUpdate()
|
||||
const [canShowSingleRun, setCanShowSingleRun] = useState(false)
|
||||
const isShowSingleRun = data._isSingleRun && canShowSingleRun
|
||||
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[][]>([])
|
||||
const [iterationRunResult, setIterationRunResult] = useState<NodeTracing[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (!checkValid) {
|
||||
@@ -175,7 +176,7 @@ const useOneStepRun = <T>({
|
||||
const workflowStore = useWorkflowStore()
|
||||
useEffect(() => {
|
||||
workflowStore.getState().setShowSingleRunPanel(!!isShowSingleRun)
|
||||
}, [isShowSingleRun])
|
||||
}, [isShowSingleRun, workflowStore])
|
||||
|
||||
const hideSingleRun = () => {
|
||||
handleNodeDataUpdate({
|
||||
@@ -213,7 +214,7 @@ const useOneStepRun = <T>({
|
||||
}
|
||||
else {
|
||||
setIterationRunResult([])
|
||||
let _iterationResult: NodeTracing[][] = []
|
||||
let _iterationResult: NodeTracing[] = []
|
||||
let _runResult: any = null
|
||||
ssePost(
|
||||
getIterationSingleNodeRunUrl(isChatMode, appId!, id),
|
||||
@@ -233,27 +234,43 @@ const useOneStepRun = <T>({
|
||||
_runResult.created_by = iterationData.created_by.name
|
||||
setRunResult(_runResult)
|
||||
},
|
||||
onIterationNext: () => {
|
||||
// iteration next trigger time is triggered one more time than iterationTimes
|
||||
if (_iterationResult.length >= iterationTimes!)
|
||||
return
|
||||
|
||||
onIterationStart: (params) => {
|
||||
const newIterationRunResult = produce(_iterationResult, (draft) => {
|
||||
draft.push([])
|
||||
draft.push({
|
||||
...params.data,
|
||||
status: NodeRunningStatus.Running,
|
||||
})
|
||||
})
|
||||
_iterationResult = newIterationRunResult
|
||||
setIterationRunResult(newIterationRunResult)
|
||||
},
|
||||
onIterationNext: () => {
|
||||
// iteration next trigger time is triggered one more time than iterationTimes
|
||||
if (_iterationResult.length >= iterationTimes!)
|
||||
return _iterationResult.length >= iterationTimes!
|
||||
},
|
||||
onIterationFinish: (params) => {
|
||||
_runResult = params.data
|
||||
setRunResult(_runResult)
|
||||
const iterationRunResult = _iterationResult
|
||||
const currentIndex = iterationRunResult.findIndex(trace => trace.id === params.data.id)
|
||||
const newIterationRunResult = produce(iterationRunResult, (draft) => {
|
||||
if (currentIndex > -1) {
|
||||
draft[currentIndex] = {
|
||||
...draft[currentIndex],
|
||||
...data,
|
||||
}
|
||||
}
|
||||
})
|
||||
_iterationResult = newIterationRunResult
|
||||
setIterationRunResult(newIterationRunResult)
|
||||
},
|
||||
onNodeStarted: (params) => {
|
||||
const newIterationRunResult = produce(_iterationResult, (draft) => {
|
||||
draft[draft.length - 1].push({
|
||||
draft.push({
|
||||
...params.data,
|
||||
status: NodeRunningStatus.Running,
|
||||
} as NodeTracing)
|
||||
})
|
||||
})
|
||||
_iterationResult = newIterationRunResult
|
||||
setIterationRunResult(newIterationRunResult)
|
||||
@@ -262,18 +279,25 @@ const useOneStepRun = <T>({
|
||||
const iterationRunResult = _iterationResult
|
||||
|
||||
const { data } = params
|
||||
const currentIndex = iterationRunResult[iterationRunResult.length - 1].findIndex(trace => trace.node_id === data.node_id)
|
||||
const currentIndex = iterationRunResult.findIndex(trace => trace.id === data.id)
|
||||
const newIterationRunResult = produce(iterationRunResult, (draft) => {
|
||||
if (currentIndex > -1) {
|
||||
draft[draft.length - 1][currentIndex] = {
|
||||
draft[currentIndex] = {
|
||||
...draft[currentIndex],
|
||||
...data,
|
||||
status: NodeRunningStatus.Succeeded,
|
||||
} as NodeTracing
|
||||
}
|
||||
}
|
||||
})
|
||||
_iterationResult = newIterationRunResult
|
||||
setIterationRunResult(newIterationRunResult)
|
||||
},
|
||||
onNodeRetry: (params) => {
|
||||
const newIterationRunResult = produce(_iterationResult, (draft) => {
|
||||
draft.push(params.data)
|
||||
})
|
||||
_iterationResult = newIterationRunResult
|
||||
setIterationRunResult(newIterationRunResult)
|
||||
},
|
||||
onError: () => {
|
||||
handleNodeDataUpdate({
|
||||
id,
|
||||
|
||||
Reference in New Issue
Block a user