From fef1a1702bae5908a39b46ffe0ce6e4888101537 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Tue, 7 May 2024 18:41:34 +0800 Subject: [PATCH] 4.8 test fix (#1385) * fix: tool name cannot startwith number * fix: chatbox update * fix: chatbox * perf: drag ui * perf: drag component * drag component --- .../content/docs/development/upgrading/48.md | 20 ++-- packages/global/common/string/tools.ts | 12 +- .../global/core/workflow/template/input.ts | 3 +- .../components/common/DndDrag/DragIcon.tsx | 14 +++ .../web/components/common/DndDrag/index.tsx | 61 ++++++++++ packages/web/package.json | 6 +- pnpm-lock.yaml | 12 +- projects/app/i18n/zh/common.json | 5 +- projects/app/package.json | 2 - .../ChatBox/components/VariableInput.tsx | 22 +++- projects/app/src/components/ChatBox/index.tsx | 35 ++++-- .../Flow/nodes/NodeIfElse/ListItem.tsx | 12 +- .../workflow/Flow/nodes/NodeIfElse/index.tsx | 110 +++++++----------- .../app/src/pages/api/v1/chat/completions.ts | 1 - .../app/detail/components/FlowEdit/index.tsx | 2 +- projects/app/src/pages/chat/share.tsx | 4 +- 16 files changed, 203 insertions(+), 118 deletions(-) create mode 100644 packages/web/components/common/DndDrag/DragIcon.tsx create mode 100644 packages/web/components/common/DndDrag/index.tsx diff --git a/docSite/content/docs/development/upgrading/48.md b/docSite/content/docs/development/upgrading/48.md index 1d415ed279e..ad52ce4d2ef 100644 --- a/docSite/content/docs/development/upgrading/48.md +++ b/docSite/content/docs/development/upgrading/48.md @@ -1,5 +1,5 @@ --- -title: 'V4.8(进行中)' +title: 'V4.8(开发中)' description: 'FastGPT V4.8 更新说明' icon: 'upgrade' draft: false @@ -18,10 +18,14 @@ FastGPT workflow V2上线,支持更加简洁的工作流模式。 ## V4.8 更新说明 1. 重构 - 工作流 -2. 新增 - 工作流 Debug 模式,可以调试单个节点或者逐步调试工作流。 -3. 新增 - 定时执行应用。可轻松实现定时任务。 -4. 新增 - 插件自定义输入优化,可以渲染输入组件。 -6. 优化 - 工作流连线,可以四向连接,方便构建循环工作流。 -7. 优化 - 工作流上下文传递,性能🚀。 -8. 优化 - 简易模式,更新配置后自动更新调试框内容,无需保存。 -9. 优化 - worker进程管理,并将计算 Token 任务分配给 worker 进程。 \ No newline at end of file +2. 新增 - 判断器。支持 if elseIf else 判断。 +3. 新增 - 变量更新节点。支持更新运行中工作流输出变量,或更新全局变量。 +4. 新增 - 工作流 Debug 模式,可以调试单个节点或者逐步调试工作流。 +5. 新增 - 定时执行应用。可轻松实现定时任务。 +6. 新增 - 插件自定义输入优化,可以渲染输入组件。 +7. 优化 - 工作流连线,可以四向连接,方便构建循环工作流。 +8. 优化 - 工作流上下文传递,性能🚀。 +9. 优化 - 简易模式,更新配置后自动更新调试框内容,无需保存。 +10. 优化 - worker进程管理,并将计算 Token 任务分配给 worker 进程。 +11. 修复 - 工具调用时候,name不能是数字开头(随机数有概率数字开头) +12. 修复 - 分享链接, query 全局变量会被缓存。 \ No newline at end of file diff --git a/packages/global/common/string/tools.ts b/packages/global/common/string/tools.ts index 8e3c8da7cc7..26147f5d347 100644 --- a/packages/global/common/string/tools.ts +++ b/packages/global/common/string/tools.ts @@ -50,8 +50,18 @@ export const replaceSensitiveText = (text: string) => { return text; }; +/* Make sure the first letter is definitely lowercase */ export const getNanoid = (size = 12) => { - return customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', size)(); + const firstChar = customAlphabet('abcdefghijklmnopqrstuvwxyz', 1)(); + + if (size === 1) return firstChar; + + const randomsStr = customAlphabet( + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', + size - 1 + )(); + + return `${firstChar}${randomsStr}`; }; export const replaceRegChars = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); diff --git a/packages/global/core/workflow/template/input.ts b/packages/global/core/workflow/template/input.ts index a588910a9a3..695fec1e22d 100644 --- a/packages/global/core/workflow/template/input.ts +++ b/packages/global/core/workflow/template/input.ts @@ -9,9 +9,10 @@ export const Input_Template_History: FlowNodeInputItemType = { renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference], valueType: WorkflowIOValueTypeEnum.chatHistory, label: 'core.module.input.label.chat history', + description: '最多携带多少轮对话记录', required: true, min: 0, - max: 30, + max: 50, value: 6 }; diff --git a/packages/web/components/common/DndDrag/DragIcon.tsx b/packages/web/components/common/DndDrag/DragIcon.tsx new file mode 100644 index 00000000000..493a409d2d3 --- /dev/null +++ b/packages/web/components/common/DndDrag/DragIcon.tsx @@ -0,0 +1,14 @@ +import { DragHandleIcon } from '@chakra-ui/icons'; +import { Box } from '@chakra-ui/react'; +import React from 'react'; +import { DraggableProvided } from 'react-beautiful-dnd'; + +const DragIcon = ({ provided }: { provided: DraggableProvided }) => { + return ( + + + + ); +}; + +export default DragIcon; diff --git a/packages/web/components/common/DndDrag/index.tsx b/packages/web/components/common/DndDrag/index.tsx new file mode 100644 index 00000000000..a78cedc7951 --- /dev/null +++ b/packages/web/components/common/DndDrag/index.tsx @@ -0,0 +1,61 @@ +import { Box } from '@chakra-ui/react'; +import React, { useState } from 'react'; +import { + DragDropContext, + DroppableProps, + Droppable, + DraggableChildrenFn, + DragStart, + DropResult +} from 'react-beautiful-dnd'; + +type Props = { + onDragEndCb: (result: T[]) => void; + renderClone?: DraggableChildrenFn; + children: DroppableProps['children']; + dataList: T[]; +}; + +function DndDrag({ children, renderClone, onDragEndCb, dataList }: Props) { + const [draggingItemHeight, setDraggingItemHeight] = useState(0); + + const onDragStart = (start: DragStart) => { + const draggingNode = document.querySelector(`[data-rbd-draggable-id="${start.draggableId}"]`); + setDraggingItemHeight(draggingNode?.getBoundingClientRect().height || 0); + }; + + const onDragEnd = (result: DropResult) => { + if (!result.destination) { + return; + } + setDraggingItemHeight(0); + + const startIndex = result.source.index; + const endIndex = result.destination.index; + + const list = Array.from(dataList); + const [removed] = list.splice(startIndex, 1); + list.splice(endIndex, 0, removed); + + onDragEndCb(list); + }; + + return ( + + + {(provided, snapshot) => { + return ( + + {children(provided, snapshot)} + {snapshot.isDraggingOver && } + + ); + }} + + + ); +} + +export default DndDrag; + +export * from 'react-beautiful-dnd'; diff --git a/packages/web/package.json b/packages/web/package.json index dad5429d262..934f1c6651e 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -31,12 +31,14 @@ "react": "18.2.0", "react-day-picker": "^8.7.1", "react-dom": "18.2.0", - "react-i18next": "13.5.0" + "react-i18next": "13.5.0", + "react-beautiful-dnd": "^13.1.1" }, "devDependencies": { "@types/lodash": "^4.14.191", "@types/papaparse": "^5.3.7", "@types/react": "18.2.0", - "@types/react-dom": "18.2.0" + "@types/react-dom": "18.2.0", + "@types/react-beautiful-dnd": "^13.1.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 73c666de60c..8b174057742 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -292,6 +292,9 @@ importers: react: specifier: 18.2.0 version: 18.2.0 + react-beautiful-dnd: + specifier: ^13.1.1 + version: 13.1.1(react-dom@18.2.0)(react@18.2.0) react-day-picker: specifier: ^8.7.1 version: 8.7.1(date-fns@2.30.0)(react@18.2.0) @@ -311,6 +314,9 @@ importers: '@types/react': specifier: 18.2.0 version: 18.2.0 + '@types/react-beautiful-dnd': + specifier: ^13.1.8 + version: 13.1.8 '@types/react-dom': specifier: 18.2.0 version: 18.2.0 @@ -431,9 +437,6 @@ importers: react: specifier: 18.2.0 version: 18.2.0 - react-beautiful-dnd: - specifier: ^13.1.1 - version: 13.1.1(react-dom@18.2.0)(react@18.2.0) react-day-picker: specifier: ^8.7.1 version: 8.7.1(date-fns@2.30.0)(react@18.2.0) @@ -504,9 +507,6 @@ importers: '@types/react': specifier: 18.2.0 version: 18.2.0 - '@types/react-beautiful-dnd': - specifier: ^13.1.8 - version: 13.1.8 '@types/react-dom': specifier: 18.2.0 version: 18.2.0 diff --git a/projects/app/i18n/zh/common.json b/projects/app/i18n/zh/common.json index 7069ba9f202..0b9f171a599 100644 --- a/projects/app/i18n/zh/common.json +++ b/projects/app/i18n/zh/common.json @@ -644,8 +644,7 @@ "success": "开始同步" } }, - "training": { - } + "training": {} }, "data": { "Auxiliary Data": "辅助数据", @@ -920,7 +919,7 @@ "AppId": "应用的ID", "ChatId": "当前对话ID", "Current time": "当前时间", - "Histories": "历史记录,最多取10条", + "Histories": "最近10条聊天记录", "Key already exists": "Key 已经存在", "Key cannot be empty": "参数名不能为空", "Props name": "参数名", diff --git a/projects/app/package.json b/projects/app/package.json index 0667849c0a3..6975d9fa1e4 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -47,7 +47,6 @@ "nextjs-node-loader": "^1.1.5", "nprogress": "^0.2.0", "react": "18.2.0", - "react-beautiful-dnd": "^13.1.1", "react-day-picker": "^8.7.1", "react-dom": "18.2.0", "react-hook-form": "7.43.1", @@ -73,7 +72,6 @@ "@types/lodash": "^4.14.191", "@types/node": "^20.8.5", "@types/react": "18.2.0", - "@types/react-beautiful-dnd": "^13.1.8", "@types/react-dom": "18.2.0", "@types/react-syntax-highlighter": "^15.5.6", "@types/request-ip": "^0.0.37", diff --git a/projects/app/src/components/ChatBox/components/VariableInput.tsx b/projects/app/src/components/ChatBox/components/VariableInput.tsx index a78edbe3e8a..9135a19dc6b 100644 --- a/projects/app/src/components/ChatBox/components/VariableInput.tsx +++ b/projects/app/src/components/ChatBox/components/VariableInput.tsx @@ -1,5 +1,5 @@ import { VariableItemType } from '@fastgpt/global/core/app/type.d'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { UseFormReturn } from 'react-hook-form'; import { useTranslation } from 'next-i18next'; import { Box, Button, Card, Input, Textarea } from '@chakra-ui/react'; @@ -24,10 +24,23 @@ const VariableInput = ({ chatForm: UseFormReturn; }) => { const { t } = useTranslation(); - const [refresh, setRefresh] = useState(false); - const { register, setValue, handleSubmit: handleSubmitChat, watch } = chatForm; + const { register, unregister, setValue, handleSubmit: handleSubmitChat, watch } = chatForm; const variables = watch('variables'); + useEffect(() => { + // 重新注册所有字段 + variableModules.forEach((item) => { + register(`variables.${item.key}`, { required: item.required }); + }); + + return () => { + // 组件卸载时注销所有字段 + variableModules.forEach((item) => { + unregister(`variables.${item.key}`); + }); + }; + }, [register, unregister, variableModules]); + return ( {/* avatar */} @@ -92,7 +105,6 @@ const VariableInput = ({ value={variables[item.key]} onchange={(e) => { setValue(`variables.${item.key}`, e); - setRefresh((state) => !state); }} /> )} @@ -116,4 +128,4 @@ const VariableInput = ({ ); }; -export default React.memo(VariableInput); +export default VariableInput; diff --git a/projects/app/src/components/ChatBox/index.tsx b/projects/app/src/components/ChatBox/index.tsx index 807e265849e..66c23d31e54 100644 --- a/projects/app/src/components/ChatBox/index.tsx +++ b/projects/app/src/components/ChatBox/index.tsx @@ -158,12 +158,6 @@ const ChatBox = ( isChatting } = useChatProviderStore(); - /* variable */ - const filterVariableModules = useMemo( - () => variableModules.filter((item) => item.type !== VariableInputEnum.custom), - [variableModules] - ); - // compute variable input is finish. const chatForm = useForm({ defaultValues: { @@ -174,9 +168,15 @@ const ChatBox = ( } }); const { setValue, watch, handleSubmit } = chatForm; - const variables = watch('variables'); const chatStarted = watch('chatStarted'); - const variableIsFinish = useMemo(() => { + + /* variable */ + const variables = watch('variables'); + const filterVariableModules = useMemo( + () => variableModules.filter((item) => item.type !== VariableInputEnum.custom), + [variableModules] + ); + const variableIsFinish = (() => { if (!filterVariableModules || filterVariableModules.length === 0 || chatHistories.length > 0) return true; @@ -188,7 +188,7 @@ const ChatBox = ( } return chatStarted; - }, [filterVariableModules, chatHistories.length, chatStarted, variables]); + })(); // 滚动到底部 const scrollToBottom = (behavior: 'smooth' | 'auto' = 'smooth') => { @@ -360,6 +360,12 @@ const ChatBox = ( [questionGuide, shareId, outLinkUid, teamId, teamToken] ); + /* Abort chat completions, questionGuide */ + const abortRequest = useCallback(() => { + chatController.current?.abort('stop'); + questionGuideController.current?.abort('stop'); + }, []); + /** * user confirm send prompt */ @@ -383,6 +389,8 @@ const ChatBox = ( return; } + abortRequest(); + text = text.trim(); if (!text && files.length === 0) { @@ -472,7 +480,8 @@ const ChatBox = ( generatingMessage: (e) => generatingMessage({ ...e, autoTTSResponse }), variables }); - setValue('variables', newVariables || []); + + newVariables && setValue('variables', newVariables); isNewChatReplace.current = isNewChat; @@ -540,6 +549,7 @@ const ChatBox = ( })(); }, [ + abortRequest, chatHistories, createQuestionGuide, finishSegmentedAudio, @@ -710,7 +720,7 @@ const ChatBox = ( }); }; }, - [appId, chatId, feedbackType, teamId, teamToken] + [appId, chatId, feedbackType, setChatHistories, teamId, teamToken] ); const onADdUserDislike = useCallback( (chat: ChatSiteItemType) => { @@ -747,7 +757,7 @@ const ChatBox = ( return () => setFeedbackId(chat.dataId); } }, - [appId, chatId, feedbackType, outLinkUid, shareId, teamId, teamToken] + [appId, chatId, feedbackType, outLinkUid, setChatHistories, shareId, teamId, teamToken] ); const onReadUserDislike = useCallback( (chat: ChatSiteItemType) => { @@ -868,6 +878,7 @@ const ChatBox = ( setValue('variables', e || defaultVal); }, resetHistory(e) { + abortRequest(); setValue('chatStarted', e.length > 0); setChatHistories(e); }, diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/ListItem.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/ListItem.tsx index 61c172e5828..7a5b19e83e2 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/ListItem.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/ListItem.tsx @@ -1,5 +1,8 @@ import { Box, Button, Flex } from '@chakra-ui/react'; -import { DraggableProvided, DraggableStateSnapshot } from 'react-beautiful-dnd'; +import { + DraggableProvided, + DraggableStateSnapshot +} from '@fastgpt/web/components/common/DndDrag/index'; import Container from '../../components/Container'; import { DragHandleIcon, MinusIcon, SmallAddIcon } from '@chakra-ui/icons'; import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/system/ifElse/type'; @@ -25,6 +28,7 @@ import { getElseIFLabel, getHandleId } from '@fastgpt/global/core/workflow/utils import { SourceHandle } from '../render/Handle'; import { Position, useReactFlow } from 'reactflow'; import { getReferenceDataValueType } from '@/web/core/workflow/utils'; +import DragIcon from '@fastgpt/web/components/common/DndDrag/DragIcon'; const ListItem = ({ provided, @@ -63,11 +67,7 @@ const ListItem = ({ > - {ifElseList.length > 1 && ( - - - - )} + {ifElseList.length > 1 && } {getElseIFLabel(conditionIndex)} diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/index.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/index.tsx index b1042ecdb8a..026245d9862 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/index.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse/index.tsx @@ -9,7 +9,7 @@ import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/syste import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '../../../context'; import Container from '../../components/Container'; -import { DragDropContext, DragStart, Draggable, DropResult, Droppable } from 'react-beautiful-dnd'; +import DndDrag, { Draggable, DropResult } from '@fastgpt/web/components/common/DndDrag/index'; import { SourceHandle } from '../render/Handle'; import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import ListItem from './ListItem'; @@ -20,8 +20,6 @@ const NodeIfElse = ({ data, selected }: NodeProps) => { const { nodeId, inputs = [] } = data; const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); - const [draggingItemHeight, setDraggingItemHeight] = useState(0); - const ifElseList = useMemo( () => (inputs.find((input) => input.key === NodeInputKeyEnum.ifElseList) @@ -47,73 +45,49 @@ const NodeIfElse = ({ data, selected }: NodeProps) => { [inputs, nodeId, onChangeNode] ); - const reorder = (list: IfElseListItemType[], startIndex: number, endIndex: number) => { - const result = Array.from(list); - const [removed] = result.splice(startIndex, 1); - result.splice(endIndex, 0, removed); - - return result; - }; - - const onDragStart = (start: DragStart) => { - const draggingNode = document.querySelector(`[data-rbd-draggable-id="${start.draggableId}"]`); - setDraggingItemHeight(draggingNode?.getBoundingClientRect().height || 0); - }; - - const onDragEnd = (result: DropResult) => { - if (!result.destination) { - return; - } - const newList = reorder(ifElseList, result.source.index, result.destination.index); - - onUpdateIfElseList(newList); - setDraggingItemHeight(0); - }; - return ( - - - ( - - )} - > - {(provided, snapshot) => ( - - {ifElseList.map((conditionItem, conditionIndex) => ( - - {(provided, snapshot) => ( - - )} - - ))} - {snapshot.isDraggingOver && } - - )} - - + + + onDragEndCb={(list) => onUpdateIfElseList(list)} + dataList={ifElseList} + renderClone={(provided, snapshot, rubric) => ( + + )} + > + {(provided) => ( + + {ifElseList.map((conditionItem, conditionIndex) => ( + + {(provided, snapshot) => ( + + )} + + ))} + + )} + + diff --git a/projects/app/src/pages/api/v1/chat/completions.ts b/projects/app/src/pages/api/v1/chat/completions.ts index 7f3b4625f90..f1bd84207ba 100644 --- a/projects/app/src/pages/api/v1/chat/completions.ts +++ b/projects/app/src/pages/api/v1/chat/completions.ts @@ -45,7 +45,6 @@ import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runti import { dispatchWorkFlowV1 } from '@fastgpt/service/core/workflow/dispatchV1'; import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatchV1/utils'; import { NextAPI } from '@/service/middle/entry'; -import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema'; import { getAppLatestVersion } from '@fastgpt/service/core/app/controller'; type FastGptWebChatProps = { diff --git a/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx b/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx index 0f9738e8989..71bd0865425 100644 --- a/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx +++ b/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx @@ -28,7 +28,7 @@ const Render = ({ app, onClose }: Props) => { useEffect(() => { if (!isV2Workflow) return; initData(JSON.parse(workflowStringData)); - }, [isV2Workflow, initData, workflowStringData]); + }, [isV2Workflow, initData, app._id]); useEffect(() => { if (!isV2Workflow) { diff --git a/projects/app/src/pages/chat/share.tsx b/projects/app/src/pages/chat/share.tsx index 439cb725c81..32933f45041 100644 --- a/projects/app/src/pages/chat/share.tsx +++ b/projects/app/src/pages/chat/share.tsx @@ -99,8 +99,8 @@ const OutLink = ({ data: { messages: prompts, variables: { - ...customVariables, - ...variables + ...variables, + ...customVariables }, shareId, chatId: completionChatId,