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:
Yeuoly
2025-02-17 17:05:13 +08:00
committed by GitHub
parent 222df44d21
commit 403e2d58b9
3272 changed files with 66339 additions and 281594 deletions

View File

@@ -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

View File

@@ -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'

View File

@@ -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'

View File

@@ -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

View File

@@ -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[]

View File

@@ -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) => (

View File

@@ -1,8 +1,9 @@
import type { ReactNode } from 'react'
import Collapse from '.'
type FieldCollapseProps = {
title: string
children: JSX.Element
children: ReactNode
}
const FieldCollapse = ({
title,

View File

@@ -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

View File

@@ -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
})()

View File

@@ -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>

View 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>
}

View File

@@ -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> = ({

View File

@@ -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 (

View File

@@ -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>
}

View File

@@ -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

View File

@@ -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

View File

@@ -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> = ({

View File

@@ -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>
)}

View File

@@ -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'

View File

@@ -3,7 +3,7 @@ import type { FC } from 'react'
import React from 'react'
import cn from '@/utils/classnames'
type Props = {
interface Props {
className?: string
}

View File

@@ -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>
}

View File

@@ -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

View File

@@ -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}

View File

@@ -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[]

View File

@@ -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#')

View File

@@ -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

View File

@@ -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[]