feat(web): workflow check list
This commit is contained in:
285
web/src/views/Workflow/components/CheckList/index.tsx
Normal file
285
web/src/views/Workflow/components/CheckList/index.tsx
Normal file
@@ -0,0 +1,285 @@
|
||||
import { type FC, useState, useCallback, useEffect, useRef } from 'react'
|
||||
import { Popover, Flex } from 'antd'
|
||||
import { WarningFilled } from '@ant-design/icons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Node } from '@antv/x6';
|
||||
|
||||
import type { WorkflowRef } from '@/views/ApplicationConfig/types'
|
||||
import { nodeLibrary } from '../../constant'
|
||||
import { getToolMethods } from '@/api/tools'
|
||||
import RbDrawer from '@/components/RbDrawer'
|
||||
|
||||
interface CheckListProps {
|
||||
workflowRef: React.RefObject<WorkflowRef>
|
||||
}
|
||||
|
||||
interface CheckError {
|
||||
key: string
|
||||
message: string
|
||||
}
|
||||
|
||||
interface NodeCheckResult {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
icon: string
|
||||
errors: CheckError[]
|
||||
}
|
||||
|
||||
const allNodes = nodeLibrary.flatMap(c => c.nodes)
|
||||
const nodeIconMap: Record<string, string> = Object.fromEntries(allNodes.map(n => [n.type, n.icon]))
|
||||
const nodeConfigMap: Record<string, Record<string, any>> = Object.fromEntries(
|
||||
allNodes.filter(n => n.config).map(n => [n.type, n.config!])
|
||||
)
|
||||
|
||||
// Special validators for fields that need deeper checks beyond simple empty check
|
||||
const specialValidators: Record<string, (val: any) => boolean> = {
|
||||
// llm.messages: at least one message with non-empty content
|
||||
'llm.messages': (val: any[]) => !Array.isArray(val) || !val.some(m => m?.content && String(m.content).trim()),
|
||||
// knowledge-retrieval.knowledge_retrieval: knowledge_bases array must be non-empty
|
||||
'knowledge-retrieval.knowledge_retrieval': (val: any) => !(val?.knowledge_bases?.length > 0),
|
||||
'memory-write.messages': (val: any[]) => !Array.isArray(val) || !val.some(m => m?.content && String(m.content).trim()),
|
||||
// if-else.cases: every case must have at least one expression, and every expression must be fully set
|
||||
'if-else.cases': (val: any[]) => {
|
||||
if (!Array.isArray(val) || !val.length) return true
|
||||
return val.some(c => {
|
||||
if (!c?.expressions?.length) return true
|
||||
return c.expressions.some((expr: any) => {
|
||||
if (!expr?.left) return true
|
||||
if (['not_empty', 'empty'].includes(expr.operator)) return false
|
||||
return !(!!expr.left && (!!expr.right || typeof expr.right === 'boolean' || typeof expr.right === 'number'))
|
||||
})
|
||||
})
|
||||
},
|
||||
// question-classifier.categories: every category must have a value
|
||||
'question-classifier.categories': (val: any[]) => !Array.isArray(val) || !val.some(c => c?.class_name && String(c.class_name).trim()),
|
||||
// var-aggregator.group_variables: must be non-empty array
|
||||
'var-aggregator.group_variables': (val: any[]) => !Array.isArray(val) || !val.length,
|
||||
// assigner.assignments: every item needs variable_selector + operation; value required unless operation is 'clear'
|
||||
'assigner.assignments': (val: any[]) => {
|
||||
if (!Array.isArray(val) || !val.length) return false
|
||||
return val.some(a => {
|
||||
if (!a?.variable_selector || !a?.operation) return true
|
||||
if (a.operation === 'clear') return false
|
||||
return a.value === undefined || a.value === null || a.value === ''
|
||||
})
|
||||
},
|
||||
// http-request.body: binary content_type requires data
|
||||
'http-request.body': (val: any) => val?.content_type === 'binary' && !val?.data,
|
||||
// tool.tool_parameters: validated async via API, placeholder always returns false
|
||||
'tool.tool_parameters': () => false,
|
||||
// code.input_variables: if non-empty, every item must have both name and variable
|
||||
'code.input_variables': (val: any[]) => Array.isArray(val) && val.length > 0 && val.some(v => !v?.name || !v?.variable),
|
||||
// code.output_variables: must be non-empty
|
||||
'code.output_variables': (val: any[]) => !Array.isArray(val) || !val.length,
|
||||
// jinja-render.mapping: if non-empty, every item must have a name
|
||||
'jinja-render.mapping': (val: any[]) => Array.isArray(val) && val.length > 0 && val.some(v => !v?.name || !v?.value),
|
||||
}
|
||||
|
||||
function isEmpty(val: any): boolean {
|
||||
console.log('validateNode isEmpty', val, val === undefined || val === null || val === '')
|
||||
if (val === undefined || val === null || val === '') return true
|
||||
if (Array.isArray(val)) return val.length === 0
|
||||
return false
|
||||
}
|
||||
|
||||
function validateNode(type: string, config: Record<string, any>): CheckError[] {
|
||||
const errors: CheckError[] = []
|
||||
const nodeConfig = nodeConfigMap[type]
|
||||
if (!nodeConfig) return errors
|
||||
|
||||
const get = (key: string) => config[key]?.defaultValue
|
||||
|
||||
Object.entries(nodeConfig).forEach(([field, fieldConfig]) => {
|
||||
if (!fieldConfig?.required) return
|
||||
const val = get(field)
|
||||
const specialKey = `${type}.${field}`
|
||||
const specialValidator = specialValidators[specialKey]
|
||||
const isInvalid = specialValidator ? specialValidator(val) : isEmpty(val)
|
||||
console.log('validateNode', val, specialKey, specialValidator, isEmpty(val))
|
||||
if (isInvalid) errors.push({ key: specialKey, message: '' })
|
||||
})
|
||||
|
||||
// http-request body.data (binary) — not a top-level required field, check separately
|
||||
if (type === 'http-request') {
|
||||
const body = get('body')
|
||||
if (body?.content_type === 'binary' && !body?.data) {
|
||||
errors.push({ key: 'http-request.body.data', message: '' })
|
||||
}
|
||||
}
|
||||
|
||||
// console.log('nodeConfig', nodeConfigMap, nodeConfig, errors)
|
||||
return errors
|
||||
}
|
||||
|
||||
const CheckList: FC<CheckListProps> = ({ workflowRef }) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [results, setResults] = useState<NodeCheckResult[]>([])
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout>>()
|
||||
|
||||
const runCheck = useCallback(async () => {
|
||||
const graph = workflowRef.current?.graphRef?.current
|
||||
if (!graph) return []
|
||||
|
||||
const nodes = graph.getNodes()
|
||||
const edges = graph.getEdges()
|
||||
const sourceIds = new Set<string>()
|
||||
const targetIds = new Set<string>()
|
||||
// child-to-child edges within same parent (cycle)
|
||||
const childTargetIds = new Set<string>()
|
||||
edges.forEach(e => {
|
||||
sourceIds.add(e.getSourceCellId())
|
||||
targetIds.add(e.getTargetCellId())
|
||||
const srcData = graph.getCellById(e.getSourceCellId())?.getData()
|
||||
const tgtData = graph.getCellById(e.getTargetCellId())?.getData()
|
||||
if (srcData?.cycle && tgtData?.cycle && srcData.cycle === tgtData.cycle) {
|
||||
childTargetIds.add(e.getTargetCellId())
|
||||
}
|
||||
})
|
||||
|
||||
const checked: NodeCheckResult[] = []
|
||||
for (const node of nodes) {
|
||||
const data = node.getData()
|
||||
if (!data || ['add-node', 'notes', 'cycle-start', 'break'].includes(data.type)) continue
|
||||
|
||||
const errors: CheckError[] = []
|
||||
|
||||
|
||||
// Check connectivity
|
||||
const isChildNode = !!data.cycle
|
||||
const hasIncoming = isChildNode ? childTargetIds.has(node.id) : !['start', 'cycle-start'].includes(data.type) ? targetIds.has(node.id) : true
|
||||
if (!hasIncoming) {
|
||||
errors.push({ key: 'notConnected', message: t('workflow.notConnected') })
|
||||
}
|
||||
|
||||
// Validate config
|
||||
const configErrors = validateNode(data.type, data.config ?? {})
|
||||
configErrors.forEach(e => {
|
||||
errors.push({ key: e.key, message: `${t(`workflow.checkListErrors.${e.key}`)} ${t('workflow.cannotBeEmpty')}`.trim() })
|
||||
})
|
||||
|
||||
// Tool node: fetch parameters via API and check required fields
|
||||
if (data.type === 'tool') {
|
||||
const toolId = data.config?.tool_id?.defaultValue ?? data.config?.tool_id
|
||||
const toolParameters = data.config?.tool_parameters?.defaultValue ?? data.config?.tool_parameters ?? {}
|
||||
if (toolId) {
|
||||
try {
|
||||
const methods = await getToolMethods(toolId) as Array<{ name: string; parameters: Array<{ name: string; required: boolean }> }>
|
||||
const operation = toolParameters?.operation
|
||||
const method = operation ? methods.find(m => m.name === operation) : methods[0]
|
||||
if (method) {
|
||||
const missingParams = method.parameters.filter(p => p.required && (toolParameters[p.name] === undefined || toolParameters[p.name] === null || toolParameters[p.name] === ''))
|
||||
missingParams.forEach(p => errors.push({ key: 'tool.tool_parameters', message: `${p.name} ${t('workflow.cannotBeEmpty')}` }))
|
||||
}
|
||||
} catch {
|
||||
// ignore API errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
checked.push({
|
||||
id: node.id,
|
||||
name: data.name || t(`workflow.${data.type}`),
|
||||
type: data.type,
|
||||
icon: nodeIconMap[data.type] ?? '',
|
||||
errors,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return checked
|
||||
}, [workflowRef.current?.graphRef?.current, t])
|
||||
|
||||
const scheduleCheck = useCallback(() => {
|
||||
clearTimeout(timerRef.current)
|
||||
timerRef.current = setTimeout(async () => {
|
||||
setResults(await runCheck())
|
||||
}, 500)
|
||||
}, [runCheck])
|
||||
|
||||
useEffect(() => {
|
||||
const graph = workflowRef.current?.graphRef?.current
|
||||
if (!graph) return
|
||||
const events = ['node:added', 'node:removed', 'node:change:data', 'edge:added', 'edge:removed']
|
||||
events.forEach(e => graph.on(e, scheduleCheck))
|
||||
scheduleCheck()
|
||||
return () => {
|
||||
events.forEach(e => graph.off(e, scheduleCheck))
|
||||
clearTimeout(timerRef.current)
|
||||
}
|
||||
}, [workflowRef.current?.graphRef?.current])
|
||||
|
||||
const handleOpen = () => {
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
const focusNode = (id: string) => {
|
||||
const graph = workflowRef.current?.graphRef?.current
|
||||
if (!graph) return
|
||||
const node = graph.getCellById(id)
|
||||
if (node) {
|
||||
workflowRef.current?.nodeClick({node} as { node: Node })
|
||||
}
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover content={t('workflow.checkList')} classNames={{ body: 'rb:py-0.5! rb:px-1! rb:rounded-[6px]! rb:text-[12px]!' }}>
|
||||
<div className="rb:relative rb:cursor-pointer rb:size-7.5" onClick={handleOpen}>
|
||||
<div className="rb:size-7.5 rb:border rb:border-[#EBEBEB] rb:hover:bg-[#F6F6F6] rb:rounded-[10px] rb:bg-[url('@/assets/images/workflow/checkList.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat" />
|
||||
{results.length > 0 && (
|
||||
<span className="rb:absolute rb:-top-1 rb:-right-1 rb:min-w-3.5 rb:h-3.5 rb:px-0.5 rb:bg-[#F04438] rb:text-white rb:text-[9px] rb:leading-3.5 rb:rounded-full rb:flex rb:items-center rb:justify-center">
|
||||
{results.reduce((sum, n) => sum + n.errors.length, 0)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Popover>
|
||||
<RbDrawer
|
||||
title={
|
||||
<span className="rb:text-[16px] rb:font-semibold">
|
||||
{t('workflow.checkList')}{results.length > 0 ? `(${results.reduce((sum, n) => sum + n.errors.length, 0)})` : ''}
|
||||
</span>
|
||||
}
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
width={360}
|
||||
styles={{ body: { padding: '12px 16px' } }}
|
||||
>
|
||||
<p className="rb:text-[12px] rb:text-[#5B6167] rb:mb-3">{t('workflow.checkListDesc')}</p>
|
||||
{results.length === 0
|
||||
? <div className="rb:text-center rb:text-[#5B6167] rb:text-[13px] rb:py-8">{t('workflow.checkListEmpty')}</div>
|
||||
: <Flex vertical gap={8} className="rb:pb-3!">
|
||||
{results.map(node => (
|
||||
<div key={node.id} className="rb-border rb:rounded-lg">
|
||||
<Flex align="center" gap={8} className="rb:px-3! rb:py-2.5! rb-border-b">
|
||||
<div className={`rb:size-5 rb:rounded-md rb:bg-size-[14px_14px] rb:bg-center rb:bg-no-repeat ${node.icon}`} />
|
||||
<span className="rb:text-[13px] rb:font-medium rb:flex-1 rb:truncate">{node.name}</span>
|
||||
<span
|
||||
className="rb:text-[12px] rb:text-[#155EEF] rb:cursor-pointer rb:whitespace-nowrap"
|
||||
onClick={() => focusNode(node.id)}
|
||||
>
|
||||
{t('workflow.goto')} →
|
||||
</span>
|
||||
</Flex>
|
||||
|
||||
<Flex vertical gap={4} className="rb:px-3! rb:py-2!">
|
||||
{node.errors.map((err, i) => (
|
||||
<Flex key={i} align="center" gap={6}>
|
||||
<WarningFilled className="rb:text-[#FF5D34]! rb:text-[12px] rb:shrink-0" />
|
||||
<span className="rb:text-[12px] rb:text-[#5B6167]">{err.message}</span>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</div>
|
||||
))}
|
||||
</Flex>
|
||||
}
|
||||
</RbDrawer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CheckList
|
||||
@@ -27,7 +27,8 @@ const OutputList: FC<OutputListProps> = ({ label, name, extra }) => {
|
||||
<>
|
||||
<Flex align="center" justify="space-between" className="rb:mb-2!">
|
||||
<div className="rb:text-[12px] rb:font-medium rb:leading-4.5">
|
||||
{label}
|
||||
|
||||
<span className="rb:text-[#ff5d34] rb:text-[14px] rb:font-[SimSun,sans-serif] rb:mr-1">*</span>{label}
|
||||
</div>
|
||||
|
||||
<Space size={8}>
|
||||
|
||||
@@ -58,7 +58,7 @@ const ConditionList: FC<CaseListProps> = ({
|
||||
const { t } = useTranslation();
|
||||
const form = Form.useFormInstance();
|
||||
|
||||
const handleLeftFieldChange = (index: number, newValue: string) => {
|
||||
const handleLeftFieldChange = (index: number, newValue?: string | string[]) => {
|
||||
form.setFieldsValue({
|
||||
[parentName]: {
|
||||
expressions: {
|
||||
|
||||
@@ -87,7 +87,9 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
|
||||
return (
|
||||
<>
|
||||
<Flex align="center" justify="space-between" className="rb:mb-1!">
|
||||
<div className="rb:font-medium rb:text-[12px] rb:leading-4.5">API</div>
|
||||
<div className="rb:font-medium rb:text-[12px] rb:leading-4.5">
|
||||
<span className="rb:text-[#ff5d34] rb:text-[14px] rb:font-[SimSun,sans-serif] rb:mr-1">*</span>API
|
||||
</div>
|
||||
<Button onClick={handleChangeAuth}
|
||||
size="small"
|
||||
type="text"
|
||||
@@ -145,7 +147,7 @@ const HttpRequest: FC<{ options: Suggestion[]; selectedNode?: any; graphRef?: an
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="BODY" className="rb:mb-0!">
|
||||
<Form.Item label="BODY" className="rb:mb-0!" required>
|
||||
<Form.Item name={['body', 'content_type']} className="rb:mb-3!">
|
||||
<Radio.Group
|
||||
size="small"
|
||||
|
||||
@@ -114,7 +114,7 @@ const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfi
|
||||
<div>
|
||||
<Flex align="center" justify="space-between" className="rb:mb-2!">
|
||||
<div className="rb:text-[12px] rb:font-medium rb:leading-4.5">
|
||||
{t('application.knowledgeBaseAssociation')}
|
||||
<span className="rb:text-[#ff5d34] rb:text-[14px] rb:font-[SimSun,sans-serif] rb:mr-1">*</span>{t('application.knowledgeBaseAssociation')}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
|
||||
@@ -18,6 +18,7 @@ const ModelConfig: FC = () => {
|
||||
name="model_id"
|
||||
label={t('workflow.config.llm.model_id')}
|
||||
className={model_id ? 'rb:mb-2!' : 'rb:mb-4!'}
|
||||
required
|
||||
>
|
||||
<ModelSelect
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
|
||||
@@ -41,7 +41,7 @@ const ParamsList: FC<ParamsListProps> = ({
|
||||
return (
|
||||
<div>
|
||||
<div className="rb:leading-4.25 rb:text-[12px] rb:font-medium rb:mb-2">
|
||||
{label}
|
||||
<span className="rb:text-[#ff5d34] rb:text-[14px] rb:font-[SimSun,sans-serif] rb:mr-1">*</span>{label}
|
||||
</div>
|
||||
|
||||
<Flex gap={10} vertical>
|
||||
|
||||
@@ -186,7 +186,7 @@ const VariableSelect: FC<VariableSelectProps> = ({
|
||||
const nodeData = (parentOfSelected ?? selectedSuggestion)?.nodeData;
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="rb:relative rb:w-full">
|
||||
<div ref={containerRef} className={`rb:relative rb:w-full ${className}`}>
|
||||
{/* Trigger */}
|
||||
<div
|
||||
className={clsx(
|
||||
|
||||
@@ -585,7 +585,7 @@ const Properties: FC<PropertiesProps> = ({
|
||||
|
||||
if (config.type === 'messageEditor') {
|
||||
return (
|
||||
<Form.Item key={key} name={key} label={selectedNode?.data?.type === 'memory-write' ? t(`workflow.config.${selectedNode?.data?.type}.${key}`) : undefined}>
|
||||
<Form.Item key={key} name={key} required={config.required} label={selectedNode?.data?.type === 'memory-write' ? t(`workflow.config.${selectedNode?.data?.type}.${key}`) : undefined}>
|
||||
<MessageEditor
|
||||
title={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
|
||||
placeholder={t(config.placeholder || 'common.pleaseEnter')}
|
||||
@@ -733,6 +733,7 @@ const Properties: FC<PropertiesProps> = ({
|
||||
: ''
|
||||
}
|
||||
hidden={Boolean(config.hidden)}
|
||||
required={config.required}
|
||||
>
|
||||
{config.type === 'input'
|
||||
? <Input placeholder={t('common.pleaseEnter')} />
|
||||
|
||||
@@ -64,11 +64,11 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "end", icon: 'rb:bg-[url("@/assets/images/workflow/end.svg")]',
|
||||
{ type: "end", icon: 'rb:bg-[url("@/assets/images/workflow/end.svg")]',
|
||||
config: {
|
||||
output: {
|
||||
type: 'editor'
|
||||
type: 'editor',
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -82,6 +82,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
config: {
|
||||
model_id: {
|
||||
type: 'define',
|
||||
required: true,
|
||||
params: { type: 'llm,chat' }, // llm/chat
|
||||
valueKey: 'id',
|
||||
labelKey: 'name',
|
||||
@@ -106,6 +107,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
},
|
||||
messages: {
|
||||
type: 'define',
|
||||
required: true,
|
||||
defaultValue: [
|
||||
{
|
||||
role: 'SYSTEM',
|
||||
@@ -138,7 +140,8 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
type: 'variableList',
|
||||
},
|
||||
knowledge_retrieval: {
|
||||
type: 'knowledge'
|
||||
type: 'knowledge',
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -146,15 +149,18 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
config: {
|
||||
model_id: {
|
||||
type: 'modelSelect',
|
||||
required: true,
|
||||
params: { type: 'llm,chat' }, // llm/chat
|
||||
},
|
||||
text: {
|
||||
type: 'variableList',
|
||||
required: true,
|
||||
filterLoopIterationVars: true,
|
||||
placeholder: 'workflow.config.parameter-extractor.textPlaceholder'
|
||||
},
|
||||
params: {
|
||||
type: 'paramList',
|
||||
required: true,
|
||||
},
|
||||
prompt: {
|
||||
type: 'messageEditor',
|
||||
@@ -173,16 +179,19 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
config: {
|
||||
message: {
|
||||
type: 'editor',
|
||||
required: true,
|
||||
isArray: false
|
||||
},
|
||||
config_id: {
|
||||
type: 'customSelect',
|
||||
required: true,
|
||||
url: memoryConfigListUrl,
|
||||
valueKey: 'config_id',
|
||||
labelKey: 'config_name'
|
||||
},
|
||||
search_switch: {
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: [
|
||||
{ value: '0', label: 'memoryConversation.deepThinking' },
|
||||
{ value: '1', label: 'memoryConversation.normalReply' },
|
||||
@@ -201,12 +210,14 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
},
|
||||
messages: {
|
||||
type: 'messageEditor',
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
placeholder: 'workflow.config.llm.messagesPlaceholder',
|
||||
isArray: true
|
||||
},
|
||||
config_id: {
|
||||
type: 'customSelect',
|
||||
required: true,
|
||||
url: memoryConfigListUrl,
|
||||
valueKey: 'config_id',
|
||||
labelKey: 'config_name'
|
||||
@@ -222,6 +233,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
config: {
|
||||
cases: {
|
||||
type: 'caseList',
|
||||
required: true,
|
||||
defaultValue: [
|
||||
{
|
||||
logical_operator: 'and',
|
||||
@@ -235,13 +247,16 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
config: {
|
||||
model_id: {
|
||||
type: 'modelSelect',
|
||||
required: true,
|
||||
params: { type: 'llm,chat' }, // llm/chat
|
||||
},
|
||||
input_variable: {
|
||||
type: 'variableList',
|
||||
required: true,
|
||||
},
|
||||
categories: {
|
||||
type: 'categoryList',
|
||||
required: true,
|
||||
defaultValue: [
|
||||
{},
|
||||
{}
|
||||
@@ -259,6 +274,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
config: {
|
||||
input: {
|
||||
type: 'variableList',
|
||||
required: true,
|
||||
filterNodeTypes: ['knowledge-retrieval', 'iteration', 'loop', 'parameter-extractor', 'code', 'CONVERSATION'],
|
||||
filterVariableNames: ['message']
|
||||
},
|
||||
@@ -281,6 +297,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
},
|
||||
output: {
|
||||
type: 'variableList',
|
||||
required: true,
|
||||
filterChildNodes: true
|
||||
},
|
||||
output_type: {
|
||||
@@ -321,6 +338,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
},
|
||||
group_variables: {
|
||||
type: 'groupVariableList',
|
||||
required: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
group_type: {
|
||||
@@ -332,6 +350,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
config: {
|
||||
assignments: {
|
||||
type: 'assignmentList',
|
||||
required: true,
|
||||
filterLoopIterationVars: true
|
||||
}
|
||||
}
|
||||
@@ -357,6 +376,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
},
|
||||
url: {
|
||||
type: 'messageEditor',
|
||||
required: true,
|
||||
isArray: false,
|
||||
},
|
||||
auth: {
|
||||
@@ -415,6 +435,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
config: {
|
||||
input_variables: {
|
||||
type: 'inputList',
|
||||
required: true,
|
||||
defaultValue: [{ name: 'arg1' }, { name: 'arg2' }]
|
||||
},
|
||||
language: {
|
||||
@@ -423,6 +444,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
},
|
||||
code: {
|
||||
type: 'messageEditor',
|
||||
required: true,
|
||||
isArray: false,
|
||||
language: ['python3', 'javascript'],
|
||||
titleVariant: 'borderless',
|
||||
@@ -433,6 +455,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
},
|
||||
output_variables: {
|
||||
type: 'outputList',
|
||||
required: true,
|
||||
defaultValue: [{name: 'result', type: 'string'}]
|
||||
},
|
||||
}
|
||||
@@ -441,10 +464,12 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
config: {
|
||||
mapping: {
|
||||
type: 'mappingList',
|
||||
required: true,
|
||||
defaultValue: [{name: 'arg1'}]
|
||||
},
|
||||
template: {
|
||||
type: 'messageEditor',
|
||||
required: true,
|
||||
isArray: false,
|
||||
language: 'jinja2',
|
||||
titleVariant: 'borderless',
|
||||
@@ -456,6 +481,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
config: {
|
||||
file_selector: {
|
||||
type: 'variableList',
|
||||
required: true,
|
||||
placeholder: 'common.pleaseSelect',
|
||||
onFilterVariableType: ['array[file]', 'file']
|
||||
}
|
||||
@@ -465,6 +491,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
||||
config: {
|
||||
input_list: {
|
||||
type: 'variableList',
|
||||
required: true,
|
||||
},
|
||||
filter_by: {
|
||||
type: 'define',
|
||||
|
||||
@@ -18,7 +18,6 @@ import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application'
|
||||
import { useUser } from '@/store/user';
|
||||
import type { FeaturesConfigForm } from '@/views/ApplicationConfig/types'
|
||||
import { calcConditionNodeTotalHeight, getConditionNodeCasePortY } from '../utils'
|
||||
import type { Suggestion } from '../components/Editor/plugin/AutocompletePlugin';
|
||||
|
||||
/**
|
||||
* Props for useWorkflowGraph hook
|
||||
@@ -76,6 +75,7 @@ export interface UseWorkflowGraphReturn {
|
||||
features?: FeaturesConfigForm;
|
||||
/** Get start node output variable list (user-defined + system variables) */
|
||||
getStartNodeVariables: () => Array<{ name: string; type: string; readonly?: boolean }>;
|
||||
nodeClick: ({ node }: { node: Node }) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1494,6 +1494,7 @@ export const useWorkflowGraph = ({
|
||||
setIsHandMode,
|
||||
onDrop,
|
||||
blankClick,
|
||||
nodeClick,
|
||||
deleteEvent,
|
||||
copyEvent,
|
||||
parseEvent,
|
||||
|
||||
@@ -28,6 +28,7 @@ const Workflow = forwardRef<WorkflowRef, { onFeaturesLoad?: (features: FeaturesC
|
||||
setIsHandMode,
|
||||
onDrop,
|
||||
blankClick,
|
||||
nodeClick,
|
||||
deleteEvent,
|
||||
copyEvent,
|
||||
parseEvent,
|
||||
@@ -71,7 +72,8 @@ const Workflow = forwardRef<WorkflowRef, { onFeaturesLoad?: (features: FeaturesC
|
||||
config,
|
||||
features: features,
|
||||
handleFeaturesConfig,
|
||||
handleSaveFeaturesConfig
|
||||
handleSaveFeaturesConfig,
|
||||
nodeClick
|
||||
}))
|
||||
return (
|
||||
<div className="rb:h-full rb:relative">
|
||||
|
||||
@@ -31,6 +31,7 @@ export interface NodeConfig {
|
||||
group_variables?: Array<{ key: string, value: string[] }>
|
||||
cycle?: string;
|
||||
cycle_vars?: Array<{ name: string; type: string; value: string; input_type: string; }>
|
||||
required?: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user