diff --git a/docSite/content/docs/development/upgrading/48.md b/docSite/content/docs/development/upgrading/48.md index 146e2bd791d9..1d415ed279ef 100644 --- a/docSite/content/docs/development/upgrading/48.md +++ b/docSite/content/docs/development/upgrading/48.md @@ -21,5 +21,7 @@ FastGPT workflow V2上线,支持更加简洁的工作流模式。 2. 新增 - 工作流 Debug 模式,可以调试单个节点或者逐步调试工作流。 3. 新增 - 定时执行应用。可轻松实现定时任务。 4. 新增 - 插件自定义输入优化,可以渲染输入组件。 -5. 优化 - 工作流连线,可以四向连接,方便构建循环工作流。 -6. 优化 - worker进程管理,并将计算 Token 任务分配给 worker 进程。 \ No newline at end of file +6. 优化 - 工作流连线,可以四向连接,方便构建循环工作流。 +7. 优化 - 工作流上下文传递,性能🚀。 +8. 优化 - 简易模式,更新配置后自动更新调试框内容,无需保存。 +9. 优化 - worker进程管理,并将计算 Token 任务分配给 worker 进程。 \ No newline at end of file diff --git a/packages/global/core/app/utils.ts b/packages/global/core/app/utils.ts index 703d05e96f86..7ba2fa684912 100644 --- a/packages/global/core/app/utils.ts +++ b/packages/global/core/app/utils.ts @@ -125,6 +125,7 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => { defaultAppForm.selectedTools.push({ id: node.pluginId, + pluginId: node.pluginId, name: node.name, avatar: node.avatar, intro: node.intro || '', diff --git a/packages/global/support/outLink/constant.ts b/packages/global/support/outLink/constant.ts index 2fb684037098..7562997b9c5b 100644 --- a/packages/global/support/outLink/constant.ts +++ b/packages/global/support/outLink/constant.ts @@ -1,5 +1,6 @@ -export enum OutLinkTypeEnum { +export enum PublishChannelEnum { share = 'share', iframe = 'iframe', - apikey = 'apikey' + apikey = 'apikey', + feishu = 'feishu' } diff --git a/packages/global/support/outLink/type.d.ts b/packages/global/support/outLink/type.d.ts index 619caa8e45a9..9e37f44411e3 100644 --- a/packages/global/support/outLink/type.d.ts +++ b/packages/global/support/outLink/type.d.ts @@ -1,31 +1,79 @@ import { AppSchema } from 'core/app/type'; -import { OutLinkTypeEnum } from './constant'; +import { PublishChannelEnum } from './constant'; -export type OutLinkSchema = { +// Feishu Config interface +export interface FeishuType { + appId: string; + appSecret: string; + // Encrypt config + // refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/configure-encrypt-key + encryptKey?: string; // no secret if null + // Token Verification + // refer to: https://open.feishu.cn/document/server-docs/event-subscription-guide/event-subscription-configure-/encrypt-key-encryption-configuration-case + verificationToken: string; +} + +// TODO: Unused +export interface WecomType { + ReplyLimit: Boolean; + defaultResponse: string; + immediateResponse: boolean; + WXWORK_TOKEN: string; + WXWORK_AESKEY: string; + WXWORK_SECRET: string; + WXWORD_ID: string; +} + +export type OutLinkSchema = { _id: string; shareId: string; teamId: string; tmbId: string; appId: string; + // teamId: Schema.Types.ObjectId; + // tmbId: Schema.Types.ObjectId; + // appId: Schema.Types.ObjectId; name: string; usagePoints: number; lastTime: Date; - type: `${OutLinkTypeEnum}`; + type: PublishChannelEnum; + + // whether the response content is detailed responseDetail: boolean; + + // response when request + immediateResponse?: string; + // response when error or other situation + defaultResponse?: string; + limit?: { expiredTime?: Date; + // Questions per minute QPM: number; maxUsagePoints: number; + // Verification message hook url hookUrl?: string; }; + + app?: T; }; + +// to handle MongoDB querying export type OutLinkWithAppType = Omit & { appId: AppSchema; }; -export type OutLinkEditType = { +// Edit the Outlink +export type OutLinkEditType = { _id?: string; name: string; - responseDetail: OutLinkSchema['responseDetail']; - limit: OutLinkSchema['limit']; + responseDetail: OutLinkSchema['responseDetail']; + // response when request + immediateResponse?: string; + // response when error or other situation + defaultResponse?: string; + limit?: OutLinkSchema['limit']; + + // config for specific platform + app?: T; }; diff --git a/packages/service/support/outLink/schema.ts b/packages/service/support/outLink/schema.ts index af0c4b29aa67..63926f73d88b 100644 --- a/packages/service/support/outLink/schema.ts +++ b/packages/service/support/outLink/schema.ts @@ -1,7 +1,6 @@ import { connectionMongo, type Model } from '../../common/mongo'; const { Schema, model, models } = connectionMongo; import { OutLinkSchema as SchemaType } from '@fastgpt/global/support/outLink/type'; -import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant'; import { TeamCollectionName, TeamMemberCollectionName @@ -30,7 +29,7 @@ const OutLinkSchema = new Schema({ }, type: { type: String, - default: OutLinkTypeEnum.share + required: true }, name: { type: String, @@ -62,6 +61,26 @@ const OutLinkSchema = new Schema({ hookUrl: { type: String } + }, + app: { + appId: { + type: String + }, + appSecret: { + type: String + }, + encryptKey: { + type: String + }, + verificationToken: { + type: String + } + }, + immediateResponse: { + type: String + }, + defaultResponse: { + type: String } }); diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 8896b248dfa9..62936a42261c 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -65,6 +65,7 @@ export const iconPaths = { 'core/app/headphones': () => import('./icons/core/app/headphones.svg'), 'core/app/logsLight': () => import('./icons/core/app/logsLight.svg'), 'core/app/markLight': () => import('./icons/core/app/markLight.svg'), + 'core/app/publish/lark': () => import('./icons/core/app/publish/lark.svg'), 'core/app/questionGuide': () => import('./icons/core/app/questionGuide.svg'), 'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'), 'core/app/simpleMode/ai': () => import('./icons/core/app/simpleMode/ai.svg'), diff --git a/packages/web/components/common/Icon/icons/core/app/publish/lark.svg b/packages/web/components/common/Icon/icons/core/app/publish/lark.svg new file mode 100644 index 000000000000..46e6c5cb0ed4 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/publish/lark.svg @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Textarea/PromptEditor/type.d.ts b/packages/web/components/common/Textarea/PromptEditor/type.d.ts index 8c9d8aaa7834..c1f0b82e322c 100644 --- a/packages/web/components/common/Textarea/PromptEditor/type.d.ts +++ b/packages/web/components/common/Textarea/PromptEditor/type.d.ts @@ -1,5 +1,8 @@ +import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; + export type EditorVariablePickerType = { key: string; label: string; icon?: string; + valueType?: WorkflowIOValueTypeEnum; }; diff --git a/packages/web/package.json b/packages/web/package.json index a3d654b12e12..b244f462b4c8 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -8,8 +8,8 @@ "@chakra-ui/react": "2.8.1", "@chakra-ui/styled-system": "2.9.1", "@chakra-ui/system": "2.6.1", - "@emotion/react": "^11.11.1", - "@emotion/styled": "^11.11.0", + "@emotion/react": "11.11.1", + "@emotion/styled": "11.11.0", "@fastgpt/global": "workspace:*", "@fingerprintjs/fingerprintjs": "^4.3.0", "@lexical/react": "0.12.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ce95b255852..c4017210fd13 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -230,10 +230,10 @@ importers: specifier: 2.6.1 version: 2.6.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) '@emotion/react': - specifier: ^11.11.1 + specifier: 11.11.1 version: 11.11.1(@types/react@18.2.0)(react@18.2.0) '@emotion/styled': - specifier: ^11.11.0 + specifier: 11.11.0 version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.0)(react@18.2.0) '@fastgpt/global': specifier: workspace:* @@ -336,10 +336,10 @@ importers: specifier: 2.6.1 version: 2.6.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) '@emotion/react': - specifier: ^11.11.1 + specifier: 11.11.1 version: 11.11.1(@types/react@18.2.0)(react@18.2.0) '@emotion/styled': - specifier: ^11.11.0 + specifier: 11.11.0 version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.0)(react@18.2.0) '@fastgpt/global': specifier: workspace:* @@ -408,7 +408,7 @@ importers: specifier: ^4.17.21 version: 4.17.21 mermaid: - specifier: 10.2.3 + specifier: ^10.2.3 version: 10.2.3 nanoid: specifier: ^4.0.1 @@ -467,6 +467,9 @@ importers: sass: specifier: ^1.58.3 version: 1.58.3 + use-context-selector: + specifier: ^1.4.4 + version: 1.4.4(react-dom@18.2.0)(react@18.2.0)(scheduler@0.23.0) zustand: specifier: ^4.3.5 version: 4.3.5(immer@9.0.19)(react@18.2.0) @@ -12158,6 +12161,24 @@ packages: tslib: 2.6.2 dev: false + /use-context-selector@1.4.4(react-dom@18.2.0)(react@18.2.0)(scheduler@0.23.0): + resolution: {integrity: sha512-pS790zwGxxe59GoBha3QYOwk8AFGp4DN6DOtH+eoqVmgBBRXVx4IlPDhJmmMiNQAgUaLlP+58aqRC3A4rdaSjg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '*' + react-native: '*' + scheduler: '>=0.19.0' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + scheduler: 0.23.0 + dev: false + /use-sidecar@1.1.2(@types/react@18.2.0)(react@18.2.0): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} diff --git a/projects/app/next.config.js b/projects/app/next.config.js index 6a0b0dcfb6ce..3deb3ac55925 100644 --- a/projects/app/next.config.js +++ b/projects/app/next.config.js @@ -76,7 +76,7 @@ const nextConfig = { return config; }, - transpilePackages: ['@fastgpt/*'], + transpilePackages: ['@fastgpt/*', 'ahooks'], experimental: { // 外部包独立打包 serverComponentsExternalPackages: ['mongoose', 'pg'], diff --git a/projects/app/package.json b/projects/app/package.json index f893f60dccdf..c977f1f29b58 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -16,13 +16,14 @@ "@chakra-ui/react": "2.8.1", "@chakra-ui/styled-system": "2.9.1", "@chakra-ui/system": "2.6.1", - "@emotion/react": "^11.11.1", - "@emotion/styled": "^11.11.0", + "@emotion/react": "11.11.1", + "@emotion/styled": "11.11.0", "@fastgpt/global": "workspace:*", "@fastgpt/plugins": "workspace:*", "@fastgpt/service": "workspace:*", "@fastgpt/web": "workspace:*", "@fortaine/fetch-event-source": "^3.0.6", + "@node-rs/jieba": "1.10.0", "@tanstack/react-query": "^4.24.10", "@types/nprogress": "^0.2.0", "ahooks": "^3.7.11", @@ -39,10 +40,11 @@ "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", - "mermaid": "10.2.3", + "mermaid": "^10.2.3", "nanoid": "^4.0.1", "next": "13.5.2", "next-i18next": "15.2.0", + "nextjs-node-loader": "^1.1.5", "nprogress": "^0.2.0", "react": "18.2.0", "react-day-picker": "^8.7.1", @@ -58,9 +60,8 @@ "remark-math": "^5.1.1", "request-ip": "^3.3.0", "sass": "^1.58.3", - "zustand": "^4.3.5", - "nextjs-node-loader": "^1.1.5", - "@node-rs/jieba": "1.10.0" + "use-context-selector": "^1.4.4", + "zustand": "^4.3.5" }, "devDependencies": { "@svgr/webpack": "^6.5.1", @@ -76,6 +77,7 @@ "@types/request-ip": "^0.0.37", "eslint": "8.34.0", "eslint-config-next": "13.1.6", + "nextjs-node-loader": "^1.1.5", "typescript": "4.9.5" } } diff --git a/projects/app/public/locales/en/common.json b/projects/app/public/locales/en/common.json index 0c1cba7f48d6..d5ec7a5cbb72 100644 --- a/projects/app/public/locales/en/common.json +++ b/projects/app/public/locales/en/common.json @@ -153,6 +153,7 @@ "Submit failed": "Submit failed", "Submit success": "Update Success", "Sync success": "", + "System Output": "System Output", "System version": "System version", "Team": "Team", "Team Tags Set": "Team Tags", @@ -280,6 +281,7 @@ "App intro": "App intro", "App params config": "App Config", "Auto Save time": "Auto-saved: {{time}}", + "Change to simple mode": "Switch easy mode", "Chat Variable": "", "Config schedule plan": "Config schedule config", "Config whisper": "Config whisper", @@ -374,6 +376,11 @@ "Show History": "Show History", "Web Link": "Web Link" }, + "publish": { + "Fei Shu Bot Desc": "Access the Lark robot", + "Fei shu bot": "Lark", + "Fei shu bot publish": "Posted to Lark Robot" + }, "schedule": { "Default prompt": "Default prompt", "Default prompt placeholder": "Default problem when executing the application", @@ -957,6 +964,7 @@ "anyInput": "Any input", "chat history": "chat history", "switch": "Trigger", + "system params": "System params", "textEditor textarea": "Text edit", "user question": "User question" }, @@ -1108,6 +1116,7 @@ "Stop debug": "Stop", "Success": "Running success", "Value type": "Type", + "Variable outputs": "Variables", "chat": { "Quote prompt": "Quote prompt" }, diff --git a/projects/app/public/locales/zh/common.json b/projects/app/public/locales/zh/common.json index ff47933b8055..cd71bc55f2c4 100644 --- a/projects/app/public/locales/zh/common.json +++ b/projects/app/public/locales/zh/common.json @@ -153,6 +153,7 @@ "Submit failed": "提交失败", "Submit success": "提交成功", "Sync success": "同步成功", + "System Output": "系统输出", "System version": "系统版本", "Team": "团队", "Team Tags Set": "标签", @@ -280,6 +281,7 @@ "App intro": "应用介绍", "App params config": "应用配置", "Auto Save time": "自动保存: {{time}}", + "Change to simple mode": "切换简易模式", "Chat Variable": "对话框变量", "Config schedule plan": "配置定时执行", "Config whisper": "配置语音输入", @@ -374,6 +376,11 @@ "Show History": "展示历史对话", "Web Link": "网络链接" }, + "publish": { + "Fei Shu Bot Desc": "接入到飞书机器人中", + "Fei shu bot": "飞书", + "Fei shu bot publish": "发布到飞书机器人" + }, "schedule": { "Default prompt": "默认问题", "Default prompt placeholder": "执行应用时的默认问题", @@ -638,7 +645,8 @@ "success": "开始同步" } }, - "training": {} + "training": { + } }, "data": { "Auxiliary Data": "辅助数据", @@ -958,6 +966,7 @@ "anyInput": "", "chat history": "聊天记录", "switch": "触发器", + "system params": "系统参数", "textEditor textarea": "文本编辑", "user question": "用户问题" }, @@ -1109,6 +1118,7 @@ "Stop debug": "停止调试", "Success": "运行成功", "Value type": "数据类型", + "Variable outputs": "全局变量", "chat": { "Quote prompt": "引用提示词" }, diff --git a/projects/app/src/components/common/HiddenInput/index.tsx b/projects/app/src/components/common/HiddenInput/index.tsx new file mode 100644 index 000000000000..dce31c96a743 --- /dev/null +++ b/projects/app/src/components/common/HiddenInput/index.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Button, Input, InputGroup, InputRightElement } from '@chakra-ui/react'; +import { ViewOffIcon, ViewIcon } from '@chakra-ui/icons'; +function HiddenInput(props: any) { + const [show, setShow] = React.useState(false); + return ( + <> + + + + + + + + ); +} + +export default HiddenInput; diff --git a/projects/app/src/components/core/app/VariableEdit.tsx b/projects/app/src/components/core/app/VariableEdit.tsx index 0abf679c8ba2..aff39f82bb5a 100644 --- a/projects/app/src/components/core/app/VariableEdit.tsx +++ b/projects/app/src/components/core/app/VariableEdit.tsx @@ -13,7 +13,6 @@ import { Switch, Input, FormControl, - Image, Table, Thead, Tbody, @@ -21,7 +20,6 @@ import { Th, Td, TableContainer, - BoxProps, useDisclosure } from '@chakra-ui/react'; import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons'; diff --git a/projects/app/src/components/core/workflow/Flow/ImportSettings.tsx b/projects/app/src/components/core/workflow/Flow/ImportSettings.tsx index 3c40d6051e7e..791e0f995ba4 100644 --- a/projects/app/src/components/core/workflow/Flow/ImportSettings.tsx +++ b/projects/app/src/components/core/workflow/Flow/ImportSettings.tsx @@ -3,7 +3,8 @@ import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { useTranslation } from 'next-i18next'; import { useToast } from '@fastgpt/web/hooks/useToast'; -import { useFlowProviderStore } from './FlowProvider'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../context'; type Props = { onClose: () => void; @@ -12,7 +13,8 @@ type Props = { const ImportSettings = ({ onClose }: Props) => { const { t } = useTranslation(); const { toast } = useToast(); - const { setNodes, setEdges, initData } = useFlowProviderStore(); + + const initData = useContextSelector(WorkflowContext, (v) => v.initData); const [value, setValue] = useState(''); return ( diff --git a/projects/app/src/components/core/workflow/Flow/NodeTemplatesModal.tsx b/projects/app/src/components/core/workflow/Flow/NodeTemplatesModal.tsx index e6074f9fd3a0..5980a7a07870 100644 --- a/projects/app/src/components/core/workflow/Flow/NodeTemplatesModal.tsx +++ b/projects/app/src/components/core/workflow/Flow/NodeTemplatesModal.tsx @@ -1,14 +1,5 @@ import React, { useCallback, useMemo, useState } from 'react'; -import { - Box, - Card, - Flex, - IconButton, - Input, - InputGroup, - InputLeftElement, - css -} from '@chakra-ui/react'; +import { Box, Flex, IconButton, Input, InputGroup, InputLeftElement, css } from '@chakra-ui/react'; import type { FlowNodeTemplateType, nodeTemplateListType @@ -16,8 +7,6 @@ import type { import { useViewport, XYPosition } from 'reactflow'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import Avatar from '@/components/Avatar'; -import { useFlowProviderStore } from './FlowProvider'; -import { customAlphabet } from 'nanoid'; import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils'; import { useTranslation } from 'next-i18next'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; @@ -36,6 +25,9 @@ import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants'; import { useQuery } from '@tanstack/react-query'; import { debounce } from 'lodash'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../context'; +import { useCreation } from 'ahooks'; type ModuleTemplateListProps = { isOpen: boolean; @@ -62,7 +54,9 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { const [currentParent, setCurrentParent] = useState(); const [searchKey, setSearchKey] = useState(''); const { feConfigs } = useSystemStore(); - const { nodes, basicNodeTemplates, hasToolNode } = useFlowProviderStore(); + const basicNodeTemplates = useContextSelector(WorkflowContext, (v) => v.basicNodeTemplates); + const hasToolNode = useContextSelector(WorkflowContext, (v) => v.hasToolNode); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); const { systemNodeTemplates, @@ -72,12 +66,12 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { } = useWorkflowStore(); const [templateType, setTemplateType] = useState(TemplateTypeEnum.basic); - const templatesString = useMemo(() => { + const templates = useCreation(() => { const map = { [TemplateTypeEnum.basic]: basicNodeTemplates.filter((item) => { // unique node filter if (item.unique) { - const nodeExist = nodes.some((node) => node.data.flowNodeType === item.flowNodeType); + const nodeExist = nodeList.some((node) => node.flowNodeType === item.flowNodeType); if (nodeExist) { return false; } @@ -97,12 +91,12 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { searchKey ? item.pluginType !== PluginTypeEnum.folder : true ) }; - return JSON.stringify(map[templateType]); + return map[templateType]; }, [ basicNodeTemplates, feConfigs.lafEnv, hasToolNode, - nodes, + nodeList, searchKey, systemNodeTemplates, teamPluginNodeTemplates, @@ -132,135 +126,120 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { }) ); - const Render = useMemo(() => { - const parseTemplates = JSON.parse(templatesString) as FlowNodeTemplateType[]; - return ( - <> - - - - - - {/* close icon */} - } - w={'26px'} - h={'26px'} - borderColor={'myGray.300'} - variant={'grayBase'} - aria-label={''} - onClick={onClose} - /> - - {templateType === TemplateTypeEnum.teamPlugin && ( - - - - - - setSearchKey(e.target.value), 200)} - /> - - - router.push('/plugin/list')} - > - 去创建 - - - - )} - {templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && ( - - { - setCurrentParent(undefined); - }} - fontSize="md" + return ( + <> + + + + + + {/* close icon */} + } + w={'26px'} + h={'26px'} + borderColor={'myGray.300'} + variant={'grayBase'} + aria-label={''} + onClick={onClose} + /> + + {templateType === TemplateTypeEnum.teamPlugin && ( + + + + + + setSearchKey(e.target.value), 200)} /> + + + router.push('/plugin/list')} + > + 去创建 + - )} - - - - - ); - }, [ - currentParent, - isOpen, - onChangeTab, - onClose, - router, - searchKey, - t, - templateType, - templatesString - ]); - - return Render; + + )} + {templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && ( + + { + setCurrentParent(undefined); + }} + fontSize="md" + /> + + )} + + + + + ); }; export default React.memo(NodeTemplatesModal); @@ -276,7 +255,8 @@ const RenderList = React.memo(function RenderList({ const { x, y, zoom } = useViewport(); const { setLoading } = useSystemStore(); const { toast } = useToast(); - const { reactFlowWrapper, setNodes } = useFlowProviderStore(); + const reactFlowWrapper = useContextSelector(WorkflowContext, (v) => v.reactFlowWrapper); + const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes); const formatTemplates = useMemo(() => { const copy: nodeTemplateListType = JSON.parse(JSON.stringify(moduleTemplatesList)); diff --git a/projects/app/src/components/core/workflow/Flow/components/ButtonEdge.tsx b/projects/app/src/components/core/workflow/Flow/components/ButtonEdge.tsx index 15ae8072053f..20a6ef0c537a 100644 --- a/projects/app/src/components/core/workflow/Flow/components/ButtonEdge.tsx +++ b/projects/app/src/components/core/workflow/Flow/components/ButtonEdge.tsx @@ -1,12 +1,16 @@ import React, { useCallback, useMemo } from 'react'; import { BezierEdge, getBezierPath, EdgeLabelRenderer, EdgeProps } from 'reactflow'; -import { useFlowProviderStore } from '../FlowProvider'; import { Flex } from '@chakra-ui/react'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/workflow/constants'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; const ButtonEdge = (props: EdgeProps) => { - const { nodes, setEdges, workflowDebugData } = useFlowProviderStore(); + const nodes = useContextSelector(WorkflowContext, (v) => v.nodes); + const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges); + const workflowDebugData = useContextSelector(WorkflowContext, (v) => v.workflowDebugData); + const { id, sourceX, diff --git a/projects/app/src/components/core/workflow/Flow/hooks/useDebug.tsx b/projects/app/src/components/core/workflow/Flow/hooks/useDebug.tsx index ed2ca9886ba6..14afa8879fa6 100644 --- a/projects/app/src/components/core/workflow/Flow/hooks/useDebug.tsx +++ b/projects/app/src/components/core/workflow/Flow/hooks/useDebug.tsx @@ -2,7 +2,6 @@ import { storeNodes2RuntimeNodes } from '@fastgpt/global/core/workflow/runtime/u import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type'; import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { useCallback, useState } from 'react'; -import { getWorkflowStore, useFlowProviderStore } from '../FlowProvider'; import { checkWorkflowNodeAndConnection } from '@/web/core/workflow/utils'; import { useTranslation } from 'next-i18next'; import { useToast } from '@fastgpt/web/hooks/useToast'; @@ -25,6 +24,8 @@ import { import { useForm } from 'react-hook-form'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { checkInputIsReference } from '@fastgpt/global/core/workflow/utils'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext, getWorkflowStore } from '../../context'; const MyRightDrawer = dynamic( () => import('@fastgpt/web/components/common/MyDrawer/MyRightDrawer') @@ -35,7 +36,10 @@ export const useDebug = () => { const { t } = useTranslation(); const { toast } = useToast(); - const { edges, setNodes, onStartNodeDebug, onUpdateNodeError } = useFlowProviderStore(); + const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes); + const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError); + const edges = useContextSelector(WorkflowContext, (v) => v.edges); + const onStartNodeDebug = useContextSelector(WorkflowContext, (v) => v.onStartNodeDebug); const [runtimeNodeId, setRuntimeNodeId] = useState(); const [runtimeNodes, setRuntimeNodes] = useState(); diff --git a/projects/app/src/components/core/workflow/Flow/hooks/useKeyboard.tsx b/projects/app/src/components/core/workflow/Flow/hooks/useKeyboard.tsx index b7ef581458bc..cf8038a82345 100644 --- a/projects/app/src/components/core/workflow/Flow/hooks/useKeyboard.tsx +++ b/projects/app/src/components/core/workflow/Flow/hooks/useKeyboard.tsx @@ -1,14 +1,15 @@ import { useCallback, useEffect, useState } from 'react'; -import { getWorkflowStore, useFlowProviderStore } from '../FlowProvider'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { useCopyData } from '@/web/common/hooks/useCopyData'; import { useTranslation } from 'next-i18next'; import { Node } from 'reactflow'; import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext, getWorkflowStore } from '../../context'; export const useKeyboard = () => { const { t } = useTranslation(); - const { setNodes } = useFlowProviderStore(); + const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes); const { copyData } = useCopyData(); const [isDowningCtrl, setIsDowningCtrl] = useState(false); @@ -29,6 +30,7 @@ export const useKeyboard = () => { const onCopy = useCallback(async () => { if (hasInputtingElement()) return; const { nodes } = await getWorkflowStore(); + const selectedNodes = nodes.filter( (node) => node.selected && !node.data?.isError && node.data?.unique !== true ); diff --git a/projects/app/src/components/core/workflow/Flow/index.tsx b/projects/app/src/components/core/workflow/Flow/index.tsx index 6c2cedd20630..7076b363a5f2 100644 --- a/projects/app/src/components/core/workflow/Flow/index.tsx +++ b/projects/app/src/components/core/workflow/Flow/index.tsx @@ -21,7 +21,6 @@ import dynamic from 'next/dynamic'; import ButtonEdge from './components/ButtonEdge'; import NodeTemplatesModal from './NodeTemplatesModal'; -import { useFlowProviderStore } from './FlowProvider'; import 'reactflow/dist/style.css'; import { useToast } from '@fastgpt/web/hooks/useToast'; @@ -32,6 +31,8 @@ import MyTooltip from '@/components/MyTooltip'; import { connectionLineStyle, defaultEdgeOptions } from '../constants'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useKeyboard } from './hooks/useKeyboard'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../context'; const NodeSimple = dynamic(() => import('./nodes/NodeSimple')); const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = { @@ -71,16 +72,13 @@ const Container = React.memo(function Container() { }); const { isDowningCtrl } = useKeyboard(); - - const { - reactFlowWrapper, - nodes, - onNodesChange, - edges, - setEdges, - onEdgesChange, - setConnectingEdge - } = useFlowProviderStore(); + const setConnectingEdge = useContextSelector(WorkflowContext, (v) => v.setConnectingEdge); + const reactFlowWrapper = useContextSelector(WorkflowContext, (v) => v.reactFlowWrapper); + const nodes = useContextSelector(WorkflowContext, (v) => v.nodes); + const onNodesChange = useContextSelector(WorkflowContext, (v) => v.onNodesChange); + const edges = useContextSelector(WorkflowContext, (v) => v.edges); + const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges); + const onEdgesChange = useContextSelector(WorkflowContext, (v) => v.onEdgesChange); /* node */ const handleNodesChange = useCallback( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeAnswer.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeAnswer.tsx index bf4737634ef6..71b3ba439b20 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeAnswer.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeAnswer.tsx @@ -4,15 +4,16 @@ import NodeCard from './render/NodeCard'; import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; import Container from '../components/Container'; import RenderInput from './render/RenderInput'; -import { useFlowProviderStore } from '../FlowProvider'; import RenderToolInput from './render/RenderToolInput'; import { useTranslation } from 'next-i18next'; import IOTitle from '../components/IOTitle'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; const NodeAnswer = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); - const { nodeId, inputs, outputs } = data; - const { splitToolInputs } = useFlowProviderStore(); + const { nodeId, inputs } = data; + const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs); const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId); return ( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeCQNode.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeCQNode.tsx index 9fb86770db6b..bb0d238bdb2d 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeCQNode.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeCQNode.tsx @@ -11,16 +11,16 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { useTranslation } from 'next-i18next'; import MyTooltip from '@/components/MyTooltip'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; -import { useFlowProviderStore } from '../FlowProvider'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { SourceHandle } from './render/Handle'; -import IOTitle from '../components/IOTitle'; import { getHandleId } from '@fastgpt/global/core/workflow/utils'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; const NodeCQNode = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); const { nodeId, inputs } = data; - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const CustomComponent = useMemo( () => ({ diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeDatasetConcat.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeDatasetConcat.tsx index 450cabbd1455..cf8722bf0d5f 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeDatasetConcat.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeDatasetConcat.tsx @@ -9,19 +9,21 @@ import { useTranslation } from 'next-i18next'; import { SmallAddIcon } from '@chakra-ui/icons'; import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { getOneQuoteInputTemplate } from '@fastgpt/global/core/workflow/template/system/datasetConcat'; -import { useFlowProviderStore } from '../FlowProvider'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import MySlider from '@/components/Slider'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; import RenderOutput from './render/RenderOutput'; import IOTitle from '../components/IOTitle'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; const NodeDatasetConcat = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); const { llmModelList } = useSystemStore(); - const { nodeList, onChangeNode } = useFlowProviderStore(); const { nodeId, inputs, outputs } = data; + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const quotes = useMemo( () => inputs.filter((item) => item.valueType === WorkflowIOValueTypeEnum.datasetQuote), diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeExtract/index.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeExtract/index.tsx index deed8191994e..6f1295fcff8b 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeExtract/index.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeExtract/index.tsx @@ -26,7 +26,6 @@ import ExtractFieldModal, { defaultField } from './ExtractFieldModal'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; -import { useFlowProviderStore } from '../../FlowProvider'; import RenderToolInput from '../render/RenderToolInput'; import { FlowNodeInputItemType, @@ -34,12 +33,17 @@ import { } from '@fastgpt/global/core/workflow/type/io.d'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import IOTitle from '../../components/IOTitle'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../../context'; const NodeExtract = ({ data }: NodeProps) => { const { inputs, outputs, nodeId } = data; - const { splitToolInputs, onChangeNode } = useFlowProviderStore(); - const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId); + const { t } = useTranslation(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + + const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs); + const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId); const [editExtractFiled, setEditExtractField] = useState(); const CustomComponent = useMemo( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/CurlImportModal.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/CurlImportModal.tsx index a413eb2885ec..cf40bdaa73ed 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/CurlImportModal.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/CurlImportModal.tsx @@ -7,7 +7,8 @@ import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { useForm } from 'react-hook-form'; import parse from '@bany/curl-to-json'; -import { useFlowProviderStore } from '../../FlowProvider'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../../context'; type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch'; const methodMap: { [K in RequestMethod]: string } = { @@ -28,7 +29,8 @@ const CurlImportModal = ({ onClose: () => void; }) => { const { t } = useTranslation(); - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + const { register, handleSubmit } = useForm({ defaultValues: { curlContent: '' diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/index.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/index.tsx index 06361cebb5f0..deb72b1de519 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/index.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeHttp/index.tsx @@ -20,7 +20,6 @@ import { useDisclosure } from '@chakra-ui/react'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import { useFlowProviderStore } from '../../FlowProvider'; import { useTranslation } from 'next-i18next'; import Tabs from '@/components/Tabs'; import MyIcon from '@fastgpt/web/components/common/Icon'; @@ -41,6 +40,9 @@ import MySelect from '@fastgpt/web/components/common/MySelect'; import RenderToolInput from '../render/RenderToolInput'; import IOTitle from '../../components/IOTitle'; import { getSystemVariables } from '@/web/core/app/utils'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../../context'; +import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; const CurlImportModal = dynamic(() => import('./CurlImportModal')); export const HttpHeaders = [ @@ -105,7 +107,7 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({ const { t } = useTranslation(); const { toast } = useToast(); const [_, startSts] = useTransition(); - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const { isOpen: isOpenCurl, onOpen: onOpenCurl, onClose: onCloseCurl } = useDisclosure(); @@ -277,7 +279,7 @@ export function RenderHttpProps({ }) { const { t } = useTranslation(); const [selectedTab, setSelectedTab] = useState(TabEnum.params); - const { nodeList } = useFlowProviderStore(); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); const requestMethods = inputs.find((item) => item.key === NodeInputKeyEnum.httpMethod)?.value; const params = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams); @@ -289,11 +291,7 @@ export function RenderHttpProps({ // get variable const variables = useMemo(() => { - const globalVariables = formatEditorVariablePickerIcon( - splitGuideModule(getGuideModule(nodeList))?.variableModules || [] - ); - - const systemVariables = getSystemVariables(t); + const globalVariables = getWorkflowGlobalVariables(nodeList, t); const moduleVariables = formatEditorVariablePickerIcon( inputs @@ -304,7 +302,7 @@ export function RenderHttpProps({ })) ); - return [...moduleVariables, ...globalVariables, ...systemVariables]; + return [...moduleVariables, ...globalVariables]; }, [inputs, nodeList, t]); const variableText = useMemo(() => { @@ -407,7 +405,7 @@ const RenderForm = ({ }) => { const { t } = useTranslation(); const { toast } = useToast(); - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const [list, setList] = useState(input.value || []); const [updateTrigger, setUpdateTrigger] = useState(false); @@ -601,7 +599,7 @@ const RenderJson = ({ variables: EditorVariablePickerType[]; }) => { const { t } = useTranslation(); - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const [_, startSts] = useTransition(); const Render = useMemo(() => { @@ -650,7 +648,7 @@ const RenderPropsItem = ({ text, num }: { text: string; num: number }) => { const NodeHttp = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); const { nodeId, inputs, outputs } = data; - const { splitToolInputs } = useFlowProviderStore(); + const splitToolInputs = useContextSelector(WorkflowContext, (v) => v.splitToolInputs); const { toolInputs, commonInputs, isTool } = splitToolInputs(inputs, nodeId); const CustomComponents = useMemo( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse.tsx index 9da7eda684ee..e8649d354874 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeIfElse.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useMemo } from 'react'; import NodeCard from './render/NodeCard'; import { useTranslation } from 'next-i18next'; -import { useFlowProviderStore } from '../FlowProvider'; import { Box, Button, Flex, background } from '@chakra-ui/react'; import { SmallAddIcon } from '@chakra-ui/icons'; import MyIcon from '@fastgpt/web/components/common/Icon'; @@ -25,11 +24,13 @@ import { import { stringConditionList } from '@fastgpt/global/core/workflow/template/system/ifElse/constant'; import MySelect from '@fastgpt/web/components/common/MySelect'; import MyInput from '@/components/MyInput'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; const NodeIfElse = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); const { nodeId, inputs = [], outputs } = data; - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const condition = useMemo( () => @@ -274,7 +275,7 @@ const ConditionSelect = ({ variable?: ReferenceValueProps; onSelect: (e: VariableConditionEnum) => void; }) => { - const { nodeList } = useFlowProviderStore(); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); // get condition type const valueType = useMemo(() => { @@ -337,7 +338,7 @@ const ConditionValueInput = ({ condition?: VariableConditionEnum; onChange: (e: string) => void; }) => { - const { nodeList } = useFlowProviderStore(); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); // get value type const valueType = useMemo(() => { diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeLaf.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeLaf.tsx index e20e2cd531bd..5acc27da19b0 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeLaf.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeLaf.tsx @@ -5,7 +5,6 @@ import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; import Container from '../components/Container'; import { Box, Button, Center, Flex, useDisclosure } from '@chakra-ui/react'; import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import { useFlowProviderStore } from '../FlowProvider'; import { useTranslation } from 'next-i18next'; import { getLafAppDetail } from '@/web/support/laf/api'; import MySelect from '@fastgpt/web/components/common/MySelect'; @@ -32,6 +31,8 @@ import { } from '@fastgpt/global/core/workflow/type/io'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import IOTitle from '../components/IOTitle'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccountModal')); @@ -42,7 +43,7 @@ const NodeLaf = (props: NodeProps) => { const { data, selected } = props; const { nodeId, inputs, outputs } = data; - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const requestUrl = inputs.find((item) => item.key === NodeInputKeyEnum.httpReqUrl); @@ -293,7 +294,7 @@ const ConfigLaf = () => { const RenderIO = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); const { nodeId, inputs, outputs } = data; - const { splitToolInputs } = useFlowProviderStore(); + const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs); const { commonInputs, toolInputs, isTool } = splitToolInputs(inputs, nodeId); return ( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodePluginInput.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodePluginInput.tsx index 7eee53cc9840..e34dbcbceefa 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodePluginInput.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodePluginInput.tsx @@ -2,7 +2,6 @@ import React, { useMemo, useState } from 'react'; import { NodeProps } from 'reactflow'; import NodeCard from './render/NodeCard'; import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; -import dynamic from 'next/dynamic'; import { Box, Button, Flex } from '@chakra-ui/react'; import { SmallAddIcon } from '@chakra-ui/icons'; import { @@ -17,7 +16,6 @@ import type { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type.d'; import { useTranslation } from 'next-i18next'; -import { useFlowProviderStore } from '../FlowProvider'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeInputMap, @@ -26,6 +24,8 @@ import { } from '@fastgpt/global/core/workflow/node/constant'; import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType'; import VariableTable from '../nodes/render/VariableTable'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; const defaultCreateField: EditNodeFieldType = { label: '', @@ -50,7 +50,7 @@ const NodePluginInput = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); const { nodeId, inputs, outputs } = data; - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const [createField, setCreateField] = useState(); const [editField, setEditField] = useState(); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodePluginOutput.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodePluginOutput.tsx index 731c3987896c..00268ba48065 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodePluginOutput.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodePluginOutput.tsx @@ -11,8 +11,9 @@ import { EditInputFieldMapType, EditNodeFieldType } from '@fastgpt/global/core/w import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { useTranslation } from 'next-i18next'; -import { useFlowProviderStore } from '../FlowProvider'; import RenderInput from './render/RenderInput'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; const FieldEditModal = dynamic(() => import('./render/FieldEditModal')); @@ -31,7 +32,7 @@ const createEditField: EditInputFieldMapType = { const NodePluginOutput = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); const { nodeId, inputs } = data; - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const [createField, setCreateField] = useState(); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeSimple.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeSimple.tsx index 1198919ff750..3509f7691f6a 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeSimple.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeSimple.tsx @@ -7,9 +7,10 @@ import RenderInput from './render/RenderInput'; import RenderOutput from './render/RenderOutput'; import RenderToolInput from './render/RenderToolInput'; import { useTranslation } from 'next-i18next'; -import { useFlowProviderStore } from '../FlowProvider'; import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import IOTitle from '../components/IOTitle'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; const NodeSimple = ({ data, @@ -18,7 +19,7 @@ const NodeSimple = ({ maxW }: NodeProps & { minW?: string | number; maxW?: string | number }) => { const { t } = useTranslation(); - const { splitToolInputs } = useFlowProviderStore(); + const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs); const { nodeId, inputs, outputs } = data; const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeSystemConfig.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeSystemConfig.tsx index 8a04411e2cbb..bc31cde4b94e 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeSystemConfig.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeSystemConfig.tsx @@ -1,24 +1,31 @@ -import React, { useCallback, useMemo, useTransition } from 'react'; +import React, { useMemo, useTransition } from 'react'; import { NodeProps } from 'reactflow'; import { Box, Flex, Textarea, useTheme } from '@chakra-ui/react'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { FlowNodeItemType, StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; -import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { welcomeTextTip } from '@fastgpt/global/core/workflow/template/tip'; -import type { VariableItemType } from '@fastgpt/global/core/app/type.d'; import QGSwitch from '@/components/core/app/QGSwitch'; import TTSSelect from '@/components/core/app/TTSSelect'; import WhisperConfig from '@/components/core/app/WhisperConfig'; import { splitGuideModule } from '@fastgpt/global/core/workflow/utils'; import { useTranslation } from 'next-i18next'; import { TTSTypeEnum } from '@/constants/app'; -import { useFlowProviderStore } from '../FlowProvider'; -import VariableEdit from '../../../app/VariableEdit'; import MyIcon from '@fastgpt/web/components/common/Icon'; import MyTooltip from '@/components/MyTooltip'; import NodeCard from './render/NodeCard'; import ScheduledTriggerConfig from '@/components/core/app/ScheduledTriggerConfig'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; +import { VariableItemType } from '@fastgpt/global/core/app/type'; +import { useMemoizedFn } from 'ahooks'; +import VariableEdit from '@/components/core/app/VariableEdit'; +import { + FlowNodeOutputTypeEnum, + FlowNodeTypeEnum +} from '@fastgpt/global/core/workflow/node/constant'; +import { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io'; const NodeUserGuide = ({ data, selected }: NodeProps) => { const theme = useTheme(); @@ -38,10 +45,10 @@ const NodeUserGuide = ({ data, selected }: NodeProps) => { > - + - + @@ -65,7 +72,7 @@ function WelcomeText({ data }: { data: FlowNodeItemType }) { const { t } = useTranslation(); const { inputs, nodeId } = data; const [, startTst] = useTransition(); - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const welcomeText = inputs.find((item) => item.key === NodeInputKeyEnum.welcomeText); @@ -108,7 +115,7 @@ function WelcomeText({ data }: { data: FlowNodeItemType }) { function ChatStartVariable({ data }: { data: FlowNodeItemType }) { const { inputs, nodeId } = data; - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const variables = useMemo( () => @@ -117,27 +124,25 @@ function ChatStartVariable({ data }: { data: FlowNodeItemType }) { [inputs] ); - const updateVariables = useCallback( - (value: VariableItemType[]) => { - onChangeNode({ - nodeId, - key: NodeInputKeyEnum.variables, - type: 'updateInput', - value: { - ...inputs.find((item) => item.key === NodeInputKeyEnum.variables), - value - } - }); - }, - [inputs, nodeId, onChangeNode] - ); + const updateVariables = useMemoizedFn((value: VariableItemType[]) => { + // update system config node + onChangeNode({ + nodeId, + key: NodeInputKeyEnum.variables, + type: 'updateInput', + value: { + ...inputs.find((item) => item.key === NodeInputKeyEnum.variables), + value + } + }); + }); return updateVariables(e)} />; } function QuestionGuide({ data }: { data: FlowNodeItemType }) { const { inputs, nodeId } = data; - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const questionGuide = useMemo( () => @@ -168,7 +173,7 @@ function QuestionGuide({ data }: { data: FlowNodeItemType }) { function TTSGuide({ data }: { data: FlowNodeItemType }) { const { inputs, nodeId } = data; - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const { ttsConfig } = splitGuideModule({ inputs } as StoreNodeItemType); return ( @@ -191,7 +196,7 @@ function TTSGuide({ data }: { data: FlowNodeItemType }) { function WhisperGuide({ data }: { data: FlowNodeItemType }) { const { inputs, nodeId } = data; - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const { ttsConfig, whisperConfig } = splitGuideModule({ inputs } as StoreNodeItemType); return ( @@ -215,7 +220,7 @@ function WhisperGuide({ data }: { data: FlowNodeItemType }) { function ScheduledTrigger({ data }: { data: FlowNodeItemType }) { const { inputs, nodeId } = data; - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const { scheduledTriggerConfig } = splitGuideModule({ inputs } as StoreNodeItemType); return ( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/NodeWorkflowStart.tsx b/projects/app/src/components/core/workflow/Flow/nodes/NodeWorkflowStart.tsx index 885d2ef3b293..09a3c9546db1 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/NodeWorkflowStart.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/NodeWorkflowStart.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { NodeProps } from 'reactflow'; import NodeCard from './render/NodeCard'; import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; @@ -6,10 +6,30 @@ import Container from '../components/Container'; import RenderOutput from './render/RenderOutput'; import IOTitle from '../components/IOTitle'; import { useTranslation } from 'next-i18next'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../context'; +import { useCreation } from 'ahooks'; +import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; +import { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io'; +import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; +import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; const NodeStart = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); const { nodeId, outputs } = data; + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); + + const variablesOutputs = useCreation(() => { + const variables = getWorkflowGlobalVariables(nodeList, t); + + return variables.map((item) => ({ + id: item.key, + type: FlowNodeOutputTypeEnum.static, + key: item.key, + valueType: item.valueType || WorkflowIOValueTypeEnum.any, + label: item.label + })); + }, [nodeList, t]); return ( ) => { + + + + ); }; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ConnectionHandle.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ConnectionHandle.tsx index 12d1142fa05a..66978becaeaa 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ConnectionHandle.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ConnectionHandle.tsx @@ -1,12 +1,15 @@ import React, { useMemo } from 'react'; import { Position } from 'reactflow'; -import { useFlowProviderStore } from '../../../FlowProvider'; import { SourceHandle, TargetHandle } from '.'; import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; export const ConnectionSourceHandle = ({ nodeId }: { nodeId: string }) => { - const { nodeList, edges, connectingEdge } = useFlowProviderStore(); + const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); + const edges = useContextSelector(WorkflowContext, (v) => v.edges); const node = useMemo(() => nodeList.find((node) => node.nodeId === nodeId), [nodeList, nodeId]); @@ -102,7 +105,8 @@ export const ConnectionSourceHandle = ({ nodeId }: { nodeId: string }) => { }; export const ConnectionTargetHandle = ({ nodeId }: { nodeId: string }) => { - const { nodeList, connectingEdge } = useFlowProviderStore(); + const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); const node = useMemo(() => nodeList.find((node) => node.nodeId === nodeId), [nodeList, nodeId]); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ToolHandle.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ToolHandle.tsx index 7f939362a185..571a29cff3ee 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ToolHandle.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/ToolHandle.tsx @@ -1,15 +1,12 @@ import MyTooltip from '@/components/MyTooltip'; -import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType'; import { Box, BoxProps } from '@chakra-ui/react'; -import { - WorkflowIOValueTypeEnum, - NodeOutputKeyEnum -} from '@fastgpt/global/core/workflow/constants'; +import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { useTranslation } from 'next-i18next'; import { Connection, Handle, Position } from 'reactflow'; -import { useFlowProviderStore } from '../../../FlowProvider'; import { useCallback, useMemo } from 'react'; import { getHandleId } from '@fastgpt/global/core/workflow/utils'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const handleSize = '14px'; type ToolHandleProps = BoxProps & { @@ -17,7 +14,9 @@ type ToolHandleProps = BoxProps & { }; export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => { const { t } = useTranslation(); - const { connectingEdge, edges } = useFlowProviderStore(); + const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge); + const edges = useContextSelector(WorkflowContext, (v) => v.edges); + const handleId = NodeOutputKeyEnum.selectedTools; const connected = edges.some((edge) => edge.target === nodeId && edge.targetHandle === handleId); @@ -62,7 +61,7 @@ export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => { export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => { const { t } = useTranslation(); - const { setEdges } = useFlowProviderStore(); + const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges); /* onConnect edge, delete tool input and switch */ const onConnect = useCallback( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/index.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/index.tsx index 5f161bd511e1..bbda66be3cfd 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/index.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/Handle/index.tsx @@ -1,9 +1,10 @@ import React, { useMemo } from 'react'; import { Handle, Position } from 'reactflow'; -import { useFlowProviderStore } from '../../../FlowProvider'; import { SmallAddIcon } from '@chakra-ui/icons'; import { handleHighLightStyle, sourceCommonStyle, handleConnectedStyle, handleSize } from './style'; import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; type Props = { nodeId: string; @@ -23,7 +24,11 @@ const MySourceHandle = React.memo(function MySourceHandle({ highlightStyle: Record; connectedStyle: Record; }) { - const { nodes, hoverNodeId, edges, connectingEdge } = useFlowProviderStore(); + const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge); + const edges = useContextSelector(WorkflowContext, (v) => v.edges); + + const nodes = useContextSelector(WorkflowContext, (v) => v.nodes); + const hoverNodeId = useContextSelector(WorkflowContext, (v) => v.hoverNodeId); const node = useMemo(() => nodes.find((node) => node.data.nodeId === nodeId), [nodes, nodeId]); const connected = edges.some((edge) => edge.sourceHandle === handleId); @@ -136,8 +141,10 @@ const MyTargetHandle = React.memo(function MyTargetHandle({ highlightStyle: Record; connectedStyle: Record; }) { - const { nodeList, edges, connectingEdge } = useFlowProviderStore(); + const connectingEdge = useContextSelector(WorkflowContext, (ctx) => ctx.connectingEdge); + const edges = useContextSelector(WorkflowContext, (v) => v.edges); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); const node = useMemo(() => nodeList.find((node) => node.nodeId === nodeId), [nodeList, nodeId]); const connected = edges.some((edge) => edge.targetHandle === handleId); const connectedEdges = edges.filter((edge) => edge.target === nodeId); @@ -194,12 +201,13 @@ const MyTargetHandle = React.memo(function MyTargetHandle({ return false; } if (connectingEdge?.handleId && !connectingEdge.handleId?.includes('source')) return false; - // Same source node - if (connectedEdges.some((item) => item.sourceHandle === connectingEdge?.handleId)) return false; + // Same source node + if (connectedEdges.some((item) => item.target === nodeId && item.targetHandle !== handleId)) + return false; return true; - }, [connectedEdges, connectingEdge?.handleId, edges, node, nodeId]); + }, [connectedEdges, connectingEdge?.handleId, edges, handleId, node, nodeId]); const RenderHandle = useMemo(() => { return ( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/NodeCard.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/NodeCard.tsx index 6e68f8b391ac..ee5854d39f68 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/NodeCard.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/NodeCard.tsx @@ -6,7 +6,6 @@ import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index. import { useTranslation } from 'next-i18next'; import { useEditTitle } from '@/web/common/hooks/useEditTitle'; import { useToast } from '@fastgpt/web/hooks/useToast'; -import { useFlowProviderStore } from '../../FlowProvider'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; @@ -21,6 +20,8 @@ import { getPreviewPluginModule } from '@/web/core/plugin/api'; import { getErrText } from '@fastgpt/global/common/error/utils'; import { storeNode2FlowNode } from '@/web/core/workflow/utils'; import { getNanoid } from '@fastgpt/global/common/string/tools'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '../../../context'; type Props = FlowNodeItemType & { children?: React.ReactNode | React.ReactNode[] | string; @@ -55,7 +56,9 @@ const NodeCard = (props: Props) => { pluginId } = props; - const { nodeList, setHoverNodeId, onUpdateNodeError } = useFlowProviderStore(); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); + const setHoverNodeId = useContextSelector(WorkflowContext, (v) => v.setHoverNodeId); + const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError); const showToolHandle = useMemo( () => isTool && !!nodeList.find((item) => item?.flowNodeType === FlowNodeTypeEnum.tools), @@ -105,44 +108,40 @@ const NodeCard = (props: Props) => { intro ]); - const Render = useMemo(() => { - return ( - setHoverNodeId(nodeId)} + onMouseLeave={() => setHoverNodeId(undefined)} + {...(isError + ? { + borderColor: 'red.500', + onMouseDownCapture: () => onUpdateNodeError(nodeId, false) } - }} - onMouseEnter={() => setHoverNodeId(nodeId)} - onMouseLeave={() => setHoverNodeId(undefined)} - {...(isError - ? { - borderColor: 'red.500', - onMouseDownCapture: () => onUpdateNodeError(nodeId, false) - } - : { - borderColor: selected ? 'primary.600' : 'borderColor.base' - })} - > - {Header} - {children} - - - - ); - }, [Header, children, isError, maxW, minW, nodeId, onUpdateNodeError, selected, setHoverNodeId]); - - return Render; + : { + borderColor: selected ? 'primary.600' : 'borderColor.base' + })} + > + {Header} + {children} + + + + ); }; export default React.memo(NodeCard); @@ -180,7 +179,10 @@ const MenuRender = React.memo(function MenuRender({ type: 'delete' }); - const { setNodes, setEdges, onResetNode, onChangeNode } = useFlowProviderStore(); + const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes); + const onResetNode = useContextSelector(WorkflowContext, (v) => v.onResetNode); + const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const onCopyNode = useCallback( (nodeId: string) => { @@ -383,7 +385,8 @@ const NodeIntro = React.memo(function NodeIntro({ intro?: string; }) { const { t } = useTranslation(); - const { onChangeNode, splitToolInputs } = useFlowProviderStore(); + const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const moduleIsTool = useMemo(() => { const { isTool } = splitToolInputs([], nodeId); @@ -442,8 +445,12 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({ debugResult: FlowNodeItemType['debugResult']; }) { const { t } = useTranslation(); - const { onChangeNode, onStopNodeDebug, onNextNodeDebug, workflowDebugData } = - useFlowProviderStore(); + + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + const onStopNodeDebug = useContextSelector(WorkflowContext, (v) => v.onStopNodeDebug); + const onNextNodeDebug = useContextSelector(WorkflowContext, (v) => v.onNextNodeDebug); + const workflowDebugData = useContextSelector(WorkflowContext, (v) => v.workflowDebugData); + const { openConfirm, ConfirmModal } = useConfirm({ content: t('core.workflow.Confirm stop debug') }); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/Label.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/Label.tsx index 25f0eb137e1d..541fa68f69d4 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/Label.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/Label.tsx @@ -1,7 +1,6 @@ import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'next-i18next'; -import { useFlowProviderStore } from '../../../FlowProvider'; import { Box, Flex } from '@chakra-ui/react'; import MyTooltip from '@/components/MyTooltip'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; @@ -13,17 +12,20 @@ import dynamic from 'next/dynamic'; import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type'; import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import ValueTypeLabel from '../ValueTypeLabel'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const FieldEditModal = dynamic(() => import('../FieldEditModal')); type Props = { nodeId: string; input: FlowNodeInputItemType; - mode?: 'app' | 'plugin'; }; const InputLabel = ({ nodeId, input }: Props) => { const { t } = useTranslation(); - const { onChangeNode } = useFlowProviderStore(); + + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + const { description, toolDescription, diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/AddInputParam.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/AddInputParam.tsx index 86ac821c9ecc..166b8be724c6 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/AddInputParam.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/AddInputParam.tsx @@ -5,18 +5,21 @@ import { SmallAddIcon } from '@chakra-ui/icons'; import { useTranslation } from 'next-i18next'; import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type'; import dynamic from 'next/dynamic'; -import { useFlowProviderStore } from '../../../../FlowProvider'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; import Reference from './Reference'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const FieldEditModal = dynamic(() => import('../../FieldEditModal')); const AddInputParam = (props: RenderInputProps) => { const { item, inputs, nodeId } = props; const { t } = useTranslation(); - const { onChangeNode, mode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + const mode = useContextSelector(WorkflowContext, (ctx) => ctx.mode); + const inputValue = useMemo(() => (item.value || []) as FlowNodeInputItemType[], [item.value]); const [editField, setEditField] = useState(); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx index 6ab5c8cc7a05..f2b1ef26ecaa 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/JsonEditor.tsx @@ -1,21 +1,22 @@ import React, { useCallback, useMemo } from 'react'; import type { RenderInputProps } from '../type'; -import { useFlowProviderStore } from '../../../../FlowProvider'; import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor'; -import { - formatEditorVariablePickerIcon, - getGuideModule, - splitGuideModule -} from '@fastgpt/global/core/workflow/utils'; +import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; +import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; +import { useCreation } from 'ahooks'; +import { useTranslation } from 'next-i18next'; const JsonEditor = ({ inputs = [], item, nodeId }: RenderInputProps) => { - const { nodeList, onChangeNode } = useFlowProviderStore(); + const { t } = useTranslation(); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); // get variable - const variables = useMemo(() => { - const globalVariables = formatEditorVariablePickerIcon( - splitGuideModule(getGuideModule(nodeList))?.variableModules || [] - ); + const variables = useCreation(() => { + const globalVariables = getWorkflowGlobalVariables(nodeList, t); + const moduleVariables = formatEditorVariablePickerIcon( inputs .filter((input) => input.canEdit) diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/NumberInput.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/NumberInput.tsx index 884b72439377..80a1f71ac932 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/NumberInput.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/NumberInput.tsx @@ -7,10 +7,11 @@ import { NumberInputField, NumberInputStepper } from '@chakra-ui/react'; -import { useFlowProviderStore } from '../../../../FlowProvider'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const NumberInputRender = ({ item, nodeId }: RenderInputProps) => { - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const Render = useMemo(() => { return ( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Reference.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Reference.tsx index 0f3a543da0a6..1174ac41adc9 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Reference.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Reference.tsx @@ -1,16 +1,19 @@ import React, { useCallback, useMemo } from 'react'; import type { RenderInputProps } from '../type'; import { Flex, Box, ButtonProps } from '@chakra-ui/react'; -import { useFlowProviderStore } from '../../../../FlowProvider'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { computedNodeInputReference } from '@/web/core/workflow/utils'; import { useTranslation } from 'next-i18next'; import { NodeOutputKeyEnum, + VARIABLE_NODE_ID, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import type { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io'; import dynamic from 'next/dynamic'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; +import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; const MultipleRowSelect = dynamic( () => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect') @@ -34,21 +37,37 @@ type SelectProps = { const Reference = ({ item, nodeId }: RenderInputProps) => { const { t } = useTranslation(); - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); const onSelect = useCallback( (e: any) => { - onChangeNode({ - nodeId, - type: 'updateInput', - key: item.key, - value: { - ...item, - value: e - } - }); + const workflowStartNode = nodeList.find( + (node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart + ); + if (e[0] === workflowStartNode?.id && e[1] !== NodeOutputKeyEnum.userChatInput) { + onChangeNode({ + nodeId, + type: 'updateInput', + key: item.key, + value: { + ...item, + value: [VARIABLE_NODE_ID, e[1]] + } + }); + } else { + onChangeNode({ + nodeId, + type: 'updateInput', + key: item.key, + value: { + ...item, + value: e + } + }); + } }, - [item, nodeId, onChangeNode] + [item, nodeId, nodeList, onChangeNode] ); const { referenceList, formatValue } = useReference({ @@ -79,13 +98,15 @@ export const useReference = ({ value?: any; }) => { const { t } = useTranslation(); - const { nodeList, edges } = useFlowProviderStore(); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); + const edges = useContextSelector(WorkflowContext, (v) => v.edges); const referenceList = useMemo(() => { const sourceNodes = computedNodeInputReference({ nodeId, nodes: nodeList, - edges: edges + edges: edges, + t }); if (!sourceNodes) return []; diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Select.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Select.tsx index f45d0ce080e5..09ab20ec356b 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Select.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Select.tsx @@ -1,10 +1,11 @@ import React, { useMemo } from 'react'; import type { RenderInputProps } from '../type'; -import { useFlowProviderStore } from '../../../../FlowProvider'; import MySelect from '@fastgpt/web/components/common/MySelect'; +import { WorkflowContext } from '@/components/core/workflow/context'; +import { useContextSelector } from 'use-context-selector'; const SelectRender = ({ item, nodeId }: RenderInputProps) => { - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const Render = useMemo(() => { return ( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectApp.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectApp.tsx index ecc24173e287..db78f2418e0b 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectApp.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectApp.tsx @@ -1,16 +1,18 @@ import React, { useMemo } from 'react'; import type { RenderInputProps } from '../type'; -import { useFlowProviderStore } from '../../../../FlowProvider'; import { Box, Button, Flex, useDisclosure, useTheme } from '@chakra-ui/react'; import { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d'; import Avatar from '@/components/Avatar'; import SelectAppModal from '../../../../SelectAppModal'; import { useTranslation } from 'next-i18next'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const SelectAppRender = ({ item, nodeId }: RenderInputProps) => { const { t } = useTranslation(); const theme = useTheme(); - const { filterAppIds, onChangeNode } = useFlowProviderStore(); + const filterAppIds = useContextSelector(WorkflowContext, (ctx) => ctx.filterAppIds); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const { isOpen: isOpenSelectApp, @@ -20,7 +22,7 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => { const value = item.value as SelectAppItemType | undefined; - const filterAppString = useMemo(() => filterAppIds.join(','), [filterAppIds]); + const filterAppString = useMemo(() => filterAppIds?.join(',') || '', [filterAppIds]); const Render = useMemo(() => { return ( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx index f34f4e33930a..be8f034f00af 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; import type { RenderInputProps } from '../type'; -import { useFlowProviderStore } from '../../../../FlowProvider'; import { Box, Button, Flex, Grid, useDisclosure, useTheme } from '@chakra-ui/react'; import { useDatasetStore } from '@/web/core/dataset/store/dataset'; import { SelectedDatasetType } from '@fastgpt/global/core/workflow/api'; @@ -8,19 +7,19 @@ import Avatar from '@/components/Avatar'; import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'next-i18next'; import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; -import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; -import { useSystemStore } from '@/web/common/system/useSystemStore'; import dynamic from 'next/dynamic'; import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) => { const { t } = useTranslation(); const theme = useTheme(); - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + const [data, setData] = useState({ searchMode: DatasetSearchModeEnum.embedding, limit: 5, diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx index 666f25bcb471..cf1b3d845f3c 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectDatasetParams.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; import type { RenderInputProps } from '../type'; -import { useFlowProviderStore } from '../../../../FlowProvider'; import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants'; @@ -10,9 +9,13 @@ import MyIcon from '@fastgpt/web/components/common/Icon'; import DatasetParamsModal, { DatasetParamsProps } from '@/components/core/app/DatasetParamsModal'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import SearchParamsTip from '@/components/core/dataset/SearchParamsTip'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => { - const { nodeList, onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); + const { t } = useTranslation(); const { llmModelList } = useSystemStore(); diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectLLMModel.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectLLMModel.tsx index 40e88679ee16..7705847f5a13 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectLLMModel.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SelectLLMModel.tsx @@ -3,11 +3,12 @@ import type { RenderInputProps } from '../type'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants'; import AIModelSelector from '@/components/Select/AIModelSelector'; -import { useFlowProviderStore } from '../../../../FlowProvider'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const SelectAiModelRender = ({ item, nodeId }: RenderInputProps) => { const { llmModelList } = useSystemStore(); - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const modelList = useMemo( () => diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx index ab08f10e977c..9e62816062ca 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingLLMModel.tsx @@ -1,12 +1,13 @@ import React, { useCallback, useMemo } from 'react'; import type { RenderInputProps } from '../type'; -import { useFlowProviderStore } from '../../../../FlowProvider'; import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d'; import SettingLLMModel from '@/components/core/ai/SettingLLMModel'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const SelectAiModelRender = ({ item, inputs = [], nodeId }: RenderInputProps) => { - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const onChangeModel = useCallback( (e: SettingAIDataType) => { diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx index 5b2dcec07a31..fd1e29ef981d 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/SettingQuotePrompt.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import type { RenderInputProps } from '../type'; import { Box, BoxProps, Button, Flex, ModalFooter, useDisclosure } from '@chakra-ui/react'; -import { useFlowProviderStore } from '../../../../FlowProvider'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { useForm } from 'react-hook-form'; import { PromptTemplateItem } from '@fastgpt/global/core/ai/type'; @@ -25,6 +24,10 @@ import MyIcon from '@fastgpt/web/components/common/Icon'; import Reference from './Reference'; import { getSystemVariables } from '@/web/core/app/utils'; import ValueTypeLabel from '../../ValueTypeLabel'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; +import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; +import { useCreation } from 'ahooks'; const LabelStyles: BoxProps = { fontSize: ['sm', 'md'] @@ -38,7 +41,9 @@ const SettingQuotePrompt = (props: RenderInputProps) => { const { inputs = [], nodeId } = props; const { t } = useTranslation(); const { isOpen, onOpen, onClose } = useDisclosure(); - const { nodeList, onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); + const { watch, setValue, handleSubmit } = useForm({ defaultValues: { quoteTemplate: inputs.find((input) => input.key === 'quoteTemplate')?.value || '', @@ -48,14 +53,10 @@ const SettingQuotePrompt = (props: RenderInputProps) => { const aiChatQuoteTemplate = watch('quoteTemplate'); const aiChatQuotePrompt = watch('quotePrompt'); - const variables = useMemo(() => { - const globalVariables = formatEditorVariablePickerIcon( - splitGuideModule(getGuideModule(nodeList))?.variableModules || [] - ); - - const systemVariables = getSystemVariables(t); + const variables = useCreation(() => { + const globalVariables = getWorkflowGlobalVariables(nodeList, t); - return [...globalVariables, ...systemVariables]; + return globalVariables; }, [nodeList, t]); const [selectTemplateData, setSelectTemplateData] = useState<{ diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Slider.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Slider.tsx index 9566dc168df8..7d0cd11e5774 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Slider.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Slider.tsx @@ -1,13 +1,14 @@ import React, { useMemo } from 'react'; import type { RenderInputProps } from '../type'; -import { useFlowProviderStore } from '../../../../FlowProvider'; import { useTranslation } from 'next-i18next'; import { Box } from '@chakra-ui/react'; import MySlider from '@/components/Slider'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const SliderRender = ({ item, nodeId }: RenderInputProps) => { const { t } = useTranslation(); - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const Render = useMemo(() => { return ( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Switch.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Switch.tsx index 26363fce8efa..6d1b124ee437 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Switch.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Switch.tsx @@ -1,10 +1,11 @@ import React, { useMemo } from 'react'; import type { RenderInputProps } from '../type'; import { Switch } from '@chakra-ui/react'; -import { useFlowProviderStore } from '../../../../FlowProvider'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const SwitchRender = ({ item, nodeId }: RenderInputProps) => { - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const Render = useMemo(() => { return ( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/TextInput.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/TextInput.tsx index 9d6faee2474e..c93cea13fd46 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/TextInput.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/TextInput.tsx @@ -1,10 +1,11 @@ import React, { useMemo } from 'react'; import type { RenderInputProps } from '../type'; import { Input } from '@chakra-ui/react'; -import { useFlowProviderStore } from '../../../../FlowProvider'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const TextInput = ({ item, nodeId }: RenderInputProps) => { - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const Render = useMemo(() => { return ( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Textarea.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Textarea.tsx index 8986d2756bef..a5b61cd2323b 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Textarea.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderInput/templates/Textarea.tsx @@ -1,24 +1,22 @@ import React, { useCallback, useMemo } from 'react'; import type { RenderInputProps } from '../type'; -import { useFlowProviderStore } from '../../../../FlowProvider'; import { useTranslation } from 'next-i18next'; import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor'; -import { - formatEditorVariablePickerIcon, - getGuideModule, - splitGuideModule -} from '@fastgpt/global/core/workflow/utils'; -import { getSystemVariables } from '@/web/core/app/utils'; +import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; +import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; +import { useCreation } from 'ahooks'; const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => { const { t } = useTranslation(); - const { nodeList, onChangeNode } = useFlowProviderStore(); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); // get variable - const variables = useMemo(() => { - const globalVariables = formatEditorVariablePickerIcon( - splitGuideModule(getGuideModule(nodeList))?.variableModules || [] - ); + const variables = useCreation(() => { + const globalVariables = getWorkflowGlobalVariables(nodeList, t); + const moduleVariables = formatEditorVariablePickerIcon( inputs .filter((input) => input.canEdit) @@ -28,9 +26,7 @@ const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => { })) ); - const systemVariables = getSystemVariables(t); - - return [...globalVariables, ...moduleVariables, ...systemVariables]; + return [...globalVariables, ...moduleVariables]; }, [nodeList, inputs, t]); const onChange = useCallback( diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/index.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/index.tsx index 12103a9f4488..4e1a8642f73a 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/index.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderOutput/index.tsx @@ -11,7 +11,8 @@ import VariableTable from '../VariableTable'; import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type'; import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType'; import { getNanoid } from '@fastgpt/global/common/string/tools'; -import { useFlowProviderStore } from '../../../FlowProvider'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const RenderList: { types: `${FlowNodeOutputTypeEnum}`[]; @@ -26,7 +27,7 @@ const RenderOutput = ({ flowOutputList: FlowNodeOutputItemType[]; }) => { const { t } = useTranslation(); - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const outputString = useMemo(() => JSON.stringify(flowOutputList), [flowOutputList]); const copyOutputs = useMemo(() => { diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/EditFieldModal.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/EditFieldModal.tsx index 2f26d404c20c..5926a74fd5db 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/EditFieldModal.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/EditFieldModal.tsx @@ -17,8 +17,9 @@ import { defaultEditFormData } from './constants'; import MySelect from '@fastgpt/web/components/common/MySelect'; import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useToast } from '@fastgpt/web/hooks/useToast'; -import { useFlowProviderStore } from '../../../FlowProvider'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const EditFieldModal = ({ defaultValue = defaultEditFormData, @@ -27,7 +28,7 @@ const EditFieldModal = ({ }: EditFieldModalProps) => { const { t } = useTranslation(); const { toast } = useToast(); - const { onChangeNode } = useFlowProviderStore(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const { register, setValue, handleSubmit, watch } = useForm({ defaultValues: defaultValue diff --git a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/index.tsx b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/index.tsx index 5956e8cdfcce..0acd384c41dd 100644 --- a/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/index.tsx +++ b/projects/app/src/components/core/workflow/Flow/nodes/render/RenderToolInput/index.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import type { FlowNodeInputItemType, FlowNodeOutputItemType @@ -19,7 +19,8 @@ import { useTranslation } from 'next-i18next'; import MyIcon from '@fastgpt/web/components/common/Icon'; import dynamic from 'next/dynamic'; import { defaultEditFormData } from './constants'; -import { useFlowProviderStore } from '../../../FlowProvider'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext } from '@/components/core/workflow/context'; const EditFieldModal = dynamic(() => import('./EditFieldModal')); const RenderToolInput = ({ @@ -32,8 +33,9 @@ const RenderToolInput = ({ canEdit?: boolean; }) => { const { t } = useTranslation(); - const { onChangeNode } = useFlowProviderStore(); - const [editField, setEditField] = React.useState(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + + const [editField, setEditField] = useState(); return ( <> diff --git a/projects/app/src/components/core/workflow/Flow/FlowProvider.tsx b/projects/app/src/components/core/workflow/context.tsx similarity index 65% rename from projects/app/src/components/core/workflow/Flow/FlowProvider.tsx rename to projects/app/src/components/core/workflow/context.tsx index 9b32f8306101..91a21dc68535 100644 --- a/projects/app/src/components/core/workflow/Flow/FlowProvider.tsx +++ b/projects/app/src/components/core/workflow/context.tsx @@ -1,58 +1,89 @@ +import { postWorkflowDebug } from '@/web/core/workflow/api'; +import { storeEdgesRenderEdge, storeNode2FlowNode } from '@/web/core/workflow/utils'; +import { getErrText } from '@fastgpt/global/common/error/utils'; +import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/workflow/constants'; +import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; +import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; import { - type Node, - type NodeChange, - type Edge, - type EdgeChange, - useNodesState, - useEdgesState, - OnConnectStartParams -} from 'reactflow'; -import type { FlowNodeItemType, - FlowNodeTemplateType -} from '@fastgpt/global/core/workflow/type/index.d'; -import type { FlowNodeChangeProps } from '@fastgpt/global/core/workflow/type/fe.d'; -import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; + FlowNodeTemplateType, + StoreNodeItemType +} from '@fastgpt/global/core/workflow/type'; +import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; +import { FlowNodeChangeProps } from '@fastgpt/global/core/workflow/type/fe'; +import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; +import { useToast } from '@fastgpt/web/hooks/useToast'; +import { useCreation, useMemoizedFn } from 'ahooks'; import React, { - type SetStateAction, - type Dispatch, - useContext, + Dispatch, + SetStateAction, useCallback, - createContext, - useRef, + useEffect, useMemo, - useState, - useEffect + useRef, + useState } from 'react'; -import { storeEdgesRenderEdge, storeNode2FlowNode } from '@/web/core/workflow/utils'; -import { useToast } from '@fastgpt/web/hooks/useToast'; -import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; -import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/workflow/constants'; -import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; -import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; -import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type'; -import { defaultRunningStatus } from '../constants'; -import { postWorkflowDebug } from '@/web/core/workflow/api'; -import { getErrText } from '@fastgpt/global/common/error/utils'; +import { + Edge, + EdgeChange, + Node, + NodeChange, + OnConnectStartParams, + useEdgesState, + useNodesState +} from 'reactflow'; +import { createContext } from 'use-context-selector'; +import { defaultRunningStatus } from './constants'; import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils'; import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus'; -import { delay } from '@fastgpt/global/common/system/utils'; type OnChange = (changes: ChangesType[]) => void; -export type useFlowProviderStoreType = { - // connect - connectingEdge: OnConnectStartParams | undefined; - setConnectingEdge: React.Dispatch>; - // nodes - basicNodeTemplates: FlowNodeTemplateType[]; - reactFlowWrapper: null | React.RefObject; +type WorkflowContextType = { mode: 'app' | 'plugin'; - filterAppIds: string[]; + basicNodeTemplates: FlowNodeTemplateType[]; + filterAppIds?: string[]; + reactFlowWrapper: React.RefObject | null; + + // nodes nodes: Node[]; nodeList: FlowNodeItemType[]; setNodes: Dispatch[]>>; onNodesChange: OnChange; + hasToolNode: boolean; + hoverNodeId?: string; + setHoverNodeId: React.Dispatch>; + onUpdateNodeError: (node: string, isError: Boolean) => void; + onResetNode: (e: { id: string; module: FlowNodeTemplateType }) => void; + onChangeNode: (e: FlowNodeChangeProps) => void; + + // edges + edges: Edge[]; + setEdges: Dispatch[]>>; + onEdgesChange: OnChange; + onDelEdge: (e: { + nodeId: string; + sourceHandle?: string | undefined; + targetHandle?: string | undefined; + }) => void; + + // connect + connectingEdge?: OnConnectStartParams; + setConnectingEdge: React.Dispatch>; + + // common function + onFixView: () => void; + splitToolInputs: ( + inputs: FlowNodeInputItemType[], + nodeId: string + ) => { + isTool: boolean; + toolInputs: FlowNodeInputItemType[]; + commonInputs: FlowNodeInputItemType[]; + }; + initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => Promise; + + // debug // debug workflowDebugData: | { @@ -72,67 +103,66 @@ export type useFlowProviderStoreType = { runtimeEdges: RuntimeEdgeItemType[]; }) => Promise; onStopNodeDebug: () => void; +}; - edges: Edge[]; - setEdges: Dispatch[]>>; - onEdgesChange: OnChange; - onFixView: () => void; - onChangeNode: (e: FlowNodeChangeProps) => void; - onResetNode: (e: { id: string; module: FlowNodeTemplateType }) => void; - onDelEdge: (e: { - nodeId: string; - sourceHandle?: string | undefined; - targetHandle?: string | undefined; - }) => void; - initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => Promise; - splitToolInputs: ( - inputs: FlowNodeInputItemType[], - nodeId: string - ) => { - isTool: boolean; - toolInputs: FlowNodeInputItemType[]; - commonInputs: FlowNodeInputItemType[]; - }; - hasToolNode: boolean; - hoverNodeId: string | undefined; - setHoverNodeId: React.Dispatch>; - onUpdateNodeError: (node: string, isError: Boolean) => void; +type ContextValueProps = Pick< + WorkflowContextType, + 'mode' | 'basicNodeTemplates' | 'filterAppIds' +> & { + appId?: string; + pluginId?: string; }; -const StateContext = createContext({ - reactFlowWrapper: null, +type DebugDataType = { + runtimeNodes: RuntimeNodeItemType[]; + runtimeEdges: RuntimeEdgeItemType[]; + nextRunNodes: RuntimeNodeItemType[]; +}; + +export const WorkflowContext = createContext({ mode: 'app', - filterAppIds: [], + setConnectingEdge: function ( + value: React.SetStateAction + ): void { + throw new Error('Function not implemented.'); + }, + onFixView: function (): void { + throw new Error('Function not implemented.'); + }, + basicNodeTemplates: [], + reactFlowWrapper: null, nodes: [], + nodeList: [], setNodes: function ( value: React.SetStateAction[]> ): void { - return; + throw new Error('Function not implemented.'); }, onNodesChange: function (changes: NodeChange[]): void { - return; + throw new Error('Function not implemented.'); + }, + hasToolNode: false, + setHoverNodeId: function (value: React.SetStateAction): void { + throw new Error('Function not implemented.'); + }, + onUpdateNodeError: function (node: string, isError: Boolean): void { + throw new Error('Function not implemented.'); }, edges: [], setEdges: function (value: React.SetStateAction[]>): void { - return; + throw new Error('Function not implemented.'); }, onEdgesChange: function (changes: EdgeChange[]): void { - return; - }, - onFixView: function (): void { - return; + throw new Error('Function not implemented.'); }, - onChangeNode: function (e: FlowNodeChangeProps): void { - return; + onResetNode: function (e: { id: string; module: FlowNodeTemplateType }): void { + throw new Error('Function not implemented.'); }, onDelEdge: function (e: { nodeId: string; sourceHandle?: string | undefined; targetHandle?: string | undefined; }): void { - return; - }, - onResetNode: function (e): void { throw new Error('Function not implemented.'); }, splitToolInputs: function ( @@ -145,23 +175,12 @@ const StateContext = createContext({ } { throw new Error('Function not implemented.'); }, - hasToolNode: false, - connectingEdge: undefined, - basicNodeTemplates: [], initData: function (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[]; }): Promise { throw new Error('Function not implemented.'); }, - hoverNodeId: undefined, - setHoverNodeId: function (value: React.SetStateAction): void { - throw new Error('Function not implemented.'); - }, - onUpdateNodeError: function (nodeId: string, isError: Boolean): void { - throw new Error('Function not implemented.'); - }, - nodeList: [], workflowDebugData: undefined, onNextNodeDebug: function (): Promise { throw new Error('Function not implemented.'); @@ -180,55 +199,24 @@ const StateContext = createContext({ onStopNodeDebug: function (): void { throw new Error('Function not implemented.'); }, - setConnectingEdge: function ( - value: React.SetStateAction - ): void { + onChangeNode: function (e: FlowNodeChangeProps): void { throw new Error('Function not implemented.'); } }); -export const useFlowProviderStore = () => useContext(StateContext); -export const FlowProvider = ({ - mode, - basicNodeTemplates = [], - filterAppIds = [], +const WorkflowContextProvider = ({ children, - appId, - pluginId + value }: { - mode: useFlowProviderStoreType['mode']; - basicNodeTemplates: FlowNodeTemplateType[]; - filterAppIds?: string[]; children: React.ReactNode; - appId?: string; - pluginId?: string; + value: ContextValueProps; }) => { - const reactFlowWrapper = useRef(null); + const { appId, pluginId } = value; const { toast } = useToast(); - const [nodes = [], setNodes, onNodesChange] = useNodesState([]); - const [edges, setEdges, onEdgesChange] = useEdgesState([]); - const [hoverNodeId, setHoverNodeId] = useState(); - const [connectingEdge, setConnectingEdge] = useState(); - - const stringifyNodes = useMemo(() => JSON.stringify(nodes.map((node) => node.data)), [nodes]); - const nodeList = useMemo( - () => JSON.parse(stringifyNodes) as FlowNodeItemType[], - [stringifyNodes] - ); - - const hasToolNode = useMemo(() => { - return !!nodes.find((node) => node.data.flowNodeType === FlowNodeTypeEnum.tools); - }, [nodes]); - - const onFixView = useCallback(() => { - const btn = document.querySelector('.custom-workflow-fix_view') as HTMLButtonElement; - - setTimeout(() => { - btn && btn.click(); - }, 100); - }, []); + const reactFlowWrapper = useRef(null); /* edge */ + const [edges, setEdges, onEdgesChange] = useEdgesState([]); const onDelEdge = useCallback( ({ nodeId, @@ -252,9 +240,34 @@ export const FlowProvider = ({ [setEdges] ); + /* connect */ + const [connectingEdge, setConnectingEdge] = useState(); + /* node */ + const [nodes = [], setNodes, onNodesChange] = useNodesState([]); + const [hoverNodeId, setHoverNodeId] = useState(); + + const nodeList = useCreation(() => nodes.map((node) => node.data), [nodes]); + + const hasToolNode = useMemo(() => { + return !!nodes.find((node) => node.data.flowNodeType === FlowNodeTypeEnum.tools); + }, [nodes]); + + const onUpdateNodeError = useMemoizedFn((nodeId: string, isError: Boolean) => { + setNodes((nodes) => { + return nodes.map((item) => { + if (item.data?.nodeId === nodeId) { + item.selected = true; + //@ts-ignore + item.data.isError = isError; + } + return item; + }); + }); + }); + // reset a node data. delete edge and replace it - const onResetNode = useCallback( + const onResetNode = useMemoizedFn( ({ id, module }: { id: string; module: FlowNodeTemplateType }) => { setNodes((state) => state.map((node) => { @@ -277,131 +290,142 @@ export const FlowProvider = ({ return node; }) ); - }, - [onDelEdge, setNodes] + } ); - const onChangeNode = useCallback( - (props: FlowNodeChangeProps) => { - const { nodeId, type } = props; - setNodes((nodes) => - nodes.map((node) => { - if (node.id !== nodeId) return node; - - const updateObj: Record = {}; - - if (type === 'attr') { - if (props.key) { - updateObj[props.key] = props.value; - } - } else if (type === 'updateInput') { - updateObj.inputs = node.data.inputs.map((item) => - item.key === props.key ? props.value : item - ); - } else if (type === 'replaceInput') { - onDelEdge({ nodeId, targetHandle: props.key }); - const oldInputIndex = node.data.inputs.findIndex((item) => item.key === props.key); - updateObj.inputs = node.data.inputs.filter((item) => item.key !== props.key); - setTimeout(() => { - onChangeNode({ - nodeId, - type: 'addInput', - index: oldInputIndex, - value: props.value - }); + + const onChangeNode = useMemoizedFn((props: FlowNodeChangeProps) => { + const { nodeId, type } = props; + setNodes((nodes) => + nodes.map((node) => { + if (node.id !== nodeId) return node; + + const updateObj: Record = {}; + + if (type === 'attr') { + if (props.key) { + updateObj[props.key] = props.value; + } + } else if (type === 'updateInput') { + updateObj.inputs = node.data.inputs.map((item) => + item.key === props.key ? props.value : item + ); + } else if (type === 'replaceInput') { + onDelEdge({ nodeId, targetHandle: props.key }); + const oldInputIndex = node.data.inputs.findIndex((item) => item.key === props.key); + updateObj.inputs = node.data.inputs.filter((item) => item.key !== props.key); + setTimeout(() => { + onChangeNode({ + nodeId, + type: 'addInput', + index: oldInputIndex, + value: props.value + }); + }); + } else if (type === 'addInput') { + const input = node.data.inputs.find((input) => input.key === props.value.key); + if (input) { + toast({ + status: 'warning', + title: 'key 重复' }); - } else if (type === 'addInput') { - const input = node.data.inputs.find((input) => input.key === props.value.key); - if (input) { - toast({ - status: 'warning', - title: 'key 重复' - }); - updateObj.inputs = node.data.inputs; + updateObj.inputs = node.data.inputs; + } else { + if (props.index !== undefined) { + const inputs = [...node.data.inputs]; + inputs.splice(props.index, 0, props.value); + updateObj.inputs = inputs; } else { - if (props.index !== undefined) { - const inputs = [...node.data.inputs]; - inputs.splice(props.index, 0, props.value); - updateObj.inputs = inputs; - } else { - updateObj.inputs = node.data.inputs.concat(props.value); - } + updateObj.inputs = node.data.inputs.concat(props.value); } - } else if (type === 'delInput') { - onDelEdge({ nodeId, targetHandle: props.key }); - updateObj.inputs = node.data.inputs.filter((item) => item.key !== props.key); - } else if (type === 'updateOutput') { - updateObj.outputs = node.data.outputs.map((item) => - item.key === props.key ? props.value : item - ); - } else if (type === 'replaceOutput') { - onDelEdge({ nodeId, sourceHandle: props.key }); - const oldOutputIndex = node.data.outputs.findIndex((item) => item.key === props.key); - updateObj.outputs = node.data.outputs.filter((item) => item.key !== props.key); - console.log(props.value); - setTimeout(() => { - onChangeNode({ - nodeId, - type: 'addOutput', - index: oldOutputIndex, - value: props.value - }); + } + } else if (type === 'delInput') { + onDelEdge({ nodeId, targetHandle: props.key }); + updateObj.inputs = node.data.inputs.filter((item) => item.key !== props.key); + } else if (type === 'updateOutput') { + updateObj.outputs = node.data.outputs.map((item) => + item.key === props.key ? props.value : item + ); + } else if (type === 'replaceOutput') { + onDelEdge({ nodeId, sourceHandle: props.key }); + const oldOutputIndex = node.data.outputs.findIndex((item) => item.key === props.key); + updateObj.outputs = node.data.outputs.filter((item) => item.key !== props.key); + console.log(props.value); + setTimeout(() => { + onChangeNode({ + nodeId, + type: 'addOutput', + index: oldOutputIndex, + value: props.value }); - } else if (type === 'addOutput') { - const output = node.data.outputs.find((output) => output.key === props.value.key); - if (output) { - toast({ - status: 'warning', - title: 'key 重复' - }); - updateObj.outputs = node.data.outputs; + }); + } else if (type === 'addOutput') { + const output = node.data.outputs.find((output) => output.key === props.value.key); + if (output) { + toast({ + status: 'warning', + title: 'key 重复' + }); + updateObj.outputs = node.data.outputs; + } else { + if (props.index !== undefined) { + const outputs = [...node.data.outputs]; + outputs.splice(props.index, 0, props.value); + updateObj.outputs = outputs; } else { - if (props.index !== undefined) { - const outputs = [...node.data.outputs]; - outputs.splice(props.index, 0, props.value); - updateObj.outputs = outputs; - } else { - updateObj.outputs = node.data.outputs.concat(props.value); - } + updateObj.outputs = node.data.outputs.concat(props.value); } - } else if (type === 'delOutput') { - onDelEdge({ nodeId, sourceHandle: props.key }); - updateObj.outputs = node.data.outputs.filter((item) => item.key !== props.key); } + } else if (type === 'delOutput') { + onDelEdge({ nodeId, sourceHandle: props.key }); + updateObj.outputs = node.data.outputs.filter((item) => item.key !== props.key); + } - return { - ...node, - data: { - ...node.data, - ...updateObj - } - }; - }) - ); - }, - [onDelEdge, setNodes, toast] - ); - const onUpdateNodeError = useCallback( - (nodeId: string, isError: Boolean) => { - setNodes((nodes) => { - return nodes.map((item) => { - if (item.data?.nodeId === nodeId) { - item.selected = true; - //@ts-ignore - item.data.isError = isError; + return { + ...node, + data: { + ...node.data, + ...updateObj } - return item; - }); - }); - }, - [setNodes] + }; + }) + ); + }); + + /* function */ + const onFixView = useMemoizedFn(() => { + const btn = document.querySelector('.custom-workflow-fix_view') as HTMLButtonElement; + + setTimeout(() => { + btn && btn.click(); + }, 100); + }); + + /* If the module is connected by a tool, the tool input and the normal input are separated */ + const splitToolInputs = useMemoizedFn((inputs: FlowNodeInputItemType[], nodeId: string) => { + const isTool = !!edges.find( + (edge) => edge.targetHandle === NodeOutputKeyEnum.selectedTools && edge.target === nodeId + ); + + return { + isTool, + toolInputs: inputs.filter((item) => isTool && item.toolDescription), + commonInputs: inputs.filter((item) => { + if (!isTool) return true; + return !item.toolDescription; + }) + }; + }); + + const initData = useMemoizedFn( + async (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => { + setNodes(e.nodes?.map((item) => storeNode2FlowNode({ item }))); + + setEdges(e.edges?.map((item) => storeEdgesRenderEdge({ edge: item }))); + } ); - /* Run workflow debug and get next runtime data */ - const [workflowDebugData, setWorkflowDebugData] = useState<{ - runtimeNodes: RuntimeNodeItemType[]; - runtimeEdges: RuntimeEdgeItemType[]; - nextRunNodes: RuntimeNodeItemType[]; - }>(); + /* debug */ + const [workflowDebugData, setWorkflowDebugData] = useState(); const onNextNodeDebug = useCallback( async (debugData = workflowDebugData) => { if (!debugData) return; @@ -592,36 +616,6 @@ export const FlowProvider = ({ [onNextNodeDebug, onStopNodeDebug] ); - /* If the module is connected by a tool, the tool input and the normal input are separated */ - const splitToolInputs = useCallback( - (inputs: FlowNodeInputItemType[], nodeId: string) => { - const isTool = !!edges.find( - (edge) => edge.targetHandle === NodeOutputKeyEnum.selectedTools && edge.target === nodeId - ); - - return { - isTool, - toolInputs: inputs.filter((item) => isTool && item.toolDescription), - commonInputs: inputs.filter((item) => { - if (!isTool) return true; - return !item.toolDescription; - }) - }; - }, - [edges] - ); - - const initData = useCallback( - async (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => { - setNodes(e.nodes?.map((item) => storeNode2FlowNode({ item }))); - - setEdges(e.edges?.map((item) => storeEdgesRenderEdge({ edge: item }))); - - await delay(200); - }, - [setEdges, setNodes] - ); - useEffect(() => { eventBus.on(EventNameEnum.requestWorkflowStore, () => { eventBus.emit(EventNameEnum.receiveWorkflowStore, { @@ -633,43 +627,49 @@ export const FlowProvider = ({ }; }, [nodes]); - const value = { - reactFlowWrapper, - mode, - filterAppIds, - edges, - setEdges, - onEdgesChange, - // nodes - nodes, - nodeList, - setNodes, - onNodesChange, - hoverNodeId, - setHoverNodeId, - onUpdateNodeError, - workflowDebugData, - onNextNodeDebug, - onStartNodeDebug, - onStopNodeDebug, - - basicNodeTemplates, - // connect - connectingEdge, - setConnectingEdge, - onFixView, - onChangeNode, - onResetNode, - onDelEdge, - initData, - splitToolInputs, - hasToolNode - }; - - return {children}; + return ( + + {children} + + ); }; -export default React.memo(FlowProvider); +export default WorkflowContextProvider; type GetWorkflowStoreResponse = { nodes: Node[]; diff --git a/projects/app/src/components/support/laf/LafAccountModal.tsx b/projects/app/src/components/support/laf/LafAccountModal.tsx index 987437c7068b..d765d106a85b 100644 --- a/projects/app/src/components/support/laf/LafAccountModal.tsx +++ b/projects/app/src/components/support/laf/LafAccountModal.tsx @@ -108,7 +108,9 @@ const LafAccountModal = ({ - {t('support.user.Go laf env', { env: feConfigs.lafEnv })} + {t('support.user.Go laf env', { + env: feConfigs.lafEnv?.split('//')[1] + })} diff --git a/projects/app/src/constants/app.ts b/projects/app/src/constants/app.ts index 4785dcb28f21..40ed5aba98e4 100644 --- a/projects/app/src/constants/app.ts +++ b/projects/app/src/constants/app.ts @@ -1,5 +1,5 @@ import { AppDetailType } from '@fastgpt/global/core/app/type.d'; -import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; +import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; export const defaultApp: AppDetailType = { _id: '', @@ -28,6 +28,32 @@ export const defaultOutLinkForm: OutLinkEditType = { } }; +// export const defaultWecomOutLinkForm: OutLinkConfigEditType = { +// name: '', +// wecomConfig: { +// ReplyLimit: false, +// defaultResponse: '', +// immediateResponse: false, +// WXWORK_TOKEN: '', +// WXWORK_AESKEY: '', +// WXWORK_SECRET: '', +// WXWORD_ID: '' +// }, +// limit: { +// QPM: 100, +// maxUsagePoints: -1 +// } +// }; + +export const defaultFeishuOutLinkForm: OutLinkEditType = { + name: '', + limit: { + QPM: 100, + maxUsagePoints: -1 + }, + responseDetail: false +}; + export enum TTSTypeEnum { none = 'none', web = 'web', diff --git a/projects/app/src/pages/account/components/Individuation.tsx b/projects/app/src/pages/account/components/Individuation.tsx index 1f057bb8991a..bc1eabcd4da1 100644 --- a/projects/app/src/pages/account/components/Individuation.tsx +++ b/projects/app/src/pages/account/components/Individuation.tsx @@ -1,8 +1,7 @@ -import { Box, Card, Flex, Select } from '@chakra-ui/react'; -import React, { useCallback, useRef } from 'react'; +import { Box, Card, Flex } from '@chakra-ui/react'; +import React, { useCallback } from 'react'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useTranslation } from 'next-i18next'; -import { timezoneList } from '@fastgpt/global/common/time/timezone'; import { useUserStore } from '@/web/support/user/useUserStore'; import { UserType } from '@fastgpt/global/support/user/type'; import { useToast } from '@fastgpt/web/hooks/useToast'; diff --git a/projects/app/src/pages/api/core/plugin/update.ts b/projects/app/src/pages/api/core/plugin/update.ts index 85a95e5887f7..ec716055e83e 100644 --- a/projects/app/src/pages/api/core/plugin/update.ts +++ b/projects/app/src/pages/api/core/plugin/update.ts @@ -28,10 +28,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< avatar: props.avatar, parentId: props.parentId, version: 'v2', - ...(modules && { + ...(modules?.length && { modules: modules }), - ...(edges && { edges }), + ...(edges?.length && { edges }), metadata: props.metadata }; diff --git a/projects/app/src/pages/api/lafApi/[...path].ts b/projects/app/src/pages/api/lafApi/[...path].ts index 3b35cfed1f5b..a0fd7e2f8bae 100644 --- a/projects/app/src/pages/api/lafApi/[...path].ts +++ b/projects/app/src/pages/api/lafApi/[...path].ts @@ -2,8 +2,6 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { request } from 'https'; -import { FastGPTProUrl } from '@fastgpt/service/common/system/constants'; -import url from 'url'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -25,7 +23,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) throw new Error('lafEnv is empty'); } - const parsedUrl = url.parse(lafEnv); + const parsedUrl = new URL(lafEnv); delete req.headers?.cookie; delete req.headers?.host; delete req.headers?.origin; diff --git a/projects/app/src/pages/api/proApi/[...path].ts b/projects/app/src/pages/api/proApi/[...path].ts index fe73a5a50a28..7b9a36a58a7d 100644 --- a/projects/app/src/pages/api/proApi/[...path].ts +++ b/projects/app/src/pages/api/proApi/[...path].ts @@ -3,7 +3,6 @@ import { jsonRes } from '@fastgpt/service/common/response'; import { connectToDatabase } from '@/service/mongo'; import { request } from 'http'; import { FastGPTProUrl } from '@fastgpt/service/common/system/constants'; -import url from 'url'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -15,8 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) throw new Error('url is empty'); } - const parsedUrl = url.parse(FastGPTProUrl); - + const parsedUrl = new URL(FastGPTProUrl); delete req.headers?.rootkey; const requestResult = request({ @@ -37,6 +35,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) response.statusCode && res.writeHead(response.statusCode); response.pipe(res); }); + requestResult.on('error', (e) => { res.send(e); res.end(); diff --git a/projects/app/src/pages/api/support/outLink/create.ts b/projects/app/src/pages/api/support/outLink/create.ts index 10f684abf729..ee9edbd1d087 100644 --- a/projects/app/src/pages/api/support/outLink/create.ts +++ b/projects/app/src/pages/api/support/outLink/create.ts @@ -5,17 +5,18 @@ import { MongoOutLink } from '@fastgpt/service/support/outLink/schema'; import { authApp } from '@fastgpt/service/support/permission/auth/app'; import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; import { customAlphabet } from 'nanoid'; -import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant'; +import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24); /* create a shareChat */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { await connectToDatabase(); - const { appId, ...props } = req.body as OutLinkEditType & { - appId: string; - type: `${OutLinkTypeEnum}`; - }; + const { appId, ...props } = req.body as OutLinkEditType & + OutLinkEditType & { + appId: string; + type: PublishChannelEnum; + }; const { teamId, tmbId } = await authApp({ req, authToken: true, appId, per: 'w' }); diff --git a/projects/app/src/pages/api/support/outLink/list.ts b/projects/app/src/pages/api/support/outLink/list.ts index c3e7697a0a15..926b47fcb1b9 100644 --- a/projects/app/src/pages/api/support/outLink/list.ts +++ b/projects/app/src/pages/api/support/outLink/list.ts @@ -9,15 +9,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) try { await connectToDatabase(); - const { appId } = req.query as { + const { appId, type } = req.query as { appId: string; + type: string; }; const { teamId, tmbId, isOwner } = await authApp({ req, authToken: true, appId, per: 'w' }); const data = await MongoOutLink.find({ appId, - ...(isOwner ? { teamId } : { tmbId }) + ...(isOwner ? { teamId } : { tmbId }), + type: type }).sort({ _id: -1 }); diff --git a/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx b/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx index f8bc15b6150b..f7b85fccc444 100644 --- a/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx +++ b/projects/app/src/pages/app/detail/components/FlowEdit/Header.tsx @@ -9,10 +9,6 @@ import dynamic from 'next/dynamic'; import MyIcon from '@fastgpt/web/components/common/Icon'; import ChatTest, { type ChatTestComponentRef } from '@/components/core/workflow/Flow/ChatTest'; -import { - getWorkflowStore, - useFlowProviderStore -} from '@/components/core/workflow/Flow/FlowProvider'; import { flowNode2StoreNodes } from '@/components/core/workflow/utils'; import { useAppStore } from '@/web/core/app/store/useAppStore'; import { useToast } from '@fastgpt/web/hooks/useToast'; @@ -28,6 +24,8 @@ import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import { useQuery } from '@tanstack/react-query'; import { formatTime2HM } from '@fastgpt/global/common/string/time'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context'; const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings')); @@ -59,9 +57,11 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({ }); const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure(); const { publishApp, updateAppDetail } = useAppStore(); - const { edges, onUpdateNodeError } = useFlowProviderStore(); + const edges = useContextSelector(WorkflowContext, (v) => v.edges); + const [isSaving, setIsSaving] = useState(false); const [saveLabel, setSaveLabel] = useState(t('core.app.Onclick to save')); + const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError); const flowData2StoreDataAndCheck = useCallback(async () => { const { nodes } = await getWorkflowStore(); @@ -101,13 +101,13 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({ time: formatTime2HM() }) ); - ChatTestRef.current?.resetChatTest(); + // ChatTestRef.current?.resetChatTest(); } catch (error) {} setIsSaving(false); return null; - }, [updateAppDetail, app._id, edges, ChatTestRef, t]); + }, [updateAppDetail, app._id, edges, t]); const onclickPublish = useCallback(async () => { setIsSaving(true); 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 acd7dda6f205..0f9738e8989d 100644 --- a/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx +++ b/projects/app/src/pages/app/detail/components/FlowEdit/index.tsx @@ -2,10 +2,11 @@ import React, { useEffect, useMemo } from 'react'; import { AppSchema } from '@fastgpt/global/core/app/type.d'; import Header from './Header'; import Flow from '@/components/core/workflow/Flow'; -import FlowProvider, { useFlowProviderStore } from '@/components/core/workflow/Flow/FlowProvider'; import { appSystemModuleTemplates } from '@fastgpt/global/core/workflow/template/constants'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { v1Workflow2V2 } from '@/web/core/workflow/adapt'; +import WorkflowContextProvider, { WorkflowContext } from '@/components/core/workflow/context'; +import { useContextSelector } from 'use-context-selector'; type Props = { app: AppSchema; onClose: () => void }; @@ -17,7 +18,7 @@ const Render = ({ app, onClose }: Props) => { '检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大,会导致许多工作流无法正常排布,请重新手动连接工作流。如仍异常,可尝试删除对应节点后重新添加。\n\n你可以直接点击测试进行调试,无需点击保存,点击保存为新版工作流。' }); - const { initData } = useFlowProviderStore(); + const initData = useContextSelector(WorkflowContext, (v) => v.initData); const workflowStringData = JSON.stringify({ nodes: app.modules || [], @@ -53,13 +54,15 @@ export default React.memo(function FlowEdit(props: Props) { const filterAppIds = useMemo(() => [props.app._id], [props.app._id]); return ( - - + ); }); diff --git a/projects/app/src/pages/app/detail/components/OutLink/index.tsx b/projects/app/src/pages/app/detail/components/OutLink/index.tsx deleted file mode 100644 index dbad20d93861..000000000000 --- a/projects/app/src/pages/app/detail/components/OutLink/index.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useState } from 'react'; -import { Box, useTheme } from '@chakra-ui/react'; - -import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant'; -import dynamic from 'next/dynamic'; - -import MyRadio from '@/components/common/MyRadio'; -import Share from './Share'; -import { useTranslation } from 'next-i18next'; -const API = dynamic(() => import('./API')); - -const OutLink = ({ appId }: { appId: string }) => { - const { t } = useTranslation(); - const theme = useTheme(); - - const [linkType, setLinkType] = useState<`${OutLinkTypeEnum}`>(OutLinkTypeEnum.share); - - return ( - - - {t('core.app.navbar.Publish app')} - - - setLinkType(e as `${OutLinkTypeEnum}`)} - /> - - - {linkType === OutLinkTypeEnum.share && } - {linkType === OutLinkTypeEnum.apikey && } - - ); -}; - -export default OutLink; diff --git a/projects/app/src/pages/app/detail/components/OutLink/API.tsx b/projects/app/src/pages/app/detail/components/Publish/API/index.tsx similarity index 100% rename from projects/app/src/pages/app/detail/components/OutLink/API.tsx rename to projects/app/src/pages/app/detail/components/Publish/API/index.tsx diff --git a/projects/app/src/pages/app/detail/components/Publish/FeiShu/FeiShuEditModal.tsx b/projects/app/src/pages/app/detail/components/Publish/FeiShu/FeiShuEditModal.tsx new file mode 100644 index 000000000000..a172e0d6367b --- /dev/null +++ b/projects/app/src/pages/app/detail/components/Publish/FeiShu/FeiShuEditModal.tsx @@ -0,0 +1,211 @@ +import React, { useMemo } from 'react'; +import { Flex, Box, Button, ModalFooter, ModalBody, Input } from '@chakra-ui/react'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import { QuestionOutlineIcon } from '@chakra-ui/icons'; +import MyTooltip from '@/components/MyTooltip'; +import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; +import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type'; +import { useTranslation } from 'next-i18next'; +import { useForm } from 'react-hook-form'; +import { useRequest } from '@/web/common/hooks/useRequest'; +import dayjs from 'dayjs'; +import { createShareChat, updateShareChat } from '@/web/support/outLink/api'; + +const FeiShuEditModal = ({ + appId, + defaultData, + onClose, + onCreate, + onEdit +}: { + appId: string; + defaultData: OutLinkEditType; + onClose: () => void; + onCreate: (id: string) => void; + onEdit: () => void; +}) => { + const { t } = useTranslation(); + const { + register, + setValue, + handleSubmit: submitShareChat + } = useForm({ + defaultValues: defaultData + }); + + const isEdit = useMemo(() => !!defaultData?._id, [defaultData]); + + const { mutate: onclickCreate, isLoading: creating } = useRequest({ + mutationFn: async (e: OutLinkEditType) => { + createShareChat({ + ...e, + appId, + type: PublishChannelEnum.feishu + }); + }, + errorToast: t('common.Create Failed'), + onSuccess: onCreate + }); + const { mutate: onclickUpdate, isLoading: updating } = useRequest({ + mutationFn: (e: OutLinkEditType) => { + return updateShareChat(e); + }, + errorToast: t('common.Update Failed'), + onSuccess: onEdit + }); + + return ( + + + + {t('Name')} + + + + + QPM + + + + + + + + + {t('support.outlink.Max usage points')} + + + + + + + + + {t('common.Expired Time')} + + { + setValue('limit.expiredTime', new Date(e.target.value)); + }} + /> + + + + 默认回复 + {/* TODO: i18n */} + + + + + + 立即回复 + {/* TODO: i18n */} + + + + + {t('core.module.http.AppId')} + + + + {t('core.module.http.AppSecret')} + + + + Encrypt Key + + + + Verification Token + + + {/* */} + {/* */} + {/* 限制回复 */} + {/* */} + {/* */} + {/* */} + + + + + + + ); +}; + +export default FeiShuEditModal; diff --git a/projects/app/src/pages/app/detail/components/Publish/FeiShu/index.tsx b/projects/app/src/pages/app/detail/components/Publish/FeiShu/index.tsx new file mode 100644 index 000000000000..9d6621ef3afd --- /dev/null +++ b/projects/app/src/pages/app/detail/components/Publish/FeiShu/index.tsx @@ -0,0 +1,193 @@ +import React, { useState } from 'react'; +import { + Flex, + Box, + Button, + TableContainer, + Table, + Thead, + Tr, + Th, + Td, + Tbody +} from '@chakra-ui/react'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useLoading } from '@fastgpt/web/hooks/useLoading'; +import { useQuery } from '@tanstack/react-query'; +import { getShareChatList, delShareChatById } from '@/web/support/outLink/api'; +import { formatTimeToChatTime } from '@/utils/tools'; +import { useCopyData } from '@/web/common/hooks/useCopyData'; +import { defaultFeishuOutLinkForm } from '@/constants/app'; +import type { FeishuType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; +import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; +import { useTranslation } from 'next-i18next'; +import { useToast } from '@fastgpt/web/hooks/useToast'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import dayjs from 'dayjs'; +import dynamic from 'next/dynamic'; +import MyMenu from '@fastgpt/web/components/common/MyMenu'; + +const FeiShuEditModal = dynamic(() => import('./FeiShuEditModal')); + +const FeiShu = ({ appId }: { appId: string }) => { + const { t } = useTranslation(); + const { Loading, setIsLoading } = useLoading(); + const { feConfigs } = useSystemStore(); + const { copyData } = useCopyData(); + const [editFeiShuLinkData, setEditFeiShuLinkData] = useState>(); + const { toast } = useToast(); + const { + isFetching, + data: shareChatList = [], + refetch: refetchShareChatList + } = useQuery(['initShareChatList', appId], () => + getShareChatList({ appId, type: PublishChannelEnum.feishu }) + ); + + return ( + + + + {t('core.app.publish.Fei shu bot publish')} + + + + + + + + + + {feConfigs?.isPlus && ( + <> + + + + )} + + + + + + {shareChatList.map((item) => ( + + + + {feConfigs?.isPlus && ( + <> + + + + )} + + + + ))} + +
{t('common.Name')}{t('support.outlink.Usage points')}{t('core.app.share.Ip limit title')}{t('common.Expired Time')}{t('common.Last use time')}
{item.name} + {Math.round(item.usagePoints)} + {feConfigs?.isPlus + ? `${ + item.limit?.maxUsagePoints && item.limit.maxUsagePoints > -1 + ? ` / ${item.limit.maxUsagePoints}` + : ` / ${t('common.Unlimited')}` + }` + : ''} + {item?.limit?.QPM || '-'} + {item?.limit?.expiredTime + ? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm') + : '-'} + + {item.lastTime ? t(formatTimeToChatTime(item.lastTime)) : t('common.Un used')} + + + } + menuList={[ + { + label: t('common.Edit'), + icon: 'edit', + onClick: () => + setEditFeiShuLinkData({ + _id: item._id, + name: item.name, + limit: item.limit, + app: item.app, + responseDetail: item.responseDetail, + defaultResponse: item.defaultResponse, + immediateResponse: item.immediateResponse + }) + }, + { + label: t('common.Delete'), + icon: 'delete', + onClick: async () => { + setIsLoading(true); + try { + await delShareChatById(item._id); + refetchShareChatList(); + } catch (error) { + console.log(error); + } + setIsLoading(false); + } + } + ]} + /> +
+
+ {editFeiShuLinkData && ( + { + refetchShareChatList(); + setEditFeiShuLinkData(undefined); + }} + onEdit={() => { + toast({ + status: 'success', + title: t('common.Update Successful') + }); + refetchShareChatList(); + setEditFeiShuLinkData(undefined); + }} + onClose={() => setEditFeiShuLinkData(undefined)} + /> + )} + {shareChatList.length === 0 && !isFetching && ( + + + + {t('core.app.share.Not share link')} + + + )} + +
+ ); +}; + +export default React.memo(FeiShu); diff --git a/projects/app/src/pages/app/detail/components/OutLink/SelectUsingWayModal.tsx b/projects/app/src/pages/app/detail/components/Publish/Link/SelectUsingWayModal.tsx similarity index 100% rename from projects/app/src/pages/app/detail/components/OutLink/SelectUsingWayModal.tsx rename to projects/app/src/pages/app/detail/components/Publish/Link/SelectUsingWayModal.tsx diff --git a/projects/app/src/pages/app/detail/components/OutLink/Share.tsx b/projects/app/src/pages/app/detail/components/Publish/Link/index.tsx similarity index 94% rename from projects/app/src/pages/app/detail/components/OutLink/Share.tsx rename to projects/app/src/pages/app/detail/components/Publish/Link/index.tsx index 43acc907d08e..b20833286ad3 100644 --- a/projects/app/src/pages/app/detail/components/OutLink/Share.tsx +++ b/projects/app/src/pages/app/detail/components/Publish/Link/index.tsx @@ -32,8 +32,8 @@ import { useCopyData } from '@/web/common/hooks/useCopyData'; import { useForm } from 'react-hook-form'; import { defaultOutLinkForm } from '@/constants/app'; import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d'; -import { useRequest } from '@fastgpt/web/hooks/useRequest'; -import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant'; +import { useRequest } from '@/web/common/hooks/useRequest'; +import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; import { useTranslation } from 'next-i18next'; import { useToast } from '@fastgpt/web/hooks/useToast'; import { useSystemStore } from '@/web/common/system/useSystemStore'; @@ -47,7 +47,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; const SelectUsingWayModal = dynamic(() => import('./SelectUsingWayModal')); -const Share = ({ appId }: { appId: string }) => { +const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => { const { t } = useTranslation(); const { Loading, setIsLoading } = useLoading(); const { feConfigs } = useSystemStore(); @@ -64,7 +64,9 @@ const Share = ({ appId }: { appId: string }) => { isFetching, data: shareChatList = [], refetch: refetchShareChatList - } = useQuery(['initShareChatList', appId], () => getShareChatList(appId)); + } = useQuery(['initShareChatList', appId], () => + getShareChatList({ appId, type: PublishChannelEnum.share }) + ); return ( @@ -95,12 +97,16 @@ const Share = ({ appId }: { appId: string }) => { {t('common.Name')} + {feConfigs?.isPlus && ( + <> + {t('common.Expired Time')} + + )} {t('support.outlink.Usage points')} {t('core.app.share.Is response quote')} {feConfigs?.isPlus && ( <> {t('core.app.share.Ip limit title')} - {t('common.Expired Time')} {t('core.app.share.Role check')} )} @@ -112,6 +118,15 @@ const Share = ({ appId }: { appId: string }) => { {shareChatList.map((item) => ( {item.name} + {feConfigs?.isPlus && ( + <> + + {item.limit?.expiredTime + ? dayjs(item.limit.expiredTime).format('YYYY-MM-DD HH:mm') + : '-'} + + + )} {Math.round(item.usagePoints)} {feConfigs?.isPlus @@ -126,18 +141,14 @@ const Share = ({ appId }: { appId: string }) => { {feConfigs?.isPlus && ( <> {item?.limit?.QPM || '-'} - - {item?.limit?.expiredTime - ? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm') - : '-'} - + {item?.limit?.hookUrl ? '✔' : '✖'} )} {item.lastTime ? formatTimeToChatTime(item.lastTime) : t('common.Un used')}
@@ -272,7 +262,7 @@ const EditForm = ({ {t('common.Params')} - {getValues('dataset.datasets').length > 0 && ( + {datasetSearchSetting.datasets?.length > 0 && ( { setValue('userGuide.variables', e); - setRefresh(!refresh); }} /> @@ -411,10 +400,9 @@ const EditForm = ({ {/* tts */} { setValue('userGuide.tts', e); - setRefresh((state) => !state); }} /> @@ -422,11 +410,10 @@ const EditForm = ({ {/* whisper */} { setValue('userGuide.whisper', e); - setRefresh((state) => !state); }} /> @@ -434,12 +421,10 @@ const EditForm = ({ {/* question guide */} { - const value = e.target.checked; - setValue('userGuide.questionGuide', value); - setRefresh((state) => !state); + setValue('userGuide.questionGuide', e.target.checked); }} /> @@ -468,8 +453,6 @@ const EditForm = ({ ...getValues('dataset'), ...e }); - - setRefresh((state) => !state); }} /> )} diff --git a/projects/app/src/pages/app/detail/components/SimpleEdit/index.tsx b/projects/app/src/pages/app/detail/components/SimpleEdit/index.tsx index 00a5f5d67beb..e8707b5a2384 100644 --- a/projects/app/src/pages/app/detail/components/SimpleEdit/index.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleEdit/index.tsx @@ -2,14 +2,33 @@ import React from 'react'; import { Box, Grid } from '@chakra-ui/react'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSticky } from '@/web/common/hooks/useSticky'; +import { useMount } from 'ahooks'; +import { useDatasetStore } from '@/web/core/dataset/store/dataset'; +import { useAppStore } from '@/web/core/app/store/useAppStore'; +import { useForm } from 'react-hook-form'; +import { appWorkflow2Form } from '@fastgpt/global/core/app/utils'; import ChatTest from './ChatTest'; import AppCard from './AppCard'; import EditForm from './EditForm'; +import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type'; const SimpleEdit = ({ appId }: { appId: string }) => { const { isPc } = useSystemStore(); const { parentRef, divRef, isSticky } = useSticky(); + const { loadAllDatasets } = useDatasetStore(); + const { appDetail } = useAppStore(); + + const editForm = useForm({ + defaultValues: appWorkflow2Form({ + nodes: appDetail.modules + }) + }); + + // show selected dataset + useMount(() => { + loadAllDatasets(); + }); return ( @@ -25,10 +44,10 @@ const SimpleEdit = ({ appId }: { appId: string }) => { - +
- {isPc && } + {isPc && } ); }; diff --git a/projects/app/src/pages/app/detail/index.tsx b/projects/app/src/pages/app/detail/index.tsx index 9a56dae21180..ad0aa32d2643 100644 --- a/projects/app/src/pages/app/detail/index.tsx +++ b/projects/app/src/pages/app/detail/index.tsx @@ -21,7 +21,7 @@ import { useTranslation } from 'next-i18next'; const FlowEdit = dynamic(() => import('./components/FlowEdit'), { loading: () => }); -const OutLink = dynamic(() => import('./components/OutLink'), {}); +const Publish = dynamic(() => import('./components/Publish'), {}); const Logs = dynamic(() => import('./components/Logs'), {}); enum TabEnum { @@ -39,7 +39,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { const { feConfigs } = useSystemStore(); const { toast } = useToast(); const { appId } = router.query as { appId: string }; - const { appDetail, loadAppDetail, clearAppModules } = useAppStore(); + const { appDetail, loadAppDetail } = useAppStore(); const setCurrentTab = useCallback( (tab: `${TabEnum}`) => { @@ -82,7 +82,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]); - useQuery([appId], () => loadAppDetail(appId, true), { + const { isSuccess, isLoading } = useQuery([appId], () => loadAppDetail(appId, true), { onError(err: any) { toast({ title: err?.message || t('core.app.error.Get app failed'), @@ -100,88 +100,90 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { {appDetail.name} - - - {/* pc tab */} - - - - + + {isSuccess && ( + + {/* pc tab */} + + + + + {appDetail.name} + + + { + if (e === 'startChat') { + router.push(`/chat?appId=${appId}`); + } else { + setCurrentTab(e); + } + }} + /> + router.replace('/app/list')} + > + } + bg={'white'} + boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'} + size={'smSquare'} + borderRadius={'50%'} + aria-label={''} + /> + {t('app.My Apps')} + + + {/* phone tab */} + + {appDetail.name} - - { - if (e === 'startChat') { - router.push(`/chat?appId=${appId}`); - } else { - setCurrentTab(e); - } - }} - /> - router.replace('/app/list')} - > - } - bg={'white'} - boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'} - size={'smSquare'} - borderRadius={'50%'} - aria-label={''} + { + if (e === 'startChat') { + router.push(`/chat?appId=${appId}`); + } else { + setCurrentTab(e); + } + }} /> - {t('app.My Apps')} - - - {/* phone tab */} - - - {appDetail.name} - { - if (e === 'startChat') { - router.push(`/chat?appId=${appId}`); - } else { - setCurrentTab(e); - } - }} - /> - - - {currentTab === TabEnum.simpleEdit && } - {currentTab === TabEnum.adEdit && appDetail && ( - - )} - {currentTab === TabEnum.logs && } - {currentTab === TabEnum.publish && } - - + + {currentTab === TabEnum.simpleEdit && } + {currentTab === TabEnum.adEdit && appDetail && ( + + )} + {currentTab === TabEnum.logs && } + {currentTab === TabEnum.publish && } + + + )} ); diff --git a/projects/app/src/pages/plugin/edit/Header.tsx b/projects/app/src/pages/plugin/edit/Header.tsx index 3a131a0d16c3..8ac2d350fee3 100644 --- a/projects/app/src/pages/plugin/edit/Header.tsx +++ b/projects/app/src/pages/plugin/edit/Header.tsx @@ -11,14 +11,12 @@ import { flowNode2StoreNodes } from '@/components/core/workflow/utils'; import { putUpdatePlugin } from '@/web/core/plugin/api'; import { useToast } from '@fastgpt/web/hooks/useToast'; import MyMenu from '@fastgpt/web/components/common/MyMenu'; -import { - getWorkflowStore, - useFlowProviderStore -} from '@/components/core/workflow/Flow/FlowProvider'; import { checkWorkflowNodeAndConnection, filterSensitiveNodesData } from '@/web/core/workflow/utils'; +import { useContextSelector } from 'use-context-selector'; +import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context'; const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings')); @@ -29,11 +27,13 @@ const Header = ({ plugin, onClose }: Props) => { const { t } = useTranslation(); const { toast } = useToast(); const { copyData } = useCopyData(); - const { edges, onUpdateNodeError } = useFlowProviderStore(); + const edges = useContextSelector(WorkflowContext, (v) => v.edges); + const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError); const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure(); const flowData2StoreDataAndCheck = useCallback(async () => { const { nodes } = await getWorkflowStore(); + const checkResults = checkWorkflowNodeAndConnection({ nodes, edges }); if (!checkResults) { const storeNodes = flowNode2StoreNodes({ nodes, edges }); diff --git a/projects/app/src/pages/plugin/edit/index.tsx b/projects/app/src/pages/plugin/edit/index.tsx index ee818b4fe25e..a1b920055e73 100644 --- a/projects/app/src/pages/plugin/edit/index.tsx +++ b/projects/app/src/pages/plugin/edit/index.tsx @@ -2,7 +2,6 @@ import React, { useEffect } from 'react'; import { useRouter } from 'next/router'; import Header from './Header'; import Flow from '@/components/core/workflow/Flow'; -import FlowProvider, { useFlowProviderStore } from '@/components/core/workflow/Flow/FlowProvider'; import { pluginSystemModuleTemplates } from '@fastgpt/global/core/workflow/template/constants'; import { serviceSideProps } from '@/web/common/utils/i18n'; import { useQuery } from '@tanstack/react-query'; @@ -14,6 +13,8 @@ import { useTranslation } from 'next-i18next'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { v1Workflow2V2 } from '@/web/core/workflow/adapt'; import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; +import WorkflowContextProvider, { WorkflowContext } from '@/components/core/workflow/context'; +import { useContextSelector } from 'use-context-selector'; type Props = { pluginId: string }; @@ -21,7 +22,7 @@ const Render = ({ pluginId }: Props) => { const { t } = useTranslation(); const router = useRouter(); const { toast } = useToast(); - const { initData } = useFlowProviderStore(); + const initData = useContextSelector(WorkflowContext, (v) => v.initData); const { data: pluginDetail } = useQuery( ['getOnePlugin', pluginId], @@ -78,9 +79,11 @@ const Render = ({ pluginId }: Props) => { export default function FlowEdit(props: any) { return ( - + - + ); } diff --git a/projects/app/src/web/common/utils/voice.ts b/projects/app/src/web/common/utils/voice.ts index 40fbcb9027c2..6c1589b645ce 100644 --- a/projects/app/src/web/common/utils/voice.ts +++ b/projects/app/src/web/common/utils/voice.ts @@ -6,6 +6,7 @@ import { TTSTypeEnum } from '@/constants/app'; import { useTranslation } from 'next-i18next'; import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d'; import { getToken } from '@/web/support/user/auth'; +import { useMount } from 'ahooks'; const contentType = 'audio/mpeg'; const splitMarker = 'SPLIT_MARKER'; @@ -329,7 +330,7 @@ export const useAudioPlay = (props?: OutLinkChatAuthProps & { ttsConfig?: AppTTS ); // listen audio status - useEffect(() => { + useMount(() => { const audio = new Audio(); audioRef.current = audio; @@ -357,7 +358,7 @@ export const useAudioPlay = (props?: OutLinkChatAuthProps & { ttsConfig?: AppTTS audio.remove(); window.removeEventListener('beforeunload', listen); }; - }, []); + }); return { audio: audioRef.current, diff --git a/projects/app/src/web/context/useInitApp.ts b/projects/app/src/web/context/useInitApp.ts index 20d7ffbd08ae..bfab4fee028d 100644 --- a/projects/app/src/web/context/useInitApp.ts +++ b/projects/app/src/web/context/useInitApp.ts @@ -1,10 +1,11 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { clientInitData } from '@/web/common/system/staticData'; import { useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import type { FastGPTFeConfigsType } from '@fastgpt/global/common/system/types/index.d'; import { change2DefaultLng, setLngStore } from '@/web/common/utils/i18n'; +import { useMemoizedFn, useMount } from 'ahooks'; export const useInitApp = () => { const router = useRouter(); @@ -14,7 +15,7 @@ export const useInitApp = () => { const [scripts, setScripts] = useState([]); const [title, setTitle] = useState(process.env.SYSTEM_NAME || 'AI'); - const initFetch = useCallback(async () => { + const initFetch = useMemoizedFn(async () => { const { feConfigs: { scripts, isPlus, show_git, systemTitle } } = await clientInitData(); @@ -35,18 +36,18 @@ export const useInitApp = () => { setScripts(scripts || []); setInitd(); - }, [loadGitStar, setInitd]); + }); - const initUserLanguage = useCallback(() => { + const initUserLanguage = useMemoizedFn(() => { // get default language const targetLng = change2DefaultLng(i18n.language); if (targetLng) { setLngStore(targetLng); router.replace(router.asPath, undefined, { locale: targetLng }); } - }, []); + }); - useEffect(() => { + useMount(() => { initFetch(); initUserLanguage(); @@ -67,7 +68,7 @@ export const useInitApp = () => { return () => { window.removeEventListener('error', errorTrack); }; - }, []); + }); useEffect(() => { hiId && localStorage.setItem('inviterId', hiId); diff --git a/projects/app/src/web/core/app/utils.ts b/projects/app/src/web/core/app/utils.ts index 861fec164373..494662a04e5d 100644 --- a/projects/app/src/web/core/app/utils.ts +++ b/projects/app/src/web/core/app/utils.ts @@ -704,7 +704,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType { ...pluginTool.map((tool) => tool.edges).flat() ] }; - console.log(config); + return config; } @@ -724,23 +724,28 @@ export const getSystemVariables = (t: TFunction): EditorVariablePickerType[] => return [ { key: 'appId', - label: t('core.module.http.AppId') + label: t('core.module.http.AppId'), + valueType: WorkflowIOValueTypeEnum.string }, { key: 'chatId', - label: t('core.module.http.ChatId') + label: t('core.module.http.ChatId'), + valueType: WorkflowIOValueTypeEnum.string }, { key: 'responseChatItemId', - label: t('core.module.http.ResponseChatItemId') + label: t('core.module.http.ResponseChatItemId'), + valueType: WorkflowIOValueTypeEnum.string }, { key: 'histories', - label: t('core.module.http.Histories') + label: t('core.module.http.Histories'), + valueType: WorkflowIOValueTypeEnum.chatHistory }, { key: 'cTime', - label: t('core.module.http.Current time') + label: t('core.module.http.Current time'), + valueType: WorkflowIOValueTypeEnum.string } ]; }; diff --git a/projects/app/src/web/core/workflow/adapt.ts b/projects/app/src/web/core/workflow/adapt.ts index 74903e606704..040ee90cca9f 100644 --- a/projects/app/src/web/core/workflow/adapt.ts +++ b/projects/app/src/web/core/workflow/adapt.ts @@ -16,7 +16,7 @@ import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type'; import { VARIABLE_NODE_ID } from '@fastgpt/global/core/workflow/constants'; -import { getHandleId, splitGuideModule } from '@fastgpt/global/core/workflow/utils'; +import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { LLMModelTypeEnum } from '@fastgpt/global/core/ai/constants'; import { @@ -24,8 +24,10 @@ import { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io'; import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants'; +import { getWorkflowGlobalVariables } from './utils'; +import { TFunction } from 'next-i18next'; -export const systemConfigNode2VariableNode = (node: FlowNodeItemType) => { +export const getGlobalVariableNode = (nodes: FlowNodeItemType[], t: TFunction) => { const template: FlowNodeTemplateType = { id: FlowNodeTypeEnum.globalVariable, templateType: FlowNodeTemplateTypeEnum.other, @@ -41,17 +43,17 @@ export const systemConfigNode2VariableNode = (node: FlowNodeItemType) => { outputs: [] }; - const { variableModules } = splitGuideModule(node); + const globalVariables = getWorkflowGlobalVariables(nodes, t); const variableNode: FlowNodeItemType = { nodeId: VARIABLE_NODE_ID, ...template, - outputs: variableModules.map((item) => ({ + outputs: globalVariables.map((item) => ({ id: item.key, - type: FlowNodeOutputTypeEnum.dynamic, + type: FlowNodeOutputTypeEnum.static, label: item.label, key: item.key, - valueType: WorkflowIOValueTypeEnum.any + valueType: item.valueType || WorkflowIOValueTypeEnum.any })) }; diff --git a/projects/app/src/web/core/workflow/utils.ts b/projects/app/src/web/core/workflow/utils.ts index 491d0d5bc063..3d718d234538 100644 --- a/projects/app/src/web/core/workflow/utils.ts +++ b/projects/app/src/web/core/workflow/utils.ts @@ -13,9 +13,17 @@ import { import { EmptyNode } from '@fastgpt/global/core/workflow/template/system/emptyNode'; import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { getNanoid } from '@fastgpt/global/common/string/tools'; -import { systemConfigNode2VariableNode } from './adapt'; +import { getGlobalVariableNode } from './adapt'; import { VARIABLE_NODE_ID } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; +import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type'; +import { + formatEditorVariablePickerIcon, + getGuideModule, + splitGuideModule +} from '@fastgpt/global/core/workflow/utils'; +import { getSystemVariables } from '../app/utils'; +import { TFunction } from 'next-i18next'; export const nodeTemplate2FlowNode = ({ template, @@ -97,11 +105,13 @@ export const storeEdgesRenderEdge = ({ edge }: { edge: StoreEdgeItemType }) => { export const computedNodeInputReference = ({ nodeId, nodes, - edges + edges, + t }: { nodeId: string; nodes: FlowNodeItemType[]; edges: Edge[]; + t: TFunction; }) => { // get current node const node = nodes.find((item) => item.nodeId === nodeId); @@ -126,14 +136,7 @@ export const computedNodeInputReference = ({ }; findSourceNode(nodeId); - // add system config node - const systemConfigNode = nodes.find( - (item) => item.flowNodeType === FlowNodeTypeEnum.systemConfig - ); - - if (systemConfigNode) { - sourceNodes.unshift(systemConfigNode2VariableNode(systemConfigNode)); - } + sourceNodes.unshift(getGlobalVariableNode(nodes, t)); return sourceNodes; }; @@ -232,3 +235,17 @@ export const filterSensitiveNodesData = (nodes: StoreNodeItemType[]) => { }); return cloneNodes; }; + +/* get workflowStart output to global variables */ +export const getWorkflowGlobalVariables = ( + nodes: FlowNodeItemType[], + t: TFunction +): EditorVariablePickerType[] => { + const globalVariables = formatEditorVariablePickerIcon( + splitGuideModule(getGuideModule(nodes))?.variableModules || [] + ); + + const systemVariables = getSystemVariables(t); + + return [...globalVariables, ...systemVariables]; +}; diff --git a/projects/app/src/web/support/outLink/api.ts b/projects/app/src/web/support/outLink/api.ts index 6cf1763c8d2c..1bbf8b0114ca 100644 --- a/projects/app/src/web/support/outLink/api.ts +++ b/projects/app/src/web/support/outLink/api.ts @@ -1,26 +1,40 @@ import { GET, POST, DELETE } from '@/web/common/api/request'; import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d'; -/** - * create a shareChat - */ -export const createShareChat = ( - data: OutLinkEditType & { +// create a shareChat +export function createShareChat( + data: OutLinkEditType & { appId: string; type: OutLinkSchema['type']; } -) => POST(`/support/outLink/create`, data); +) { + return POST(`/support/outLink/create`, data); +} export const putShareChat = (data: OutLinkEditType) => POST(`/support/outLink/update`, data); -/** - * get shareChat - */ -export const getShareChatList = (appId: string) => - GET(`/support/outLink/list`, { appId }); +// get shareChat +export function getShareChatList(data: { appId: string; type: OutLinkSchema['type'] }) { + return GET[]>(`/support/outLink/list`, data); +} -/** - * delete a shareChat - */ -export const delShareChatById = (id: string) => DELETE(`/support/outLink/delete?id=${id}`); +// delete a shareChat +export function delShareChatById(id: string) { + return DELETE(`/support/outLink/delete?id=${id}`); +} + +// update a shareChat +export function updateShareChat(data: OutLinkEditType) { + return POST(`/support/outLink/update`, data); +} + +// /** +// * create a shareChat +// */ +// export const createWecomLinkChat = ( +// data: OutLinkConfigEditType & { +// appId: string; +// type: OutLinkSchema['type']; +// } +// ) => POST(`/support/outLink/create`, data); diff --git a/projects/app/src/web/support/user/hooks/useSendCode.ts b/projects/app/src/web/support/user/hooks/useSendCode.ts index f0970b1a35bb..fc0ed0da40a7 100644 --- a/projects/app/src/web/support/user/hooks/useSendCode.ts +++ b/projects/app/src/web/support/user/hooks/useSendCode.ts @@ -57,7 +57,7 @@ export const useSendCode = () => { } setCodeSending(false); }, - [codeCountDown, toast] + [codeCountDown, feConfigs?.googleClientVerKey, toast] ); return {