Merge branch 'release/v0.3.1' into develop
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
/*
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-04-09 18:58:21
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-04-20 10:39:17
|
||||
*/
|
||||
import { useState, useCallback, useEffect, useRef, type FC } from 'react'
|
||||
import { Popover, Flex } from 'antd'
|
||||
import { WarningFilled } from '@ant-design/icons'
|
||||
@@ -49,7 +55,7 @@ const specialValidators: Record<string, (val: any) => boolean> = {
|
||||
if (expr?.sub_variable_condition?.conditions?.length > 0) return expr.sub_variable_condition?.conditions.every(isSubExprSet)
|
||||
if (!expr.left) return false
|
||||
if (['not_empty', 'empty'].includes(expr.operator)) return true
|
||||
return !!expr.left && (!!expr.right || typeof expr.right === 'boolean' || typeof expr.right === 'number')
|
||||
return !!expr.left && (expr?.sub_variable_condition || !!expr.right || typeof expr.right === 'boolean' || typeof expr.right === 'number')
|
||||
}
|
||||
return val.some(c => !c?.expressions?.length || c.expressions.some((expr: any) => !isExprSet(expr)))
|
||||
},
|
||||
@@ -100,6 +106,18 @@ function validateNode(type: string, config: Record<string, any>): CheckError[] {
|
||||
if (isInvalid) errors.push({ key: specialKey, message: '' })
|
||||
})
|
||||
|
||||
// llm: vision_input required when vision is enabled
|
||||
if (type === 'llm') {
|
||||
const vision = get('vision')
|
||||
if (vision === true || vision === 'true') {
|
||||
const visionInput = get('vision_input')
|
||||
console.log('vision', vision, isEmpty(visionInput))
|
||||
if (isEmpty(visionInput)) {
|
||||
errors.push({ key: 'llm.vision_input', message: '' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// http-request body.data (binary) — not a top-level required field, check separately
|
||||
if (type === 'http-request') {
|
||||
const body = get('body')
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useVariableList } from '../Properties/hooks/useVariableList'
|
||||
import { isSubExprSet } from '../../utils'
|
||||
import { fileSubFieldOperators } from '../Properties/CaseList'
|
||||
|
||||
const caculateIsSet = (item: any, type: string) => {
|
||||
const calculateIsSet = (item: any, type: string) => {
|
||||
switch (type) {
|
||||
case 'categories':
|
||||
return typeof item?.class_name === 'string' && item?.class_name !== ''
|
||||
@@ -90,7 +90,7 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
|
||||
<div key={index} className="rb:bg-[#F0F3F8] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:rounded-md rb:py-1 rb:px-1.5 rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5">
|
||||
<Flex justify="space-between">
|
||||
<span>{t('workflow.config.question-classifier.class_name')} {index + 1}</span>
|
||||
{caculateIsSet(item, 'categories') ? t(`workflow.config.${data.type}.set`) : t(`workflow.config.${data.type}.unset`)}
|
||||
{calculateIsSet(item, 'categories') ? t(`workflow.config.${data.type}.set`) : t(`workflow.config.${data.type}.unset`)}
|
||||
</Flex>
|
||||
</div>
|
||||
))}
|
||||
@@ -100,17 +100,24 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
|
||||
<Flex vertical gap={4} className="rb:mt-3!">
|
||||
{data.config?.cases?.defaultValue.map((item: any, index: number) => (
|
||||
<div key={index} className={item.expressions.length > 0 ? '' : 'rb:mb-1'}>
|
||||
<Flex justify={item.expressions.length > 0 ? "space-between" : 'end'} className="rb:mb-1">
|
||||
{item.expressions.length > 0 && <span className="rb:text-[#5B6167] rb:text-[10px]">CASE{index + 1}</span>}
|
||||
<Flex justify={item.expressions.length > 0 ? "space-between" : 'end'} className="rb:mb-1! rb:leading-4">
|
||||
{item.expressions.length > 0 && <span className="rb:text-[#5B6167] rb:text-[10px] rb:pl-1">CASE{index + 1}</span>}
|
||||
<span className="rb:text-[#212332] rb:font-medium rb:text-[12px]">{index === 0 ? 'IF' : `ELIF`}</span>
|
||||
</Flex>
|
||||
{item.expressions.length > 0 && <Flex vertical gap={2}>
|
||||
{item.expressions.map((expression: any, eIndex: number) => (
|
||||
<div key={eIndex} className="rb:relative">
|
||||
{item.expressions.length > 1 && eIndex > 0 && <div className="rb:absolute rb:-top-2 rb:right-2 rb:text-[10px] rb:text-[#155EEF] rb:font-medium rb:leading-3.5 rb:text-right rb:pr-0.5">{item.logical_operator?.toLocaleUpperCase()}</div>}
|
||||
<Flex vertical gap={2} className="rb:bg-[#F0F3F8] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:rounded-md rb:py-1! rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5">
|
||||
{item.expressions.length > 1 && eIndex > 0 &&
|
||||
<div className="rb:absolute rb:-top-2 rb:right-2 rb:text-[10px] rb:text-[#155EEF] rb:font-medium rb:leading-3.5 rb:text-right rb:pr-0.5">{item.logical_operator?.toLocaleUpperCase()}</div>
|
||||
}
|
||||
<Flex vertical gap={2}
|
||||
className={clsx("rb:bg-[#F0F3F8] rb:shadow-[0px_2px_4px_0px_rgba(23,23,25,0.03)] rb:rounded-md rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-4", {
|
||||
'rb:pt-1!': expression.sub_variable_condition?.conditions?.length > 0,
|
||||
'rb:py-1!': !expression.sub_variable_condition?.conditions || !expression.sub_variable_condition?.conditions?.length
|
||||
})}
|
||||
>
|
||||
<Flex align="center">
|
||||
{caculateIsSet(expression, 'cases')
|
||||
{calculateIsSet(expression, 'cases')
|
||||
? <>
|
||||
{labelRender(expression.left)}
|
||||
<span className="rb:mx-1">{getLocaleField(expression.operator, typeof expression.right)}</span>
|
||||
@@ -120,11 +127,16 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
|
||||
}
|
||||
</Flex>
|
||||
{expression.sub_variable_condition?.conditions?.length > 0 && expression.sub_variable_condition?.conditions.every(isSubExprSet)
|
||||
? <div className="rb-border-l rb:ml-2 rb:mt-1.5">
|
||||
? <div className="rb-border-l rb:ml-2 rb:mt-1">
|
||||
{expression.sub_variable_condition?.conditions.map((sub: any, sIndex: number) => (
|
||||
<div key={sIndex} className="rb:relative">
|
||||
{expression.sub_variable_condition?.conditions.length > 1 && sIndex > 0 && <div className="rb:absolute rb:-top-2 rb:right-2 rb:text-[10px] rb:text-[#155EEF] rb:font-medium rb:leading-3.5 rb:text-right rb:pr-0.5">{expression.sub_variable_condition?.logical_operator?.toLocaleUpperCase()}</div>}
|
||||
<Flex align="center" className=" rb:py-1! rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5">
|
||||
<Flex align="center"
|
||||
className={clsx("rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5", {
|
||||
'rb:py-1!': sIndex !== 0,
|
||||
'rb:pb-1': sIndex === 0
|
||||
})}
|
||||
>
|
||||
<span className="rb:text-[#155EEF]">{sub.key}</span>
|
||||
<span className="rb:mx-1">{getSubLocaleField(sub.operator, sub.key)}</span>
|
||||
<span className="rb:break-all rb:line-clamp-1">
|
||||
@@ -140,7 +152,7 @@ const ConditionNode: ReactShapeConfig['component'] = ({ node }) => {
|
||||
))}
|
||||
</div>
|
||||
: expression.sub_variable_condition?.conditions?.length > 0
|
||||
? <Flex align="center" className="rb:mt-1! rb:pl-2! rb:rounded-md rb:py-1! rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-3.5">
|
||||
? <Flex align="center" className="rb:pl-2! rb:rounded-md rb:pb-1! rb:px-1.5! rb:text-[10px] rb:text-[#5B6167] rb:font-medium rb:leading-4">
|
||||
{t(`workflow.config.${data.type}.unset`)}
|
||||
</Flex>
|
||||
: null
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: ZhaoYing
|
||||
* @Date: 2026-02-09 18:24:53
|
||||
* @Last Modified by: ZhaoYing
|
||||
* @Last Modified time: 2026-04-17 20:47:49
|
||||
* @Last Modified time: 2026-04-20 10:46:05
|
||||
*/
|
||||
import { useEffect, useMemo, type FC } from 'react'
|
||||
import clsx from 'clsx'
|
||||
@@ -39,7 +39,7 @@ interface Expression {
|
||||
sub_variable_condition?: SubVariableCondition;
|
||||
}
|
||||
|
||||
interface CaseItem {
|
||||
export interface CaseItem {
|
||||
logical_operator: 'and' | 'or';
|
||||
expressions: Expression[];
|
||||
}
|
||||
@@ -274,7 +274,9 @@ const ArrayFileSubConditions: FC<ArrayFileSubConditionsProps> = ({ conditionFiel
|
||||
className="rb:w-full!"
|
||||
suffix="Byte"
|
||||
size="small"
|
||||
onChange={(value) => { form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'right'], value); }}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue([name, caseIndex, 'expressions', conditionIndex, 'sub_variable_condition', 'conditions', subIndex, 'value'], value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</Form.Item>
|
||||
@@ -483,13 +485,24 @@ const CaseList: FC<CaseListProps> = ({
|
||||
form.setFieldValue([name, index, 'logical_operator'], currentValue === 'and' ? 'or' : 'and');
|
||||
};
|
||||
|
||||
const handleLeftFieldChange = (caseIndex: number, conditionIndex: number, newValue: string) => {
|
||||
form.setFieldValue([name, caseIndex, 'expressions', conditionIndex], {
|
||||
left: newValue,
|
||||
operator: undefined,
|
||||
right: undefined,
|
||||
input_type: 'constant'
|
||||
});
|
||||
const handleLeftFieldChange = (caseIndex: number, conditionIndex: number, newValue: string, option?: Suggestion | undefined) => {
|
||||
if (option?.dataType === 'array[file]') {
|
||||
form.setFieldValue([name, caseIndex, 'expressions', conditionIndex], {
|
||||
left: newValue,
|
||||
operator: undefined,
|
||||
sub_variable_condition: {
|
||||
conditions: [],
|
||||
logical_operator: 'and'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
form.setFieldValue([name, caseIndex, 'expressions', conditionIndex], {
|
||||
left: newValue,
|
||||
operator: undefined,
|
||||
right: undefined,
|
||||
input_type: 'constant'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddCase = (addCaseFunc: Function) => {
|
||||
@@ -590,7 +603,7 @@ const CaseList: FC<CaseListProps> = ({
|
||||
options={options}
|
||||
size="small"
|
||||
allowClear={false}
|
||||
onChange={(val) => handleLeftFieldChange(caseIndex, conditionIndex, val as string)}
|
||||
onChange={(val, option) => handleLeftFieldChange(caseIndex, conditionIndex, val as string, option as unknown as Suggestion)}
|
||||
variant="borderless"
|
||||
className="rb:w-36!"
|
||||
/>
|
||||
|
||||
@@ -29,12 +29,13 @@ const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfi
|
||||
if (value && JSON.stringify(value) !== JSON.stringify(editConfig)) {
|
||||
setEditConfig({ ...(value || {}) })
|
||||
const knowledge_bases = [...(value.knowledge_bases || [])]
|
||||
setKnowledgeList(knowledge_bases)
|
||||
|
||||
// 检查是否有knowledge_bases缺少name字段
|
||||
const basesWithoutName = knowledge_bases.filter(base => !base.name)
|
||||
if (basesWithoutName.length > 0) {
|
||||
// 调用接口获取完整的知识库信息
|
||||
getKnowledgeBaseList().then(res => {
|
||||
getKnowledgeBaseList(undefined, { kb_ids: basesWithoutName.map(vo => vo.kb_id).join(',') }).then(res => {
|
||||
const fullBases = knowledge_bases.map(base => {
|
||||
if (!base.name) {
|
||||
const fullBase = res.items.find((item: any) => item.id === base.kb_id)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type FC, useEffect, useState, useMemo } from "react";
|
||||
import { type FC, useEffect, useState } from "react";
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Form, Select, Switch, Cascader, type CascaderProps, Tooltip } from 'antd'
|
||||
import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin'
|
||||
@@ -45,15 +45,15 @@ const ToolConfig: FC<{ options: Suggestion[]; }> = ({
|
||||
getToolDetail(values.tool_id)
|
||||
.then(res => {
|
||||
const detail = res as { tool_type: ToolType; }
|
||||
|
||||
|
||||
getTools({ tool_type: detail.tool_type })
|
||||
.then(toolsRes => {
|
||||
const tools = toolsRes as ToolItem[]
|
||||
|
||||
|
||||
getToolMethods(values.tool_id)
|
||||
.then(methodsRes => {
|
||||
const response = methodsRes as Array<{ method_id: string; name: string; parameters: Parameter[] }>
|
||||
|
||||
|
||||
setOptionList(prevList => {
|
||||
return prevList.map(item => {
|
||||
if (item.value === detail.tool_type) {
|
||||
@@ -76,7 +76,7 @@ const ToolConfig: FC<{ options: Suggestion[]; }> = ({
|
||||
return item
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
if (response.length > 1) {
|
||||
const filterTarget = response.find(vo => vo.name === values.tool_parameters?.operation)
|
||||
if (filterTarget) {
|
||||
@@ -98,7 +98,7 @@ const ToolConfig: FC<{ options: Suggestion[]; }> = ({
|
||||
useEffect(() => {
|
||||
if (values.tools && values.tools.length === 3) {
|
||||
const [toolType, toolId, operation] = values.tools
|
||||
|
||||
|
||||
// 从 optionList 中查找对应的参数
|
||||
const typeOption = optionList.find(opt => opt.value === toolType)
|
||||
if (typeOption?.children) {
|
||||
@@ -147,21 +147,26 @@ const ToolConfig: FC<{ options: Suggestion[]; }> = ({
|
||||
};
|
||||
|
||||
const handleChange: CascaderProps<Option>['onChange'] = (value, selectedOptions) => {
|
||||
if (!value) {
|
||||
setParameters([])
|
||||
form.resetFields()
|
||||
return
|
||||
}
|
||||
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||
const curParameters = [...(targetOption.parameters ?? [])]
|
||||
setParameters([...curParameters])
|
||||
const inititalValue: any = { tool_id: selectedOptions[1].value, tool_parameters: {} }
|
||||
const initialValue: any = { tool_id: selectedOptions[1].value, tool_parameters: { operation: undefined } }
|
||||
|
||||
if (value[0] === 'mcp' || (value[0] === 'builtin' && selectedOptions[1]?.children && selectedOptions[1].children.length > 1)) {
|
||||
inititalValue.tool_parameters.operation = value?.[2]
|
||||
initialValue.tool_parameters.operation = value?.[2]
|
||||
} else if (value[0] === 'custom') {
|
||||
inititalValue.tool_parameters.operation = selectedOptions?.[2].method_id
|
||||
initialValue.tool_parameters.operation = selectedOptions?.[2].method_id
|
||||
}
|
||||
curParameters.forEach(vo => {
|
||||
inititalValue.tool_parameters[vo.name] = vo.default
|
||||
initialValue.tool_parameters[vo.name] = vo.default
|
||||
})
|
||||
|
||||
form.setFieldsValue(inititalValue)
|
||||
form.setFieldsValue(initialValue)
|
||||
}
|
||||
|
||||
// string -> string
|
||||
@@ -209,9 +214,9 @@ const ToolConfig: FC<{ options: Suggestion[]; }> = ({
|
||||
name="tools"
|
||||
label={t('workflow.config.tool.tool_id')}
|
||||
>
|
||||
<Cascader
|
||||
<Cascader
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
options={optionList}
|
||||
options={optionList}
|
||||
loadData={loadData}
|
||||
onChange={handleChange}
|
||||
changeOnSelect={false}
|
||||
@@ -239,8 +244,8 @@ const ToolConfig: FC<{ options: Suggestion[]; }> = ({
|
||||
{parameter.type === 'string' && parameter.enum && parameter.enum.length > 0
|
||||
? <Select size="small" options={parameter.enum.map(vo => ({ value: vo, label: vo }))} placeholder={t('common.pleaseSelect')} />
|
||||
: parameter.type === 'boolean'
|
||||
? <Switch size="small" />
|
||||
: <Editor
|
||||
? <Switch size="small" />
|
||||
: <Editor
|
||||
variant="outlined"
|
||||
type="input"
|
||||
size="small"
|
||||
|
||||
@@ -393,18 +393,19 @@ export const useVariableList = (
|
||||
// Add chat variables
|
||||
chatVariables?.forEach(v => addVariable(list, keys, `CONVERSATION_${v.name}`, v.name, v.type, `conv.${v.name}`, { type: 'CONVERSATION', name: 'CONVERSATION', icon: '' }, { group: 'CONVERSATION' }));
|
||||
|
||||
// Process each relevant node: non-list-operator first, then list-operator
|
||||
const listOperatorIds: string[] = [];
|
||||
// Process each relevant node: deferred types last (they depend on prior variables)
|
||||
const deferredIds: string[] = [];
|
||||
relevantIds.forEach(id => {
|
||||
const node = nodes.find(n => n.id === id);
|
||||
if (!node) return;
|
||||
if (node.getData()?.type === 'list-operator') {
|
||||
listOperatorIds.push(id);
|
||||
const t = node.getData()?.type;
|
||||
if (['var-aggregator', 'list-operator', 'iteration'].includes(t)) {
|
||||
deferredIds.push(id);
|
||||
} else {
|
||||
processNodeVariables(node.getData(), node.getData().id, list, keys);
|
||||
}
|
||||
});
|
||||
listOperatorIds.forEach(id => {
|
||||
deferredIds.forEach(id => {
|
||||
const node = nodes.find(n => n.id === id);
|
||||
if (node) processNodeVariables(node.getData(), node.getData().id, list, keys);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user