diff --git a/src/backend/bisheng/api/services/workflow.py b/src/backend/bisheng/api/services/workflow.py index 383c4fb0f..739a2acd7 100644 --- a/src/backend/bisheng/api/services/workflow.py +++ b/src/backend/bisheng/api/services/workflow.py @@ -136,7 +136,7 @@ def run_once(cls, login_user: UserPayload, node_input: Dict[str, any], node_data continue if node_data.type == NodeType.RAG.value and one['key'] != 'retrieved_result' and one['type'] != 'variable': continue - if node_data.type == NodeType.LLM.value and not one['key'].startswith('output'): + if node_data.type == NodeType.LLM.value and one['type'] != 'variable': continue if node_data.type == NodeType.AGENT.value and one['type'] not in ['tool', 'variable']: continue diff --git a/src/backend/bisheng/api/v1/chat.py b/src/backend/bisheng/api/v1/chat.py index 248e7774b..b7e1477a2 100644 --- a/src/backend/bisheng/api/v1/chat.py +++ b/src/backend/bisheng/api/v1/chat.py @@ -156,7 +156,7 @@ def get_app_chat_list(*, res_obj = PageList(list=[ AppChatList(user_name=user_map.get(one['user_id'], one['user_id']), flow_name=flow_map[one['flow_id']].name if flow_map.get(one['flow_id']) else one['flow_id'], - flow_type=FlowType.ASSISTANT.value if assistant_map.get(one['flow_id'], None) else FlowType.FLOW.value, + flow_type=FlowType.ASSISTANT.value if assistant_map.get(one['flow_id'], None) else flow_map[one['flow_id']].flow_type, **one) for one in res if flow_map.get(one['flow_id']) ], total=count) diff --git a/src/backend/bisheng/worker/workflow/redis_callback.py b/src/backend/bisheng/worker/workflow/redis_callback.py index d1985444e..c49545a52 100644 --- a/src/backend/bisheng/worker/workflow/redis_callback.py +++ b/src/backend/bisheng/worker/workflow/redis_callback.py @@ -3,6 +3,7 @@ import uuid from cachetools import TTLCache +from langchain_core.documents import Document from loguru import logger from bisheng.api.v1.schemas import ChatResponse @@ -114,6 +115,8 @@ def save_chat_message(self, chat_response: ChatResponse, source_documents=None) if source_documents: result = {} extra = {} + if isinstance(source_documents, Document): + result = source_documents source, result = sync_judge_source(result, source_documents, self.chat_id, extra) chat_response.source = source chat_response.extra = json.dumps(extra, ensure_ascii=False) diff --git a/src/backend/bisheng/workflow/graph/graph_engine.py b/src/backend/bisheng/workflow/graph/graph_engine.py index 47fba9f89..401cb376d 100644 --- a/src/backend/bisheng/workflow/graph/graph_engine.py +++ b/src/backend/bisheng/workflow/graph/graph_engine.py @@ -129,7 +129,7 @@ def parse_fan_in_node(self, node_id: str): no_wait_nodes = [] for one in source_ids: # output节点有特殊处理逻辑 - if one.startswith('output_'): + if one.startswith(('output_', 'condition_')): continue if one in all_next_nodes: no_wait_nodes.append(one) diff --git a/src/backend/bisheng/workflow/nodes/input/input.py b/src/backend/bisheng/workflow/nodes/input/input.py index 82c4e14ab..f3538ba7a 100644 --- a/src/backend/bisheng/workflow/nodes/input/input.py +++ b/src/backend/bisheng/workflow/nodes/input/input.py @@ -49,7 +49,7 @@ def _run(self, unique_id: str): def parse_log(self, unique_id: str, result: dict) -> Any: return [ - {"key": k, "value": v, "type": "variable"} for k, v in result.items() + {"key": f'{self.id}.{k}', "value": v, "type": "variable"} for k, v in result.items() ] def parse_upload_file(self, key: str, value: str) -> dict | None: diff --git a/src/backend/bisheng/workflow/nodes/llm/llm.py b/src/backend/bisheng/workflow/nodes/llm/llm.py index a823c488d..c7db22aa4 100644 --- a/src/backend/bisheng/workflow/nodes/llm/llm.py +++ b/src/backend/bisheng/workflow/nodes/llm/llm.py @@ -61,7 +61,7 @@ def parse_log(self, unique_id: str, result: dict) -> Any: if self._batch_variable_list: ret.insert(0, {"key": "batch_variable", "value": self._batch_variable_list, "type": "params"}) for k, v in result.items(): - ret.append({"key": k, "value": v, "type": "variable"}) + ret.append({"key": f'{self.id}.{k}', "value": v, "type": "variable"}) return ret def _run_once(self, diff --git a/src/backend/bisheng/workflow/nodes/output/output.py b/src/backend/bisheng/workflow/nodes/output/output.py index b9951ebc5..abe682519 100644 --- a/src/backend/bisheng/workflow/nodes/output/output.py +++ b/src/backend/bisheng/workflow/nodes/output/output.py @@ -120,7 +120,7 @@ def parse_template_msg(self, msg: str): node_id = one.split('.')[0] # 引用qa知识库节点时,展示溯源情况 if node_id.startswith('qa_retriever'): - self._source_documents = self.graph_state.get_variable(node_id, 'retrieved_result') + self._source_documents = self.graph_state.get_variable(node_id, '$retrieved_result$') var_map[one] = self.graph_state.get_variable_by_str(one) msg = msg_template.format(var_map) return msg diff --git a/src/frontend/package.json b/src/frontend/package.json index 45806e420..c6cd15a98 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -1,6 +1,6 @@ { "name": "bisheng", - "version": "0.4.1", + "version": "0.4.1.2", "private": true, "dependencies": { "@headlessui/react": "^2.0.4", diff --git a/src/frontend/public/locales/en/bs.json b/src/frontend/public/locales/en/bs.json index 00919d32f..bb1df27ff 100644 --- a/src/frontend/public/locales/en/bs.json +++ b/src/frontend/public/locales/en/bs.json @@ -556,7 +556,15 @@ "manageAppTemplates": "Manage Application Templates", "provideSceneTemplates": "We provide scene templates for you to use and reference", "noPermissionToPublish": "You do not have permission to publish this {{type}}, please contact the administrator to publish.", - "manageYourApplications": "Manage your applications on this page, including publishing, editing, etc." + "manageYourApplications": "Manage your applications on this page, including publishing, editing, etc.", + "workFlow": "Workflow", + "skillName": "Skill Name", + "pleaseFillIn": "Please fill in the {{labelName}} name", + "nameTooLong": "{{labelName}} name is too long, do not exceed 50 characters", + "addDescription": "Add some description to quickly let others understand your {{labelName}}", + "descriptionTooLong": "{{labelName}} description cannot exceed 200 characters", + "templateCreatedSuccessfully": "Template created successfully", + "workFlowName": "Workflow Name" }, "tools": { "addTool": "Add Tool", diff --git a/src/frontend/public/locales/en/flow.json b/src/frontend/public/locales/en/flow.json index 3afadd138..cb7485d53 100644 --- a/src/frontend/public/locales/en/flow.json +++ b/src/frontend/public/locales/en/flow.json @@ -113,6 +113,7 @@ "textInput": "Text Input", "dropdown": "Dropdown", "file": "File", + "text": "Text", "displayName": "Display Name", "variableName": "Variable Name", "options": "Options", @@ -153,5 +154,13 @@ "onlineVersionMessage": "The current version is online and cannot be modified. You can save the changes as a new version.", "unsavedChangesMessage": "You have unsaved changes. Are you sure you want to leave?", "runNode": "Run this node", - "copy": "Copy" + "copy": "Copy", + "cannotBeEmpty": "{{label}} cannot be empty", + "editReportTemplate": "Edit Report Template", + "nodeErrorMessage": "{{nodeName}} node error: {{varNameCn}} is invalid, possibly because the related node has been deleted or replaced. Please reassign the variable.", + "required": "Required", + "isRequired": "Is Required", + "documentKnowledgeBase": "Document Knowledge", + "temporarySessionFiles": "Temporary Files", + "storeFilesSentInCurrentSession": "Store files sent in the current session" } \ No newline at end of file diff --git a/src/frontend/public/locales/zh/bs.json b/src/frontend/public/locales/zh/bs.json index 4875cb439..02de68aed 100644 --- a/src/frontend/public/locales/zh/bs.json +++ b/src/frontend/public/locales/zh/bs.json @@ -553,7 +553,15 @@ "manageAppTemplates": "管理应用模板", "provideSceneTemplates": "我们提供场景模板供您使用和参考", "noPermissionToPublish": "您没有权限上线此{{type}},请联系管理员上线。", - "manageYourApplications": "在此页面管理您的应用,对应用上下线、编辑等等" + "manageYourApplications": "在此页面管理您的应用,对应用上下线、编辑等等", + "workFlow": "工作流", + "skillName": "技能名称", + "pleaseFillIn": "请填写{{labelName}}名称", + "nameTooLong": "{{labelName}}名称过长,不要超过50字", + "addDescription": "加些描述能够快速让别人理解您创造的{{labelName}}", + "descriptionTooLong": "{{labelName}}描述不可超过 200 字", + "templateCreatedSuccessfully": "模板创建成功", + "workFlowName": "工作流名称" }, "tools": { "addTool": "添加工具", diff --git a/src/frontend/public/locales/zh/flow.json b/src/frontend/public/locales/zh/flow.json index cf724f951..6c87e5e6e 100644 --- a/src/frontend/public/locales/zh/flow.json +++ b/src/frontend/public/locales/zh/flow.json @@ -114,6 +114,7 @@ "textInput": "文本输入", "dropdown": "下拉选项", "file": "文件", + "text": "文本", "displayName": "展示名称", "variableName": "变量名称", "options": "选项", @@ -153,5 +154,13 @@ "onlineVersionMessage": "当前版本已上线不可修改,可另存为新版本保存修改内容", "unsavedChangesMessage": "您有未保存的更改,确定要离开吗?", "runNode": "运行此节点", - "copy": "复制" + "copy": "复制", + "cannotBeEmpty": "{{label}}不可为空", + "editReportTemplate": "编辑报告模板", + "nodeErrorMessage": "{{nodeName}}节点错误:{{varNameCn}}已失效,可能是相关节点已被删除或替换,请重新引用变量。", + "required": "不可为空", + "isRequired": "是否必填", + "documentKnowledgeBase": "文档知识库", + "temporarySessionFiles": "临时会话文件", + "storeFilesSentInCurrentSession": "存储当前会话中发送的文件" } \ No newline at end of file diff --git a/src/frontend/src/pages/BuildPage/flow/FlowChat/messageStore.ts b/src/frontend/src/pages/BuildPage/flow/FlowChat/messageStore.ts index f0a048eaa..2484f1370 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowChat/messageStore.ts +++ b/src/frontend/src/pages/BuildPage/flow/FlowChat/messageStore.ts @@ -156,7 +156,7 @@ export const useMessageStore = create((set, get) => ({ })) }, insetNodeRun(data) { - if (['input', 'output', 'condition'].includes(data.messages?.node_id.split('_'))) return + if (['input', 'output', 'condition'].includes(data.message?.node_id.split('_')[0])) return set((state) => { let newChat = cloneDeep(state.messages); const { category, flow_id, chat_id, files, is_bot, liked, message, receiver, type, source, user_id } = data diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/RunLog.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/RunLog.tsx index c960ef62b..8a40b8159 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/RunLog.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/RunLog.tsx @@ -96,20 +96,20 @@ export default function RunLog({ node, children }) { node.group_params.forEach(group => { group.params.forEach(param => { if (Array.isArray(param.value) && param.value.some(el => newData[el.key])) { - // 尝试去value中匹配 + // 尝试去value中匹配 (input-form; preset-quesitons) param.value.forEach(value => { if (!newData[value.key]) return - result[value.label] = newData[value.key]; + result[value.label || value.key] = newData[value.key].value; hasKeys.push(value.key) }) } else if (newData[param.key] !== undefined) { - result[param.label || param.key] = newData[param.key]; + result[param.label || param.key] = newData[param.key].value; hasKeys.push(param.key) } else if (param.key === 'tool_list') { // tool param.value.some(p => { if (newData[p.tool_key] !== undefined) { - result[p.label] = newData[p.tool_key]; + result[p.label] = newData[p.tool_key].value; hasKeys.push(p.tool_key) return true } @@ -120,7 +120,7 @@ export default function RunLog({ node, children }) { for (let key in newData) { if (!hasKeys.includes(key)) { - result[key] = newData[key]; + result[key] = newData[key].value; } } setData(result) diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/RunTest.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/RunTest.tsx index 026edee9c..4e4aa44c6 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/RunTest.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/RunTest.tsx @@ -122,7 +122,7 @@ export const RunTest = forwardRef((props, ref) => { if (input.required && !input.value) { message({ variant: "warning", - description: `${input.label} 不可为空` + description: `${input.label} ${t('required')}` }) return true } diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/DragOptions.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/DragOptions.tsx index 4fd0b7bc0..f413edf28 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/DragOptions.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/DragOptions.tsx @@ -1,10 +1,11 @@ import { Button } from '@/components/bs-ui/button'; import { Input } from '@/components/bs-ui/input'; import { generateUUID } from '@/components/bs-ui/utils'; +import { Handle, Position } from '@xyflow/react'; +import i18next from "i18next"; import { Edit, GripVertical, Trash2 } from 'lucide-react'; // 图标 -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useState } from 'react'; import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'; -import { Handle, Position } from '@xyflow/react'; import { useTranslation } from 'react-i18next'; interface Iprops { @@ -21,9 +22,9 @@ interface Iprops { // TODO 移动到业务组件 const itemNames = { - 'select': "下拉选项", - 'file': "文件", - 'text': "文本" + 'select': i18next.t('dropdown', { ns: 'flow' }), + 'file': i18next.t('file', { ns: 'flow' }), + 'text': i18next.t('dropdown', { ns: 'flow' }) } export default function DragOptions({ edges = false, scroll = false, options, onEditClick, onChange }: Iprops) { diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/InputFormItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/InputFormItem.tsx index 24cb6f0ef..48b217b9b 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/InputFormItem.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/InputFormItem.tsx @@ -204,7 +204,7 @@ function Form({ initialData, onSubmit, onCancel, existingOptions }) { )} */}
- + setFormData({ ...formData, isRequired: checked })} diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeQaSelectItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeQaSelectItem.tsx index 6fb0ca341..7e4741113 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeQaSelectItem.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeQaSelectItem.tsx @@ -47,13 +47,13 @@ export default function KnowledgeQaSelectItem({ data, onChange, onValidate }) { data.required && onValidate(() => { if (!data.value.length) { setError(true) - return data.label + '不可为空' + return data.label + ' ' + t('required') } setError(false) return false }) - - return () => onValidate(() => {}) + + return () => onValidate(() => { }) }, [data.value]) return
diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeSelectItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeSelectItem.tsx index 3e6f01075..ec48e08a1 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeSelectItem.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/KnowledgeSelectItem.tsx @@ -8,15 +8,25 @@ import { useTranslation } from "react-i18next"; import useFlowStore from "../../flowStore"; import { isVarInFlow } from "@/util/flowUtils"; + const TabsHead = memo(({ tab, onChange }) => { + const { t } = useTranslation('flow'); + + return ( + + + + {t('documentKnowledgeBase')} + + + {t('temporarySessionFiles')} + + + + + ); +}); - return - - 文档知识库 - 临时会话文件 - - -}) const enum KnowledgeType { Knowledge = 'knowledge', @@ -112,7 +122,7 @@ export default function KnowledgeSelectItem({ data, nodeId, onChange, onVarEvent data.required && onValidate(() => { if (!data.value.value.length) { setError(true) - return data.label + '不可为空' + return data.label + ' ' + t('required') } setError(false) return false diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ModelItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ModelItem.tsx index 11a4142da..5fa14b842 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ModelItem.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ModelItem.tsx @@ -2,9 +2,11 @@ import { Label } from "@/components/bs-ui/label"; import Cascader from "@/components/bs-ui/select/cascader"; import { getAssistantModelList, getLlmDefaultModel, getModelListApi } from "@/controllers/API/finetune"; import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; export default function ModelItem({ agent = false, data, onChange, onValidate }) { const [options, setOptions] = useState([]) + const { t } = useTranslation() useEffect(() => { (agent ? getAssistantModelList() : getModelListApi()).then(res => { @@ -61,7 +63,7 @@ export default function ModelItem({ agent = false, data, onChange, onValidate }) data.required && onValidate(() => { if (!data.value) { setError(true) - return data.label + '不可为空' + return data.label + ' ' + t('required') } setError(false) return false diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/OutputItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/OutputItem.tsx index 20e36a41b..f19f38343 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/OutputItem.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/OutputItem.tsx @@ -69,6 +69,16 @@ const OutputItem = ({ nodeId, node, data, onChange, onValidate }) => { } }; + const handleChangeType = (val) => { + setInteractionType(val); + if (interactionType === "choose" || val === "choose") { + const addNodeEvent = new CustomEvent("outputDelEdge", { + detail: { nodeId } + }); + window.dispatchEvent(addNodeEvent); + } + } + const [error, setError] = useState(false); useEffect(() => { data.required && @@ -91,7 +101,7 @@ const OutputItem = ({ nodeId, node, data, onChange, onValidate }) => { { - setInteractionType(val); + handleChangeType(val) onChange({ type: val, value: "" }); setError(false); }} diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ReportItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ReportItem.tsx index 1a2d97299..374df200f 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ReportItem.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/ReportItem.tsx @@ -1,64 +1,71 @@ -import { Button } from "@/components/bs-ui/button"; -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/bs-ui/dialog"; -import { Input } from "@/components/bs-ui/input"; -import { Label } from "@/components/bs-ui/label"; -import { useEffect, useState } from "react"; -import ReportWordEdit from "./ReportWordEdit"; + +import { Button } from '@/components/bs-ui/button'; +import { Dialog, DialogContent, DialogTrigger } from '@/components/bs-ui/dialog'; +import { Input } from '@/components/bs-ui/input'; +import { Label } from '@/components/bs-ui/label'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import ReportWordEdit from './ReportWordEdit'; export default function ReportItem({ nodeId, data, onChange, onValidate }) { + const { t } = useTranslation('flow'); const [value, setValue] = useState({ name: data.value.file_name || '', key: data.value.version_key || '' - }) + }); const handleChange = (key) => { - setValue({ ...value, key }) + setValue({ ...value, key }); onChange({ file_name: value.name, version_key: key - }) - } + }); + }; - const [error, setError] = useState(false) + const [error, setError] = useState(false); useEffect(() => { data.required && onValidate(() => { if (!data.value.file_name) { - setError(true) - return data.label + '不可为空' + setError(true); + return t('cannotBeEmpty', { label: data.label }); } - setError(false) - return false - }) + setError(false); + return false; + }); + + return () => onValidate(() => { }); + }, [data.value]); - return () => onValidate(() => { }) - }, [data.value]) - return
- - { - setValue({ ...value, name: e.target.value }); - onChange({ - file_name: e.target.value, - version_key: value.key - }) - }} - > + return ( +
+ + { + setValue({ ...value, name: e.target.value }); + onChange({ + file_name: e.target.value, + version_key: value.key + }); + }} + > - - - - - - - - -
-}; \ No newline at end of file + + + + + + + + +
+ ); +} \ No newline at end of file diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarSelectItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarSelectItem.tsx index c48892e29..4b329e3e1 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarSelectItem.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarSelectItem.tsx @@ -6,6 +6,7 @@ import { ChevronDown, X } from "lucide-react"; import React, { useEffect, useState } from "react"; import useFlowStore from "../../flowStore"; import SelectVar from "./SelectVar"; +import { useTranslation } from "react-i18next"; const valueToOutput = (newValues, varZh) => { return newValues.map(el => { @@ -39,11 +40,12 @@ export default function VarSelectItem({ nodeId, data, onChange, onOutPutChange, } }; + const { t } = useTranslation() useEffect(() => { data.required && onValidate(() => { if (!data.value.length) { setError(true) - return data.label + '不可为空' + return data.label + ' ' + t('required') } setError(false) return false @@ -101,6 +103,7 @@ export default function VarSelectItem({ nodeId, data, onChange, onOutPutChange, // 单选 export function VarSelectSingleItem({ nodeId, data, onChange, onValidate, onVarEvent }) { const [value, setValue] = React.useState(data.value) + const { t } = useTranslation() const handleChange = (item, v) => { // [nodeId.xxx] @@ -122,7 +125,7 @@ export function VarSelectSingleItem({ nodeId, data, onChange, onValidate, onVarE data.required && onValidate(() => { if (!data.value) { setError(true) - return data.label + '不可为空' + return data.label + t('required') } setError(false) return false diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarTextareaItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarTextareaItem.tsx index 32d7648b6..762e6f713 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarTextareaItem.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarTextareaItem.tsx @@ -1,15 +1,17 @@ import { Label } from "@/components/bs-ui/label"; import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import VarInput from "./VarInput"; export default function VarTextareaItem({ nodeId, data, onChange, onValidate, onVarEvent }) { const [error, setError] = useState(false) + const { t } = useTranslation() useEffect(() => { data.required && onValidate(() => { if (!data.value.trim()) { setError(true) - return data.label + '不可为空' + return data.label + ' ' + t('required') } setError(false) return false diff --git a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarTextareaUploadItem.tsx b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarTextareaUploadItem.tsx index 128efb73c..d498ba1f6 100644 --- a/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarTextareaUploadItem.tsx +++ b/src/frontend/src/pages/BuildPage/flow/FlowNode/component/VarTextareaUploadItem.tsx @@ -3,6 +3,7 @@ import { Label } from "@/components/bs-ui/label"; import { uploadFileWithProgress } from "@/modals/UploadModal/upload"; import { File, X } from "lucide-react"; import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { useParams } from "react-router-dom"; import VarInput from "./VarInput"; @@ -18,11 +19,12 @@ export default function VarTextareaUploadItem({ nodeId, data, onChange, onValida const { files, handleFileUpload, handleFileRemove } = useFileUpload(data.value?.files || [], handleFilesChange); const [error, setError] = useState(false) + const { t } = useTranslation() useEffect(() => { data.required && onValidate(() => { if (!data.value?.msg && data.value?.files.length === 0) { setError(true) - return data.label + '不可为空' + return data.label + ' ' + t('required') } setError(false) return false diff --git a/src/frontend/src/pages/BuildPage/flow/Panne.tsx b/src/frontend/src/pages/BuildPage/flow/Panne.tsx index 10f5b2717..d000da646 100644 --- a/src/frontend/src/pages/BuildPage/flow/Panne.tsx +++ b/src/frontend/src/pages/BuildPage/flow/Panne.tsx @@ -1,7 +1,7 @@ import ApiMainPage from "@/components/bs-comp/apiComponent"; import { generateUUID } from "@/components/bs-ui/utils"; import { WorkFlow, WorkflowNode } from "@/types/flow"; -import { autoNodeName, initNode, useCopyPasteNode } from "@/util/flowUtils"; +import { autoNodeName, filterUselessFlow, initNode, useCopyPasteNode } from "@/util/flowUtils"; import { useUndoRedo } from "@/util/hook"; import { Background, BackgroundVariant, Connection, Controls, ReactFlow, addEdge, applyEdgeChanges, applyNodeChanges, useReactFlow } from '@xyflow/react'; import '@xyflow/react/dist/base.css'; @@ -198,7 +198,14 @@ const useFlow = (_reactFlowInstance, data, takeSnapshot) => { const onConnect = useCallback( (params: Connection) => { takeSnapshot() + let _nodes = [] + setNodes((x) => { + _nodes = x + return cloneDeep(x) + }); setEdges((eds) => { + // 校验 + const _eds = filterUselessFlow(_nodes, eds) return addEdge( { ...params, @@ -207,10 +214,9 @@ const useFlow = (_reactFlowInstance, data, takeSnapshot) => { // className: 'stroke-foreground stroke-connection', animated: true }, - eds + _eds ) }); - setNodes((x) => cloneDeep(x)); }, [setEdges, setNodes, takeSnapshot] ); @@ -315,8 +321,10 @@ const useFlow = (_reactFlowInstance, data, takeSnapshot) => { // let edges = _reactFlowInstance.getEdges(); const newNodes = nodeIds.map(nodeId => { const node = nodes.find(n => n.id === nodeId); - const newNodeId = `${node.type}_${generateUUID(5)}` + const newNodeId = `${node.data.type}_${generateUUID(5)}` // node.id = nodeId + // id替换 + const data = JSON.parse(JSON.stringify(node.data).replaceAll(nodeId, newNodeId)) return { id: newNodeId, type: "flowNode", @@ -325,7 +333,7 @@ const useFlow = (_reactFlowInstance, data, takeSnapshot) => { y: node.position.y + 100, }, data: { - ...cloneDeep(node.data), + ...data, id: newNodeId, }, selected: false @@ -390,11 +398,18 @@ const useFlow = (_reactFlowInstance, data, takeSnapshot) => { ]); } + // 删除输出节点连线 + const handleDelOutputEdge = (event) => { + const { nodeId } = event.detail; + setEdges((eds) => eds.filter((ns) => ns.source !== nodeId)); + } + // 监听自定义事件 window.addEventListener('nodeUpdate', handleNodeUpdate); window.addEventListener('nodeDelete', handleNodeDelete); window.addEventListener('nodeCopy', handleCopy); window.addEventListener('addNodeByHandle', handleAddNode); + window.addEventListener('outputDelEdge', handleDelOutputEdge); // 在组件卸载时移除事件监听 return () => { @@ -402,6 +417,7 @@ const useFlow = (_reactFlowInstance, data, takeSnapshot) => { window.addEventListener('nodeDelete', handleNodeDelete); window.removeEventListener('nodeCopy', handleCopy); window.removeEventListener('addNodeByHandle', handleAddNode); + window.addEventListener('outputDelEdge', handleDelOutputEdge); }; }, [_reactFlowInstance]); @@ -426,9 +442,10 @@ const useKeyBoard = (_reactFlowInstance, reactFlowWrapper) => { if (newSelectNode.nodes.some(node => node.data.type === 'start')) return let bounds = reactFlowWrapper.current.getBoundingClientRect(); setNodes((nds) => { + // TODO 合并到复制节点方法 const newNodes = newSelectNode.nodes.map(node => { - const newNode = cloneDeep(node) - const nodeId = `${newNode.data.type}_${generateUUID(5)}` + const nodeId = `${node.data.type}_${generateUUID(5)}` + const newNode = JSON.parse(JSON.stringify(node).replaceAll(node.id, nodeId)) newNode.id = nodeId newNode.data.id = nodeId const newName = autoNodeName(nds, newNode.data.name) diff --git a/src/frontend/src/pages/BuildPage/skills/CreateTemp.tsx b/src/frontend/src/pages/BuildPage/skills/CreateTemp.tsx index 73622285d..e1a4498a8 100644 --- a/src/frontend/src/pages/BuildPage/skills/CreateTemp.tsx +++ b/src/frontend/src/pages/BuildPage/skills/CreateTemp.tsx @@ -1,82 +1,102 @@ -import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/bs-ui/dialog"; -import { useToast } from "@/components/bs-ui/toast/use-toast"; -import { AppType } from "@/types/app"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { Button } from "../../../components/bs-ui/button"; -import { Input, Textarea } from "../../../components/bs-ui/input"; -import { createTempApi } from "../../../controllers/API"; -import { captureAndAlertRequestErrorHoc } from "../../../controllers/request"; -import { FlowType } from "../../../types/flow"; -export default function CreateTemp({ flow, open, type, setOpen, onCreated }: { flow: FlowType, type: AppType, open: boolean, setOpen: any, onCreated?: any }) { - const { t } = useTranslation() +import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/bs-ui/dialog'; +import { useToast } from '@/components/bs-ui/toast/use-toast'; +import { AppType } from '@/types/app'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button } from '../../../components/bs-ui/button'; +import { Input, Textarea } from '../../../components/bs-ui/input'; +import { createTempApi } from '../../../controllers/API'; +import { captureAndAlertRequestErrorHoc } from '../../../controllers/request'; +import { FlowType } from '../../../types/flow'; + +export default function CreateTemp({ flow, open, type, setOpen, onCreated }) { + const { t } = useTranslation(); const [data, setData] = useState({ name: '', description: '' - }) + }); useEffect(() => { open && setData({ name: flow.name, description: flow.description || '' - }) - }, [open]) + }); + }, [open]); - const { message } = useToast() + const { message } = useToast(); const handleSubmit = () => { const nameMap = { - [AppType.FLOW]: '工作流', - [AppType.SKILL]: '技能名称', - [AppType.ASSISTANT]: '助手', - } - const labelName = nameMap[type] - const errorlist = [] + [AppType.FLOW]: t('build.workFlow'), + [AppType.SKILL]: t('build.skillName'), + [AppType.ASSISTANT]: t('build.assistant') + }; + const labelName = nameMap[type]; + const errorlist = []; - const { name, description } = data - if (!name) errorlist.push(`请填写${labelName}名称`) - if (name.length > 30) errorlist.push(`${labelName}名称过长,不要超过50字`) - if (!description && type === AppType.ASSISTANT) errorlist.push(`加些描述能够快速让别人理解您创造的${labelName}`) - if (description.length > 200) errorlist.push(`${labelName}描述不可超过 200 字`) + const { name, description } = data; + if (!name) errorlist.push(t('build.pleaseFillIn', { labelName })); + if (name.length > 30) errorlist.push(t('build.nameTooLong', { labelName })); + if (!description && type === AppType.ASSISTANT) errorlist.push(t('build.addDescription', { labelName })); + if (description.length > 200) errorlist.push(t('build.descriptionTooLong', { labelName })); if (errorlist.length) message({ variant: 'error', description: errorlist }); - captureAndAlertRequestErrorHoc(createTempApi({ ...data, flow_id: flow.id }, type).then(res => { - setOpen(false) - message({ - variant: 'success', - description: '模板创建成功' - }) - onCreated?.() - })) - } + captureAndAlertRequestErrorHoc( + createTempApi({ ...data, flow_id: flow.id }, type) + .then((res) => { + setOpen(false); + message({ + variant: 'success', + description: t('build.templateCreatedSuccessfully') + }); + onCreated?.(); + }) + ); + }; - return - - - {t('skills.createTemplate')} - -
-
- - setData({ ...data, name: e.target.value })} /> - {/* {errors.name &&

{errors.name}

} */} -
-
- -