4.8-preview fix (#1324)

* feishu app release (#85)

* Revert "lafAccount add pat & re request when token invalid (#76)" (#77)

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

* perf: workflow ux

* system config

* feat: feishu app release

* chore: sovle the conflicts files; fix the feishu entry

* fix: rename Feishu interface to FeishuType

* fix: fix type problem in app.ts

* fix: type problem

* fix: style problem

---------

Co-authored-by: Archer <545436317@qq.com>

* perf: publish channel code

* change system variable position (#94)

* perf: workflow context

* perf: variable select

* hide publish

* perf: simple edit auto refresh

* perf: simple edit data refresh

* fix: target handle

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer 2024-04-29 11:13:10 +08:00 committed by GitHub
parent 5ca4049757
commit a0c1320d47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
89 changed files with 1794 additions and 1047 deletions

View File

@ -21,5 +21,7 @@ FastGPT workflow V2上线支持更加简洁的工作流模式。
2. 新增 - 工作流 Debug 模式,可以调试单个节点或者逐步调试工作流。 2. 新增 - 工作流 Debug 模式,可以调试单个节点或者逐步调试工作流。
3. 新增 - 定时执行应用。可轻松实现定时任务。 3. 新增 - 定时执行应用。可轻松实现定时任务。
4. 新增 - 插件自定义输入优化,可以渲染输入组件。 4. 新增 - 插件自定义输入优化,可以渲染输入组件。
5. 优化 - 工作流连线,可以四向连接,方便构建循环工作流。 6. 优化 - 工作流连线,可以四向连接,方便构建循环工作流。
6. 优化 - worker进程管理并将计算 Token 任务分配给 worker 进程。 7. 优化 - 工作流上下文传递,性能🚀。
8. 优化 - 简易模式,更新配置后自动更新调试框内容,无需保存。
9. 优化 - worker进程管理并将计算 Token 任务分配给 worker 进程。

View File

@ -125,6 +125,7 @@ export const appWorkflow2Form = ({ nodes }: { nodes: StoreNodeItemType[] }) => {
defaultAppForm.selectedTools.push({ defaultAppForm.selectedTools.push({
id: node.pluginId, id: node.pluginId,
pluginId: node.pluginId,
name: node.name, name: node.name,
avatar: node.avatar, avatar: node.avatar,
intro: node.intro || '', intro: node.intro || '',

View File

@ -1,5 +1,6 @@
export enum OutLinkTypeEnum { export enum PublishChannelEnum {
share = 'share', share = 'share',
iframe = 'iframe', iframe = 'iframe',
apikey = 'apikey' apikey = 'apikey',
feishu = 'feishu'
} }

View File

@ -1,31 +1,79 @@
import { AppSchema } from 'core/app/type'; 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<T = void> = {
_id: string; _id: string;
shareId: string; shareId: string;
teamId: string; teamId: string;
tmbId: string; tmbId: string;
appId: string; appId: string;
// teamId: Schema.Types.ObjectId;
// tmbId: Schema.Types.ObjectId;
// appId: Schema.Types.ObjectId;
name: string; name: string;
usagePoints: number; usagePoints: number;
lastTime: Date; lastTime: Date;
type: `${OutLinkTypeEnum}`; type: PublishChannelEnum;
// whether the response content is detailed
responseDetail: boolean; responseDetail: boolean;
// response when request
immediateResponse?: string;
// response when error or other situation
defaultResponse?: string;
limit?: { limit?: {
expiredTime?: Date; expiredTime?: Date;
// Questions per minute
QPM: number; QPM: number;
maxUsagePoints: number; maxUsagePoints: number;
// Verification message hook url
hookUrl?: string; hookUrl?: string;
}; };
app?: T;
}; };
// to handle MongoDB querying
export type OutLinkWithAppType = Omit<OutLinkSchema, 'appId'> & { export type OutLinkWithAppType = Omit<OutLinkSchema, 'appId'> & {
appId: AppSchema; appId: AppSchema;
}; };
export type OutLinkEditType = { // Edit the Outlink
export type OutLinkEditType<T = void> = {
_id?: string; _id?: string;
name: string; name: string;
responseDetail: OutLinkSchema['responseDetail']; responseDetail: OutLinkSchema<T>['responseDetail'];
limit: OutLinkSchema['limit']; // response when request
immediateResponse?: string;
// response when error or other situation
defaultResponse?: string;
limit?: OutLinkSchema<T>['limit'];
// config for specific platform
app?: T;
}; };

View File

@ -1,7 +1,6 @@
import { connectionMongo, type Model } from '../../common/mongo'; import { connectionMongo, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo; const { Schema, model, models } = connectionMongo;
import { OutLinkSchema as SchemaType } from '@fastgpt/global/support/outLink/type'; import { OutLinkSchema as SchemaType } from '@fastgpt/global/support/outLink/type';
import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant';
import { import {
TeamCollectionName, TeamCollectionName,
TeamMemberCollectionName TeamMemberCollectionName
@ -30,7 +29,7 @@ const OutLinkSchema = new Schema({
}, },
type: { type: {
type: String, type: String,
default: OutLinkTypeEnum.share required: true
}, },
name: { name: {
type: String, type: String,
@ -62,6 +61,26 @@ const OutLinkSchema = new Schema({
hookUrl: { hookUrl: {
type: String type: String
} }
},
app: {
appId: {
type: String
},
appSecret: {
type: String
},
encryptKey: {
type: String
},
verificationToken: {
type: String
}
},
immediateResponse: {
type: String
},
defaultResponse: {
type: String
} }
}); });

View File

@ -65,6 +65,7 @@ export const iconPaths = {
'core/app/headphones': () => import('./icons/core/app/headphones.svg'), 'core/app/headphones': () => import('./icons/core/app/headphones.svg'),
'core/app/logsLight': () => import('./icons/core/app/logsLight.svg'), 'core/app/logsLight': () => import('./icons/core/app/logsLight.svg'),
'core/app/markLight': () => import('./icons/core/app/markLight.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/questionGuide': () => import('./icons/core/app/questionGuide.svg'),
'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'), 'core/app/schedulePlan': () => import('./icons/core/app/schedulePlan.svg'),
'core/app/simpleMode/ai': () => import('./icons/core/app/simpleMode/ai.svg'), 'core/app/simpleMode/ai': () => import('./icons/core/app/simpleMode/ai.svg'),

View File

@ -0,0 +1,12 @@
<svg t="1714285522209" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5343"
width="128" height="128">
<path
d="M891.318857 340.845714c4.900571 0 9.728 0.292571 14.628572 0.804572a409.965714 409.965714 0 0 1 108.836571 30.061714c10.093714 4.534857 12.580571 8.192 3.949714 17.334857-24.868571 26.624-45.494857 57.051429-61.001143 89.965714-16.822857 35.328-35.108571 69.851429-52.297142 105.033143a225.28 225.28 0 0 1-52.150858 69.412572c-53.613714 48.493714-116.150857 68.973714-187.538285 59.099428-81.92-11.337143-159.451429-38.985143-232.740572-75.483428a143.506286 143.506286 0 0 1-10.459428-5.485715 5.339429 5.339429 0 0 1 0.292571-9.216l5.12-2.706285c59.245714-31.670857 108.836571-75.849143 156.525714-122.294857 20.187429-19.529143 39.497143-40.009143 59.904-59.318858A345.014857 345.014857 0 0 1 804.571429 352.256c13.165714-3.218286 26.550857-5.778286 39.789714-8.630857h0.585143l28.233143-2.56"
fill="#133C9A" p-id="5344"></path>
<path
d="M317.659429 913.846857c-8.996571-0.512-31.158857-3.584-33.865143-3.949714a536.429714 536.429714 0 0 1-165.083429-48.274286c-30.208-14.116571-59.245714-30.72-88.356571-46.957714-19.163429-10.678857-27.794286-27.282286-27.648-49.883429 0.585143-83.382857 0.585143-166.765714 0-250.148571C2.413714 461.019429 0.731429 407.405714 0 353.718857c0-4.754286 0.731429-9.508571 2.194286-13.897143 3.291429-9.728 9.947429-10.24 16.530285-3.949714 7.606857 7.314286 13.677714 16.237714 21.211429 23.405714 67.291429 66.413714 138.752 127.195429 218.770286 177.225143 45.056 28.891429 91.940571 54.710857 140.434285 77.385143 77.750857 35.328 157.549714 66.486857 241.078858 86.235429 73.874286 17.481143 145.627429 6.436571 205.458285-40.374858 18.285714-15.652571 27.282286-27.062857 48.932572-55.881142a359.862857 359.862857 0 0 1-37.376 72.850285c-13.897143 21.942857-45.348571 51.2-69.193143 74.093715-36.278857 35.108571-83.748571 63.561143-128.292572 87.552-48.566857 26.185143-99.035429 47.104-152.941714 58.514285-27.648 6.948571-67.584 14.848-81.334857 15.579429-2.413714-0.146286-10.678857 1.682286-14.848 1.389714-35.547429 2.633143-57.490286 3.657143-92.891429 0z"
fill="#3370FF" p-id="5345"></path>
<path
d="M165.083429 110.518857a52.443429 52.443429 0 0 1 7.460571 0c152.649143 0 304.128 2.486857 456.630857 2.486857 0.292571 0 0.585143 0 0.731429 0.219429 14.189714 12.361143 27.282286 25.746286 39.277714 40.155428 34.450286 34.230857 60.123429 93.622857 77.677714 129.755429 8.777143 25.014857 21.942857 48.859429 28.16 76.8v0.438857c-15.579429 5.046857-30.72 11.190857-45.348571 18.505143-44.178286 22.381714-64.219429 38.765714-100.790857 74.752-19.968 19.529143-37.010286 37.083429-63.488 62.098286a563.346286 563.346286 0 0 1-29.769143 26.916571c-7.021714-12.434286-125.732571-244.589714-364.251429-427.300571"
fill="#00D6B9" p-id="5346"></path>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,5 +1,8 @@
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
export type EditorVariablePickerType = { export type EditorVariablePickerType = {
key: string; key: string;
label: string; label: string;
icon?: string; icon?: string;
valueType?: WorkflowIOValueTypeEnum;
}; };

View File

@ -8,8 +8,8 @@
"@chakra-ui/react": "2.8.1", "@chakra-ui/react": "2.8.1",
"@chakra-ui/styled-system": "2.9.1", "@chakra-ui/styled-system": "2.9.1",
"@chakra-ui/system": "2.6.1", "@chakra-ui/system": "2.6.1",
"@emotion/react": "^11.11.1", "@emotion/react": "11.11.1",
"@emotion/styled": "^11.11.0", "@emotion/styled": "11.11.0",
"@fastgpt/global": "workspace:*", "@fastgpt/global": "workspace:*",
"@fingerprintjs/fingerprintjs": "^4.3.0", "@fingerprintjs/fingerprintjs": "^4.3.0",
"@lexical/react": "0.12.6", "@lexical/react": "0.12.6",

31
pnpm-lock.yaml generated
View File

@ -230,10 +230,10 @@ importers:
specifier: 2.6.1 specifier: 2.6.1
version: 2.6.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) version: 2.6.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@emotion/react': '@emotion/react':
specifier: ^11.11.1 specifier: 11.11.1
version: 11.11.1(@types/react@18.2.0)(react@18.2.0) version: 11.11.1(@types/react@18.2.0)(react@18.2.0)
'@emotion/styled': '@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) version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.0)(react@18.2.0)
'@fastgpt/global': '@fastgpt/global':
specifier: workspace:* specifier: workspace:*
@ -336,10 +336,10 @@ importers:
specifier: 2.6.1 specifier: 2.6.1
version: 2.6.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) version: 2.6.1(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0)
'@emotion/react': '@emotion/react':
specifier: ^11.11.1 specifier: 11.11.1
version: 11.11.1(@types/react@18.2.0)(react@18.2.0) version: 11.11.1(@types/react@18.2.0)(react@18.2.0)
'@emotion/styled': '@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) version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.0)(react@18.2.0)
'@fastgpt/global': '@fastgpt/global':
specifier: workspace:* specifier: workspace:*
@ -408,7 +408,7 @@ importers:
specifier: ^4.17.21 specifier: ^4.17.21
version: 4.17.21 version: 4.17.21
mermaid: mermaid:
specifier: 10.2.3 specifier: ^10.2.3
version: 10.2.3 version: 10.2.3
nanoid: nanoid:
specifier: ^4.0.1 specifier: ^4.0.1
@ -467,6 +467,9 @@ importers:
sass: sass:
specifier: ^1.58.3 specifier: ^1.58.3
version: 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: zustand:
specifier: ^4.3.5 specifier: ^4.3.5
version: 4.3.5(immer@9.0.19)(react@18.2.0) version: 4.3.5(immer@9.0.19)(react@18.2.0)
@ -12158,6 +12161,24 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: false 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): /use-sidecar@1.1.2(@types/react@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
engines: {node: '>=10'} engines: {node: '>=10'}

View File

@ -76,7 +76,7 @@ const nextConfig = {
return config; return config;
}, },
transpilePackages: ['@fastgpt/*'], transpilePackages: ['@fastgpt/*', 'ahooks'],
experimental: { experimental: {
// 外部包独立打包 // 外部包独立打包
serverComponentsExternalPackages: ['mongoose', 'pg'], serverComponentsExternalPackages: ['mongoose', 'pg'],

View File

@ -16,13 +16,14 @@
"@chakra-ui/react": "2.8.1", "@chakra-ui/react": "2.8.1",
"@chakra-ui/styled-system": "2.9.1", "@chakra-ui/styled-system": "2.9.1",
"@chakra-ui/system": "2.6.1", "@chakra-ui/system": "2.6.1",
"@emotion/react": "^11.11.1", "@emotion/react": "11.11.1",
"@emotion/styled": "^11.11.0", "@emotion/styled": "11.11.0",
"@fastgpt/global": "workspace:*", "@fastgpt/global": "workspace:*",
"@fastgpt/plugins": "workspace:*", "@fastgpt/plugins": "workspace:*",
"@fastgpt/service": "workspace:*", "@fastgpt/service": "workspace:*",
"@fastgpt/web": "workspace:*", "@fastgpt/web": "workspace:*",
"@fortaine/fetch-event-source": "^3.0.6", "@fortaine/fetch-event-source": "^3.0.6",
"@node-rs/jieba": "1.10.0",
"@tanstack/react-query": "^4.24.10", "@tanstack/react-query": "^4.24.10",
"@types/nprogress": "^0.2.0", "@types/nprogress": "^0.2.0",
"ahooks": "^3.7.11", "ahooks": "^3.7.11",
@ -39,10 +40,11 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mermaid": "10.2.3", "mermaid": "^10.2.3",
"nanoid": "^4.0.1", "nanoid": "^4.0.1",
"next": "13.5.2", "next": "13.5.2",
"next-i18next": "15.2.0", "next-i18next": "15.2.0",
"nextjs-node-loader": "^1.1.5",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"react": "18.2.0", "react": "18.2.0",
"react-day-picker": "^8.7.1", "react-day-picker": "^8.7.1",
@ -58,9 +60,8 @@
"remark-math": "^5.1.1", "remark-math": "^5.1.1",
"request-ip": "^3.3.0", "request-ip": "^3.3.0",
"sass": "^1.58.3", "sass": "^1.58.3",
"zustand": "^4.3.5", "use-context-selector": "^1.4.4",
"nextjs-node-loader": "^1.1.5", "zustand": "^4.3.5"
"@node-rs/jieba": "1.10.0"
}, },
"devDependencies": { "devDependencies": {
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
@ -76,6 +77,7 @@
"@types/request-ip": "^0.0.37", "@types/request-ip": "^0.0.37",
"eslint": "8.34.0", "eslint": "8.34.0",
"eslint-config-next": "13.1.6", "eslint-config-next": "13.1.6",
"nextjs-node-loader": "^1.1.5",
"typescript": "4.9.5" "typescript": "4.9.5"
} }
} }

View File

@ -153,6 +153,7 @@
"Submit failed": "Submit failed", "Submit failed": "Submit failed",
"Submit success": "Update Success", "Submit success": "Update Success",
"Sync success": "", "Sync success": "",
"System Output": "System Output",
"System version": "System version", "System version": "System version",
"Team": "Team", "Team": "Team",
"Team Tags Set": "Team Tags", "Team Tags Set": "Team Tags",
@ -280,6 +281,7 @@
"App intro": "App intro", "App intro": "App intro",
"App params config": "App Config", "App params config": "App Config",
"Auto Save time": "Auto-saved: {{time}}", "Auto Save time": "Auto-saved: {{time}}",
"Change to simple mode": "Switch easy mode",
"Chat Variable": "", "Chat Variable": "",
"Config schedule plan": "Config schedule config", "Config schedule plan": "Config schedule config",
"Config whisper": "Config whisper", "Config whisper": "Config whisper",
@ -374,6 +376,11 @@
"Show History": "Show History", "Show History": "Show History",
"Web Link": "Web Link" "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": { "schedule": {
"Default prompt": "Default prompt", "Default prompt": "Default prompt",
"Default prompt placeholder": "Default problem when executing the application", "Default prompt placeholder": "Default problem when executing the application",
@ -957,6 +964,7 @@
"anyInput": "Any input", "anyInput": "Any input",
"chat history": "chat history", "chat history": "chat history",
"switch": "Trigger", "switch": "Trigger",
"system params": "System params",
"textEditor textarea": "Text edit", "textEditor textarea": "Text edit",
"user question": "User question" "user question": "User question"
}, },
@ -1108,6 +1116,7 @@
"Stop debug": "Stop", "Stop debug": "Stop",
"Success": "Running success", "Success": "Running success",
"Value type": "Type", "Value type": "Type",
"Variable outputs": "Variables",
"chat": { "chat": {
"Quote prompt": "Quote prompt" "Quote prompt": "Quote prompt"
}, },

View File

@ -153,6 +153,7 @@
"Submit failed": "提交失败", "Submit failed": "提交失败",
"Submit success": "提交成功", "Submit success": "提交成功",
"Sync success": "同步成功", "Sync success": "同步成功",
"System Output": "系统输出",
"System version": "系统版本", "System version": "系统版本",
"Team": "团队", "Team": "团队",
"Team Tags Set": "标签", "Team Tags Set": "标签",
@ -280,6 +281,7 @@
"App intro": "应用介绍", "App intro": "应用介绍",
"App params config": "应用配置", "App params config": "应用配置",
"Auto Save time": "自动保存: {{time}}", "Auto Save time": "自动保存: {{time}}",
"Change to simple mode": "切换简易模式",
"Chat Variable": "对话框变量", "Chat Variable": "对话框变量",
"Config schedule plan": "配置定时执行", "Config schedule plan": "配置定时执行",
"Config whisper": "配置语音输入", "Config whisper": "配置语音输入",
@ -374,6 +376,11 @@
"Show History": "展示历史对话", "Show History": "展示历史对话",
"Web Link": "网络链接" "Web Link": "网络链接"
}, },
"publish": {
"Fei Shu Bot Desc": "接入到飞书机器人中",
"Fei shu bot": "飞书",
"Fei shu bot publish": "发布到飞书机器人"
},
"schedule": { "schedule": {
"Default prompt": "默认问题", "Default prompt": "默认问题",
"Default prompt placeholder": "执行应用时的默认问题", "Default prompt placeholder": "执行应用时的默认问题",
@ -638,7 +645,8 @@
"success": "开始同步" "success": "开始同步"
} }
}, },
"training": {} "training": {
}
}, },
"data": { "data": {
"Auxiliary Data": "辅助数据", "Auxiliary Data": "辅助数据",
@ -958,6 +966,7 @@
"anyInput": "", "anyInput": "",
"chat history": "聊天记录", "chat history": "聊天记录",
"switch": "触发器", "switch": "触发器",
"system params": "系统参数",
"textEditor textarea": "文本编辑", "textEditor textarea": "文本编辑",
"user question": "用户问题" "user question": "用户问题"
}, },
@ -1109,6 +1118,7 @@
"Stop debug": "停止调试", "Stop debug": "停止调试",
"Success": "运行成功", "Success": "运行成功",
"Value type": "数据类型", "Value type": "数据类型",
"Variable outputs": "全局变量",
"chat": { "chat": {
"Quote prompt": "引用提示词" "Quote prompt": "引用提示词"
}, },

View File

@ -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 (
<>
<InputGroup>
<Input {...props} type={show ? 'text' : 'password'} />
<InputRightElement width="4.5rem">
<Button h="1.75rem" size="sm" onClick={() => setShow(!show)}>
{show ? <ViewOffIcon /> : <ViewIcon />}
</Button>
</InputRightElement>
</InputGroup>
</>
);
}
export default HiddenInput;

View File

@ -13,7 +13,6 @@ import {
Switch, Switch,
Input, Input,
FormControl, FormControl,
Image,
Table, Table,
Thead, Thead,
Tbody, Tbody,
@ -21,7 +20,6 @@ import {
Th, Th,
Td, Td,
TableContainer, TableContainer,
BoxProps,
useDisclosure useDisclosure
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons'; import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';

View File

@ -3,7 +3,8 @@ import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useFlowProviderStore } from './FlowProvider'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context';
type Props = { type Props = {
onClose: () => void; onClose: () => void;
@ -12,7 +13,8 @@ type Props = {
const ImportSettings = ({ onClose }: Props) => { const ImportSettings = ({ onClose }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const { setNodes, setEdges, initData } = useFlowProviderStore();
const initData = useContextSelector(WorkflowContext, (v) => v.initData);
const [value, setValue] = useState(''); const [value, setValue] = useState('');
return ( return (

View File

@ -1,14 +1,5 @@
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { import { Box, Flex, IconButton, Input, InputGroup, InputLeftElement, css } from '@chakra-ui/react';
Box,
Card,
Flex,
IconButton,
Input,
InputGroup,
InputLeftElement,
css
} from '@chakra-ui/react';
import type { import type {
FlowNodeTemplateType, FlowNodeTemplateType,
nodeTemplateListType nodeTemplateListType
@ -16,8 +7,6 @@ import type {
import { useViewport, XYPosition } from 'reactflow'; import { useViewport, XYPosition } from 'reactflow';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import { useFlowProviderStore } from './FlowProvider';
import { customAlphabet } from 'nanoid';
import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils'; import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; 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 { useQuery } from '@tanstack/react-query';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context';
import { useCreation } from 'ahooks';
type ModuleTemplateListProps = { type ModuleTemplateListProps = {
isOpen: boolean; isOpen: boolean;
@ -62,7 +54,9 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
const [currentParent, setCurrentParent] = useState<RenderListProps['currentParent']>(); const [currentParent, setCurrentParent] = useState<RenderListProps['currentParent']>();
const [searchKey, setSearchKey] = useState(''); const [searchKey, setSearchKey] = useState('');
const { feConfigs } = useSystemStore(); 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 { const {
systemNodeTemplates, systemNodeTemplates,
@ -72,12 +66,12 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
} = useWorkflowStore(); } = useWorkflowStore();
const [templateType, setTemplateType] = useState(TemplateTypeEnum.basic); const [templateType, setTemplateType] = useState(TemplateTypeEnum.basic);
const templatesString = useMemo(() => { const templates = useCreation(() => {
const map = { const map = {
[TemplateTypeEnum.basic]: basicNodeTemplates.filter((item) => { [TemplateTypeEnum.basic]: basicNodeTemplates.filter((item) => {
// unique node filter // unique node filter
if (item.unique) { if (item.unique) {
const nodeExist = nodes.some((node) => node.data.flowNodeType === item.flowNodeType); const nodeExist = nodeList.some((node) => node.flowNodeType === item.flowNodeType);
if (nodeExist) { if (nodeExist) {
return false; return false;
} }
@ -97,12 +91,12 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
searchKey ? item.pluginType !== PluginTypeEnum.folder : true searchKey ? item.pluginType !== PluginTypeEnum.folder : true
) )
}; };
return JSON.stringify(map[templateType]); return map[templateType];
}, [ }, [
basicNodeTemplates, basicNodeTemplates,
feConfigs.lafEnv, feConfigs.lafEnv,
hasToolNode, hasToolNode,
nodes, nodeList,
searchKey, searchKey,
systemNodeTemplates, systemNodeTemplates,
teamPluginNodeTemplates, teamPluginNodeTemplates,
@ -132,8 +126,6 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
}) })
); );
const Render = useMemo(() => {
const parseTemplates = JSON.parse(templatesString) as FlowNodeTemplateType[];
return ( return (
<> <>
<Box <Box
@ -240,7 +232,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
)} )}
</Box> </Box>
<RenderList <RenderList
templates={parseTemplates} templates={templates}
onClose={onClose} onClose={onClose}
currentParent={currentParent} currentParent={currentParent}
setCurrentParent={setCurrentParent} setCurrentParent={setCurrentParent}
@ -248,19 +240,6 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
</Flex> </Flex>
</> </>
); );
}, [
currentParent,
isOpen,
onChangeTab,
onClose,
router,
searchKey,
t,
templateType,
templatesString
]);
return Render;
}; };
export default React.memo(NodeTemplatesModal); export default React.memo(NodeTemplatesModal);
@ -276,7 +255,8 @@ const RenderList = React.memo(function RenderList({
const { x, y, zoom } = useViewport(); const { x, y, zoom } = useViewport();
const { setLoading } = useSystemStore(); const { setLoading } = useSystemStore();
const { toast } = useToast(); const { toast } = useToast();
const { reactFlowWrapper, setNodes } = useFlowProviderStore(); const reactFlowWrapper = useContextSelector(WorkflowContext, (v) => v.reactFlowWrapper);
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
const formatTemplates = useMemo<nodeTemplateListType>(() => { const formatTemplates = useMemo<nodeTemplateListType>(() => {
const copy: nodeTemplateListType = JSON.parse(JSON.stringify(moduleTemplatesList)); const copy: nodeTemplateListType = JSON.parse(JSON.stringify(moduleTemplatesList));

View File

@ -1,12 +1,16 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { BezierEdge, getBezierPath, EdgeLabelRenderer, EdgeProps } from 'reactflow'; import { BezierEdge, getBezierPath, EdgeLabelRenderer, EdgeProps } from 'reactflow';
import { useFlowProviderStore } from '../FlowProvider';
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/workflow/constants';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const ButtonEdge = (props: EdgeProps) => { 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 { const {
id, id,
sourceX, sourceX,

View File

@ -2,7 +2,6 @@ import { storeNodes2RuntimeNodes } from '@fastgpt/global/core/workflow/runtime/u
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type'; import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { getWorkflowStore, useFlowProviderStore } from '../FlowProvider';
import { checkWorkflowNodeAndConnection } from '@/web/core/workflow/utils'; import { checkWorkflowNodeAndConnection } from '@/web/core/workflow/utils';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
@ -25,6 +24,8 @@ import {
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { checkInputIsReference } from '@fastgpt/global/core/workflow/utils'; import { checkInputIsReference } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '../../context';
const MyRightDrawer = dynamic( const MyRightDrawer = dynamic(
() => import('@fastgpt/web/components/common/MyDrawer/MyRightDrawer') () => import('@fastgpt/web/components/common/MyDrawer/MyRightDrawer')
@ -35,7 +36,10 @@ export const useDebug = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); 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<string>(); const [runtimeNodeId, setRuntimeNodeId] = useState<string>();
const [runtimeNodes, setRuntimeNodes] = useState<RuntimeNodeItemType[]>(); const [runtimeNodes, setRuntimeNodes] = useState<RuntimeNodeItemType[]>();

View File

@ -1,14 +1,15 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { getWorkflowStore, useFlowProviderStore } from '../FlowProvider';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useCopyData } from '@/web/common/hooks/useCopyData'; import { useCopyData } from '@/web/common/hooks/useCopyData';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { Node } from 'reactflow'; import { Node } from 'reactflow';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type'; import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '../../context';
export const useKeyboard = () => { export const useKeyboard = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { setNodes } = useFlowProviderStore(); const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
const { copyData } = useCopyData(); const { copyData } = useCopyData();
const [isDowningCtrl, setIsDowningCtrl] = useState(false); const [isDowningCtrl, setIsDowningCtrl] = useState(false);
@ -29,6 +30,7 @@ export const useKeyboard = () => {
const onCopy = useCallback(async () => { const onCopy = useCallback(async () => {
if (hasInputtingElement()) return; if (hasInputtingElement()) return;
const { nodes } = await getWorkflowStore(); const { nodes } = await getWorkflowStore();
const selectedNodes = nodes.filter( const selectedNodes = nodes.filter(
(node) => node.selected && !node.data?.isError && node.data?.unique !== true (node) => node.selected && !node.data?.isError && node.data?.unique !== true
); );

View File

@ -21,7 +21,6 @@ import dynamic from 'next/dynamic';
import ButtonEdge from './components/ButtonEdge'; import ButtonEdge from './components/ButtonEdge';
import NodeTemplatesModal from './NodeTemplatesModal'; import NodeTemplatesModal from './NodeTemplatesModal';
import { useFlowProviderStore } from './FlowProvider';
import 'reactflow/dist/style.css'; import 'reactflow/dist/style.css';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
@ -32,6 +31,8 @@ import MyTooltip from '@/components/MyTooltip';
import { connectionLineStyle, defaultEdgeOptions } from '../constants'; import { connectionLineStyle, defaultEdgeOptions } from '../constants';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useKeyboard } from './hooks/useKeyboard'; import { useKeyboard } from './hooks/useKeyboard';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context';
const NodeSimple = dynamic(() => import('./nodes/NodeSimple')); const NodeSimple = dynamic(() => import('./nodes/NodeSimple'));
const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = { const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
@ -71,16 +72,13 @@ const Container = React.memo(function Container() {
}); });
const { isDowningCtrl } = useKeyboard(); const { isDowningCtrl } = useKeyboard();
const setConnectingEdge = useContextSelector(WorkflowContext, (v) => v.setConnectingEdge);
const { const reactFlowWrapper = useContextSelector(WorkflowContext, (v) => v.reactFlowWrapper);
reactFlowWrapper, const nodes = useContextSelector(WorkflowContext, (v) => v.nodes);
nodes, const onNodesChange = useContextSelector(WorkflowContext, (v) => v.onNodesChange);
onNodesChange, const edges = useContextSelector(WorkflowContext, (v) => v.edges);
edges, const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
setEdges, const onEdgesChange = useContextSelector(WorkflowContext, (v) => v.onEdgesChange);
onEdgesChange,
setConnectingEdge
} = useFlowProviderStore();
/* node */ /* node */
const handleNodesChange = useCallback( const handleNodesChange = useCallback(

View File

@ -4,15 +4,16 @@ import NodeCard from './render/NodeCard';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import Container from '../components/Container'; import Container from '../components/Container';
import RenderInput from './render/RenderInput'; import RenderInput from './render/RenderInput';
import { useFlowProviderStore } from '../FlowProvider';
import RenderToolInput from './render/RenderToolInput'; import RenderToolInput from './render/RenderToolInput';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import IOTitle from '../components/IOTitle'; import IOTitle from '../components/IOTitle';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const NodeAnswer = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodeAnswer = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { nodeId, inputs, outputs } = data; const { nodeId, inputs } = data;
const { splitToolInputs } = useFlowProviderStore(); const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId); const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);
return ( return (

View File

@ -11,16 +11,16 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import { useFlowProviderStore } from '../FlowProvider';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { SourceHandle } from './render/Handle'; import { SourceHandle } from './render/Handle';
import IOTitle from '../components/IOTitle';
import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const NodeCQNode = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodeCQNode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { nodeId, inputs } = data; const { nodeId, inputs } = data;
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const CustomComponent = useMemo( const CustomComponent = useMemo(
() => ({ () => ({

View File

@ -9,19 +9,21 @@ import { useTranslation } from 'next-i18next';
import { SmallAddIcon } from '@chakra-ui/icons'; import { SmallAddIcon } from '@chakra-ui/icons';
import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { getOneQuoteInputTemplate } from '@fastgpt/global/core/workflow/template/system/datasetConcat'; import { getOneQuoteInputTemplate } from '@fastgpt/global/core/workflow/template/system/datasetConcat';
import { useFlowProviderStore } from '../FlowProvider';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import MySlider from '@/components/Slider'; import MySlider from '@/components/Slider';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import RenderOutput from './render/RenderOutput'; import RenderOutput from './render/RenderOutput';
import IOTitle from '../components/IOTitle'; import IOTitle from '../components/IOTitle';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { llmModelList } = useSystemStore(); const { llmModelList } = useSystemStore();
const { nodeList, onChangeNode } = useFlowProviderStore();
const { nodeId, inputs, outputs } = data; const { nodeId, inputs, outputs } = data;
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const quotes = useMemo( const quotes = useMemo(
() => inputs.filter((item) => item.valueType === WorkflowIOValueTypeEnum.datasetQuote), () => inputs.filter((item) => item.valueType === WorkflowIOValueTypeEnum.datasetQuote),

View File

@ -26,7 +26,6 @@ import ExtractFieldModal, { defaultField } from './ExtractFieldModal';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { useFlowProviderStore } from '../../FlowProvider';
import RenderToolInput from '../render/RenderToolInput'; import RenderToolInput from '../render/RenderToolInput';
import { import {
FlowNodeInputItemType, FlowNodeInputItemType,
@ -34,12 +33,17 @@ import {
} from '@fastgpt/global/core/workflow/type/io.d'; } from '@fastgpt/global/core/workflow/type/io.d';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import IOTitle from '../../components/IOTitle'; import IOTitle from '../../components/IOTitle';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => { const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
const { inputs, outputs, nodeId } = data; const { inputs, outputs, nodeId } = data;
const { splitToolInputs, onChangeNode } = useFlowProviderStore();
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);
const { t } = useTranslation(); 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<ContextExtractAgentItemType>(); const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
const CustomComponent = useMemo( const CustomComponent = useMemo(

View File

@ -7,7 +7,8 @@ import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import parse from '@bany/curl-to-json'; 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'; type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
const methodMap: { [K in RequestMethod]: string } = { const methodMap: { [K in RequestMethod]: string } = {
@ -28,7 +29,8 @@ const CurlImportModal = ({
onClose: () => void; onClose: () => void;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { register, handleSubmit } = useForm({ const { register, handleSubmit } = useForm({
defaultValues: { defaultValues: {
curlContent: '' curlContent: ''

View File

@ -20,7 +20,6 @@ import {
useDisclosure useDisclosure
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useFlowProviderStore } from '../../FlowProvider';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import Tabs from '@/components/Tabs'; import Tabs from '@/components/Tabs';
import MyIcon from '@fastgpt/web/components/common/Icon'; 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 RenderToolInput from '../render/RenderToolInput';
import IOTitle from '../../components/IOTitle'; import IOTitle from '../../components/IOTitle';
import { getSystemVariables } from '@/web/core/app/utils'; 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')); const CurlImportModal = dynamic(() => import('./CurlImportModal'));
export const HttpHeaders = [ export const HttpHeaders = [
@ -105,7 +107,7 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const [_, startSts] = useTransition(); const [_, startSts] = useTransition();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { isOpen: isOpenCurl, onOpen: onOpenCurl, onClose: onCloseCurl } = useDisclosure(); const { isOpen: isOpenCurl, onOpen: onOpenCurl, onClose: onCloseCurl } = useDisclosure();
@ -277,7 +279,7 @@ export function RenderHttpProps({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedTab, setSelectedTab] = useState(TabEnum.params); 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 requestMethods = inputs.find((item) => item.key === NodeInputKeyEnum.httpMethod)?.value;
const params = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams); const params = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams);
@ -289,11 +291,7 @@ export function RenderHttpProps({
// get variable // get variable
const variables = useMemo(() => { const variables = useMemo(() => {
const globalVariables = formatEditorVariablePickerIcon( const globalVariables = getWorkflowGlobalVariables(nodeList, t);
splitGuideModule(getGuideModule(nodeList))?.variableModules || []
);
const systemVariables = getSystemVariables(t);
const moduleVariables = formatEditorVariablePickerIcon( const moduleVariables = formatEditorVariablePickerIcon(
inputs inputs
@ -304,7 +302,7 @@ export function RenderHttpProps({
})) }))
); );
return [...moduleVariables, ...globalVariables, ...systemVariables]; return [...moduleVariables, ...globalVariables];
}, [inputs, nodeList, t]); }, [inputs, nodeList, t]);
const variableText = useMemo(() => { const variableText = useMemo(() => {
@ -407,7 +405,7 @@ const RenderForm = ({
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [list, setList] = useState<PropsArrType[]>(input.value || []); const [list, setList] = useState<PropsArrType[]>(input.value || []);
const [updateTrigger, setUpdateTrigger] = useState(false); const [updateTrigger, setUpdateTrigger] = useState(false);
@ -601,7 +599,7 @@ const RenderJson = ({
variables: EditorVariablePickerType[]; variables: EditorVariablePickerType[];
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [_, startSts] = useTransition(); const [_, startSts] = useTransition();
const Render = useMemo(() => { const Render = useMemo(() => {
@ -650,7 +648,7 @@ const RenderPropsItem = ({ text, num }: { text: string; num: number }) => {
const NodeHttp = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodeHttp = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { nodeId, inputs, outputs } = data; const { nodeId, inputs, outputs } = data;
const { splitToolInputs } = useFlowProviderStore(); const splitToolInputs = useContextSelector(WorkflowContext, (v) => v.splitToolInputs);
const { toolInputs, commonInputs, isTool } = splitToolInputs(inputs, nodeId); const { toolInputs, commonInputs, isTool } = splitToolInputs(inputs, nodeId);
const CustomComponents = useMemo( const CustomComponents = useMemo(

View File

@ -1,7 +1,6 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import NodeCard from './render/NodeCard'; import NodeCard from './render/NodeCard';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../FlowProvider';
import { Box, Button, Flex, background } from '@chakra-ui/react'; import { Box, Button, Flex, background } from '@chakra-ui/react';
import { SmallAddIcon } from '@chakra-ui/icons'; import { SmallAddIcon } from '@chakra-ui/icons';
import MyIcon from '@fastgpt/web/components/common/Icon'; 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 { stringConditionList } from '@fastgpt/global/core/workflow/template/system/ifElse/constant';
import MySelect from '@fastgpt/web/components/common/MySelect'; import MySelect from '@fastgpt/web/components/common/MySelect';
import MyInput from '@/components/MyInput'; import MyInput from '@/components/MyInput';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const NodeIfElse = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodeIfElse = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { nodeId, inputs = [], outputs } = data; const { nodeId, inputs = [], outputs } = data;
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const condition = useMemo( const condition = useMemo(
() => () =>
@ -274,7 +275,7 @@ const ConditionSelect = ({
variable?: ReferenceValueProps; variable?: ReferenceValueProps;
onSelect: (e: VariableConditionEnum) => void; onSelect: (e: VariableConditionEnum) => void;
}) => { }) => {
const { nodeList } = useFlowProviderStore(); const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
// get condition type // get condition type
const valueType = useMemo(() => { const valueType = useMemo(() => {
@ -337,7 +338,7 @@ const ConditionValueInput = ({
condition?: VariableConditionEnum; condition?: VariableConditionEnum;
onChange: (e: string) => void; onChange: (e: string) => void;
}) => { }) => {
const { nodeList } = useFlowProviderStore(); const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
// get value type // get value type
const valueType = useMemo(() => { const valueType = useMemo(() => {

View File

@ -5,7 +5,6 @@ import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import Container from '../components/Container'; import Container from '../components/Container';
import { Box, Button, Center, Flex, useDisclosure } from '@chakra-ui/react'; import { Box, Button, Center, Flex, useDisclosure } from '@chakra-ui/react';
import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useFlowProviderStore } from '../FlowProvider';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { getLafAppDetail } from '@/web/support/laf/api'; import { getLafAppDetail } from '@/web/support/laf/api';
import MySelect from '@fastgpt/web/components/common/MySelect'; import MySelect from '@fastgpt/web/components/common/MySelect';
@ -32,6 +31,8 @@ import {
} from '@fastgpt/global/core/workflow/type/io'; } from '@fastgpt/global/core/workflow/type/io';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import IOTitle from '../components/IOTitle'; import IOTitle from '../components/IOTitle';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccountModal')); const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccountModal'));
@ -42,7 +43,7 @@ const NodeLaf = (props: NodeProps<FlowNodeItemType>) => {
const { data, selected } = props; const { data, selected } = props;
const { nodeId, inputs, outputs } = data; const { nodeId, inputs, outputs } = data;
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const requestUrl = inputs.find((item) => item.key === NodeInputKeyEnum.httpReqUrl); const requestUrl = inputs.find((item) => item.key === NodeInputKeyEnum.httpReqUrl);
@ -293,7 +294,7 @@ const ConfigLaf = () => {
const RenderIO = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const RenderIO = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { nodeId, inputs, outputs } = data; const { nodeId, inputs, outputs } = data;
const { splitToolInputs } = useFlowProviderStore(); const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const { commonInputs, toolInputs, isTool } = splitToolInputs(inputs, nodeId); const { commonInputs, toolInputs, isTool } = splitToolInputs(inputs, nodeId);
return ( return (

View File

@ -2,7 +2,6 @@ import React, { useMemo, useState } from 'react';
import { NodeProps } from 'reactflow'; import { NodeProps } from 'reactflow';
import NodeCard from './render/NodeCard'; import NodeCard from './render/NodeCard';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import dynamic from 'next/dynamic';
import { Box, Button, Flex } from '@chakra-ui/react'; import { Box, Button, Flex } from '@chakra-ui/react';
import { SmallAddIcon } from '@chakra-ui/icons'; import { SmallAddIcon } from '@chakra-ui/icons';
import { import {
@ -17,7 +16,6 @@ import type {
EditNodeFieldType EditNodeFieldType
} from '@fastgpt/global/core/workflow/node/type.d'; } from '@fastgpt/global/core/workflow/node/type.d';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../FlowProvider';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { import {
FlowNodeInputMap, FlowNodeInputMap,
@ -26,6 +24,8 @@ import {
} from '@fastgpt/global/core/workflow/node/constant'; } from '@fastgpt/global/core/workflow/node/constant';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType'; import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import VariableTable from '../nodes/render/VariableTable'; import VariableTable from '../nodes/render/VariableTable';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const defaultCreateField: EditNodeFieldType = { const defaultCreateField: EditNodeFieldType = {
label: '', label: '',
@ -50,7 +50,7 @@ const NodePluginInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { nodeId, inputs, outputs } = data; const { nodeId, inputs, outputs } = data;
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [createField, setCreateField] = useState<EditNodeFieldType>(); const [createField, setCreateField] = useState<EditNodeFieldType>();
const [editField, setEditField] = useState<EditNodeFieldType>(); const [editField, setEditField] = useState<EditNodeFieldType>();

View File

@ -11,8 +11,9 @@ import { EditInputFieldMapType, EditNodeFieldType } from '@fastgpt/global/core/w
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../FlowProvider';
import RenderInput from './render/RenderInput'; import RenderInput from './render/RenderInput';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const FieldEditModal = dynamic(() => import('./render/FieldEditModal')); const FieldEditModal = dynamic(() => import('./render/FieldEditModal'));
@ -31,7 +32,7 @@ const createEditField: EditInputFieldMapType = {
const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => { const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { nodeId, inputs } = data; const { nodeId, inputs } = data;
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [createField, setCreateField] = useState<EditNodeFieldType>(); const [createField, setCreateField] = useState<EditNodeFieldType>();

View File

@ -7,9 +7,10 @@ import RenderInput from './render/RenderInput';
import RenderOutput from './render/RenderOutput'; import RenderOutput from './render/RenderOutput';
import RenderToolInput from './render/RenderToolInput'; import RenderToolInput from './render/RenderToolInput';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../FlowProvider';
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import IOTitle from '../components/IOTitle'; import IOTitle from '../components/IOTitle';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
const NodeSimple = ({ const NodeSimple = ({
data, data,
@ -18,7 +19,7 @@ const NodeSimple = ({
maxW maxW
}: NodeProps<FlowNodeItemType> & { minW?: string | number; maxW?: string | number }) => { }: NodeProps<FlowNodeItemType> & { minW?: string | number; maxW?: string | number }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { splitToolInputs } = useFlowProviderStore(); const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const { nodeId, inputs, outputs } = data; const { nodeId, inputs, outputs } = data;
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId); const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);

View File

@ -1,24 +1,31 @@
import React, { useCallback, useMemo, useTransition } from 'react'; import React, { useMemo, useTransition } from 'react';
import { NodeProps } from 'reactflow'; import { NodeProps } from 'reactflow';
import { Box, Flex, Textarea, useTheme } from '@chakra-ui/react'; import { Box, Flex, Textarea, useTheme } from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { FlowNodeItemType, StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; 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 { 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 QGSwitch from '@/components/core/app/QGSwitch';
import TTSSelect from '@/components/core/app/TTSSelect'; import TTSSelect from '@/components/core/app/TTSSelect';
import WhisperConfig from '@/components/core/app/WhisperConfig'; import WhisperConfig from '@/components/core/app/WhisperConfig';
import { splitGuideModule } from '@fastgpt/global/core/workflow/utils'; import { splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { TTSTypeEnum } from '@/constants/app'; import { TTSTypeEnum } from '@/constants/app';
import { useFlowProviderStore } from '../FlowProvider';
import VariableEdit from '../../../app/VariableEdit';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import NodeCard from './render/NodeCard'; import NodeCard from './render/NodeCard';
import ScheduledTriggerConfig from '@/components/core/app/ScheduledTriggerConfig'; 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<FlowNodeItemType>) => { const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const theme = useTheme(); const theme = useTheme();
@ -38,10 +45,10 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
> >
<Box px={4} py={'10px'} position={'relative'} borderRadius={'md'} className="nodrag"> <Box px={4} py={'10px'} position={'relative'} borderRadius={'md'} className="nodrag">
<WelcomeText data={data} /> <WelcomeText data={data} />
<Box pt={4} pb={2}> <Box pt={4}>
<ChatStartVariable data={data} /> <ChatStartVariable data={data} />
</Box> </Box>
<Box pt={3} borderTop={theme.borders.base}> <Box mt={3} pt={3} borderTop={theme.borders.base}>
<TTSGuide data={data} /> <TTSGuide data={data} />
</Box> </Box>
<Box mt={3} pt={3} borderTop={theme.borders.base}> <Box mt={3} pt={3} borderTop={theme.borders.base}>
@ -65,7 +72,7 @@ function WelcomeText({ data }: { data: FlowNodeItemType }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { inputs, nodeId } = data; const { inputs, nodeId } = data;
const [, startTst] = useTransition(); const [, startTst] = useTransition();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const welcomeText = inputs.find((item) => item.key === NodeInputKeyEnum.welcomeText); const welcomeText = inputs.find((item) => item.key === NodeInputKeyEnum.welcomeText);
@ -108,7 +115,7 @@ function WelcomeText({ data }: { data: FlowNodeItemType }) {
function ChatStartVariable({ data }: { data: FlowNodeItemType }) { function ChatStartVariable({ data }: { data: FlowNodeItemType }) {
const { inputs, nodeId } = data; const { inputs, nodeId } = data;
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const variables = useMemo( const variables = useMemo(
() => () =>
@ -117,8 +124,8 @@ function ChatStartVariable({ data }: { data: FlowNodeItemType }) {
[inputs] [inputs]
); );
const updateVariables = useCallback( const updateVariables = useMemoizedFn((value: VariableItemType[]) => {
(value: VariableItemType[]) => { // update system config node
onChangeNode({ onChangeNode({
nodeId, nodeId,
key: NodeInputKeyEnum.variables, key: NodeInputKeyEnum.variables,
@ -128,16 +135,14 @@ function ChatStartVariable({ data }: { data: FlowNodeItemType }) {
value value
} }
}); });
}, });
[inputs, nodeId, onChangeNode]
);
return <VariableEdit variables={variables} onChange={(e) => updateVariables(e)} />; return <VariableEdit variables={variables} onChange={(e) => updateVariables(e)} />;
} }
function QuestionGuide({ data }: { data: FlowNodeItemType }) { function QuestionGuide({ data }: { data: FlowNodeItemType }) {
const { inputs, nodeId } = data; const { inputs, nodeId } = data;
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const questionGuide = useMemo( const questionGuide = useMemo(
() => () =>
@ -168,7 +173,7 @@ function QuestionGuide({ data }: { data: FlowNodeItemType }) {
function TTSGuide({ data }: { data: FlowNodeItemType }) { function TTSGuide({ data }: { data: FlowNodeItemType }) {
const { inputs, nodeId } = data; const { inputs, nodeId } = data;
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { ttsConfig } = splitGuideModule({ inputs } as StoreNodeItemType); const { ttsConfig } = splitGuideModule({ inputs } as StoreNodeItemType);
return ( return (
@ -191,7 +196,7 @@ function TTSGuide({ data }: { data: FlowNodeItemType }) {
function WhisperGuide({ data }: { data: FlowNodeItemType }) { function WhisperGuide({ data }: { data: FlowNodeItemType }) {
const { inputs, nodeId } = data; const { inputs, nodeId } = data;
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { ttsConfig, whisperConfig } = splitGuideModule({ inputs } as StoreNodeItemType); const { ttsConfig, whisperConfig } = splitGuideModule({ inputs } as StoreNodeItemType);
return ( return (
@ -215,7 +220,7 @@ function WhisperGuide({ data }: { data: FlowNodeItemType }) {
function ScheduledTrigger({ data }: { data: FlowNodeItemType }) { function ScheduledTrigger({ data }: { data: FlowNodeItemType }) {
const { inputs, nodeId } = data; const { inputs, nodeId } = data;
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { scheduledTriggerConfig } = splitGuideModule({ inputs } as StoreNodeItemType); const { scheduledTriggerConfig } = splitGuideModule({ inputs } as StoreNodeItemType);
return ( return (

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { useMemo } from 'react';
import { NodeProps } from 'reactflow'; import { NodeProps } from 'reactflow';
import NodeCard from './render/NodeCard'; import NodeCard from './render/NodeCard';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d'; 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 RenderOutput from './render/RenderOutput';
import IOTitle from '../components/IOTitle'; import IOTitle from '../components/IOTitle';
import { useTranslation } from 'next-i18next'; 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<FlowNodeItemType>) => { const NodeStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { nodeId, outputs } = data; const { nodeId, outputs } = data;
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const variablesOutputs = useCreation(() => {
const variables = getWorkflowGlobalVariables(nodeList, t);
return variables.map<FlowNodeOutputItemType>((item) => ({
id: item.key,
type: FlowNodeOutputTypeEnum.static,
key: item.key,
valueType: item.valueType || WorkflowIOValueTypeEnum.any,
label: item.label
}));
}, [nodeList, t]);
return ( return (
<NodeCard <NodeCard
@ -26,6 +46,10 @@ const NodeStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
<IOTitle text={t('common.Output')} /> <IOTitle text={t('common.Output')} />
<RenderOutput nodeId={nodeId} flowOutputList={outputs} /> <RenderOutput nodeId={nodeId} flowOutputList={outputs} />
</Container> </Container>
<Container>
<IOTitle text={t('core.module.Variable')} />
<RenderOutput nodeId={nodeId} flowOutputList={variablesOutputs} />
</Container>
</NodeCard> </NodeCard>
); );
}; };

View File

@ -1,12 +1,15 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Position } from 'reactflow'; import { Position } from 'reactflow';
import { useFlowProviderStore } from '../../../FlowProvider';
import { SourceHandle, TargetHandle } from '.'; import { SourceHandle, TargetHandle } from '.';
import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; 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 }) => { 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]); 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 }) => { 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]); const node = useMemo(() => nodeList.find((node) => node.nodeId === nodeId), [nodeList, nodeId]);

View File

@ -1,15 +1,12 @@
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import { Box, BoxProps } from '@chakra-ui/react'; import { Box, BoxProps } from '@chakra-ui/react';
import { import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
WorkflowIOValueTypeEnum,
NodeOutputKeyEnum
} from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { Connection, Handle, Position } from 'reactflow'; import { Connection, Handle, Position } from 'reactflow';
import { useFlowProviderStore } from '../../../FlowProvider';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { getHandleId } from '@fastgpt/global/core/workflow/utils'; import { getHandleId } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const handleSize = '14px'; const handleSize = '14px';
type ToolHandleProps = BoxProps & { type ToolHandleProps = BoxProps & {
@ -17,7 +14,9 @@ type ToolHandleProps = BoxProps & {
}; };
export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => { export const ToolTargetHandle = ({ nodeId }: ToolHandleProps) => {
const { t } = useTranslation(); 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 handleId = NodeOutputKeyEnum.selectedTools;
const connected = edges.some((edge) => edge.target === nodeId && edge.targetHandle === handleId); 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) => { export const ToolSourceHandle = ({ nodeId }: ToolHandleProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { setEdges } = useFlowProviderStore(); const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
/* onConnect edge, delete tool input and switch */ /* onConnect edge, delete tool input and switch */
const onConnect = useCallback( const onConnect = useCallback(

View File

@ -1,9 +1,10 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Handle, Position } from 'reactflow'; import { Handle, Position } from 'reactflow';
import { useFlowProviderStore } from '../../../FlowProvider';
import { SmallAddIcon } from '@chakra-ui/icons'; import { SmallAddIcon } from '@chakra-ui/icons';
import { handleHighLightStyle, sourceCommonStyle, handleConnectedStyle, handleSize } from './style'; import { handleHighLightStyle, sourceCommonStyle, handleConnectedStyle, handleSize } from './style';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
type Props = { type Props = {
nodeId: string; nodeId: string;
@ -23,7 +24,11 @@ const MySourceHandle = React.memo(function MySourceHandle({
highlightStyle: Record<string, any>; highlightStyle: Record<string, any>;
connectedStyle: Record<string, any>; connectedStyle: Record<string, any>;
}) { }) {
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 node = useMemo(() => nodes.find((node) => node.data.nodeId === nodeId), [nodes, nodeId]);
const connected = edges.some((edge) => edge.sourceHandle === handleId); const connected = edges.some((edge) => edge.sourceHandle === handleId);
@ -136,8 +141,10 @@ const MyTargetHandle = React.memo(function MyTargetHandle({
highlightStyle: Record<string, any>; highlightStyle: Record<string, any>;
connectedStyle: Record<string, any>; connectedStyle: Record<string, any>;
}) { }) {
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 node = useMemo(() => nodeList.find((node) => node.nodeId === nodeId), [nodeList, nodeId]);
const connected = edges.some((edge) => edge.targetHandle === handleId); const connected = edges.some((edge) => edge.targetHandle === handleId);
const connectedEdges = edges.filter((edge) => edge.target === nodeId); const connectedEdges = edges.filter((edge) => edge.target === nodeId);
@ -194,12 +201,13 @@ const MyTargetHandle = React.memo(function MyTargetHandle({
return false; return false;
} }
if (connectingEdge?.handleId && !connectingEdge.handleId?.includes('source')) 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; return true;
}, [connectedEdges, connectingEdge?.handleId, edges, node, nodeId]); }, [connectedEdges, connectingEdge?.handleId, edges, handleId, node, nodeId]);
const RenderHandle = useMemo(() => { const RenderHandle = useMemo(() => {
return ( return (

View File

@ -6,7 +6,6 @@ import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useEditTitle } from '@/web/common/hooks/useEditTitle'; import { useEditTitle } from '@/web/common/hooks/useEditTitle';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useFlowProviderStore } from '../../FlowProvider';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; 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 { getErrText } from '@fastgpt/global/common/error/utils';
import { storeNode2FlowNode } from '@/web/core/workflow/utils'; import { storeNode2FlowNode } from '@/web/core/workflow/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
type Props = FlowNodeItemType & { type Props = FlowNodeItemType & {
children?: React.ReactNode | React.ReactNode[] | string; children?: React.ReactNode | React.ReactNode[] | string;
@ -55,7 +56,9 @@ const NodeCard = (props: Props) => {
pluginId pluginId
} = props; } = 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( const showToolHandle = useMemo(
() => isTool && !!nodeList.find((item) => item?.flowNodeType === FlowNodeTypeEnum.tools), () => isTool && !!nodeList.find((item) => item?.flowNodeType === FlowNodeTypeEnum.tools),
@ -105,7 +108,6 @@ const NodeCard = (props: Props) => {
intro intro
]); ]);
const Render = useMemo(() => {
return ( return (
<Box <Box
minW={minW} minW={minW}
@ -140,9 +142,6 @@ const NodeCard = (props: Props) => {
<ConnectionTargetHandle nodeId={nodeId} /> <ConnectionTargetHandle nodeId={nodeId} />
</Box> </Box>
); );
}, [Header, children, isError, maxW, minW, nodeId, onUpdateNodeError, selected, setHoverNodeId]);
return Render;
}; };
export default React.memo(NodeCard); export default React.memo(NodeCard);
@ -180,7 +179,10 @@ const MenuRender = React.memo(function MenuRender({
type: 'delete' 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( const onCopyNode = useCallback(
(nodeId: string) => { (nodeId: string) => {
@ -383,7 +385,8 @@ const NodeIntro = React.memo(function NodeIntro({
intro?: string; intro?: string;
}) { }) {
const { t } = useTranslation(); 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 moduleIsTool = useMemo(() => {
const { isTool } = splitToolInputs([], nodeId); const { isTool } = splitToolInputs([], nodeId);
@ -442,8 +445,12 @@ const NodeDebugResponse = React.memo(function NodeDebugResponse({
debugResult: FlowNodeItemType['debugResult']; debugResult: FlowNodeItemType['debugResult'];
}) { }) {
const { t } = useTranslation(); 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({ const { openConfirm, ConfirmModal } = useConfirm({
content: t('core.workflow.Confirm stop debug') content: t('core.workflow.Confirm stop debug')
}); });

View File

@ -1,7 +1,6 @@
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useFlowProviderStore } from '../../../FlowProvider';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons'; 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 { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import ValueTypeLabel from '../ValueTypeLabel'; import ValueTypeLabel from '../ValueTypeLabel';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const FieldEditModal = dynamic(() => import('../FieldEditModal')); const FieldEditModal = dynamic(() => import('../FieldEditModal'));
type Props = { type Props = {
nodeId: string; nodeId: string;
input: FlowNodeInputItemType; input: FlowNodeInputItemType;
mode?: 'app' | 'plugin';
}; };
const InputLabel = ({ nodeId, input }: Props) => { const InputLabel = ({ nodeId, input }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { onChangeNode } = useFlowProviderStore();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { const {
description, description,
toolDescription, toolDescription,

View File

@ -5,18 +5,21 @@ import { SmallAddIcon } from '@chakra-ui/icons';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type'; import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useFlowProviderStore } from '../../../../FlowProvider';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import Reference from './Reference'; import Reference from './Reference';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const FieldEditModal = dynamic(() => import('../../FieldEditModal')); const FieldEditModal = dynamic(() => import('../../FieldEditModal'));
const AddInputParam = (props: RenderInputProps) => { const AddInputParam = (props: RenderInputProps) => {
const { item, inputs, nodeId } = props; const { item, inputs, nodeId } = props;
const { t } = useTranslation(); 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 inputValue = useMemo(() => (item.value || []) as FlowNodeInputItemType[], [item.value]);
const [editField, setEditField] = useState<EditNodeFieldType>(); const [editField, setEditField] = useState<EditNodeFieldType>();

View File

@ -1,21 +1,22 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor'; import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
import { import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
formatEditorVariablePickerIcon, import { useContextSelector } from 'use-context-selector';
getGuideModule, import { WorkflowContext } from '@/components/core/workflow/context';
splitGuideModule import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
} from '@fastgpt/global/core/workflow/utils'; import { useCreation } from 'ahooks';
import { useTranslation } from 'next-i18next';
const JsonEditor = ({ inputs = [], item, nodeId }: RenderInputProps) => { 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 // get variable
const variables = useMemo(() => { const variables = useCreation(() => {
const globalVariables = formatEditorVariablePickerIcon( const globalVariables = getWorkflowGlobalVariables(nodeList, t);
splitGuideModule(getGuideModule(nodeList))?.variableModules || []
);
const moduleVariables = formatEditorVariablePickerIcon( const moduleVariables = formatEditorVariablePickerIcon(
inputs inputs
.filter((input) => input.canEdit) .filter((input) => input.canEdit)

View File

@ -7,10 +7,11 @@ import {
NumberInputField, NumberInputField,
NumberInputStepper NumberInputStepper
} from '@chakra-ui/react'; } 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 NumberInputRender = ({ item, nodeId }: RenderInputProps) => {
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (

View File

@ -1,16 +1,19 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { Flex, Box, ButtonProps } from '@chakra-ui/react'; import { Flex, Box, ButtonProps } from '@chakra-ui/react';
import { useFlowProviderStore } from '../../../../FlowProvider';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { computedNodeInputReference } from '@/web/core/workflow/utils'; import { computedNodeInputReference } from '@/web/core/workflow/utils';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { import {
NodeOutputKeyEnum, NodeOutputKeyEnum,
VARIABLE_NODE_ID,
WorkflowIOValueTypeEnum WorkflowIOValueTypeEnum
} from '@fastgpt/global/core/workflow/constants'; } from '@fastgpt/global/core/workflow/constants';
import type { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io'; import type { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io';
import dynamic from 'next/dynamic'; 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( const MultipleRowSelect = dynamic(
() => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect') () => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect')
@ -34,10 +37,25 @@ type SelectProps = {
const Reference = ({ item, nodeId }: RenderInputProps) => { const Reference = ({ item, nodeId }: RenderInputProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const onSelect = useCallback( const onSelect = useCallback(
(e: any) => { (e: any) => {
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({ onChangeNode({
nodeId, nodeId,
type: 'updateInput', type: 'updateInput',
@ -47,8 +65,9 @@ const Reference = ({ item, nodeId }: RenderInputProps) => {
value: e value: e
} }
}); });
}
}, },
[item, nodeId, onChangeNode] [item, nodeId, nodeList, onChangeNode]
); );
const { referenceList, formatValue } = useReference({ const { referenceList, formatValue } = useReference({
@ -79,13 +98,15 @@ export const useReference = ({
value?: any; value?: any;
}) => { }) => {
const { t } = useTranslation(); 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 referenceList = useMemo(() => {
const sourceNodes = computedNodeInputReference({ const sourceNodes = computedNodeInputReference({
nodeId, nodeId,
nodes: nodeList, nodes: nodeList,
edges: edges edges: edges,
t
}); });
if (!sourceNodes) return []; if (!sourceNodes) return [];

View File

@ -1,10 +1,11 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import MySelect from '@fastgpt/web/components/common/MySelect'; 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 SelectRender = ({ item, nodeId }: RenderInputProps) => {
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (

View File

@ -1,16 +1,18 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import { Box, Button, Flex, useDisclosure, useTheme } from '@chakra-ui/react'; import { Box, Button, Flex, useDisclosure, useTheme } from '@chakra-ui/react';
import { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d'; import { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import SelectAppModal from '../../../../SelectAppModal'; import SelectAppModal from '../../../../SelectAppModal';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const SelectAppRender = ({ item, nodeId }: RenderInputProps) => { const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const theme = useTheme();
const { filterAppIds, onChangeNode } = useFlowProviderStore(); const filterAppIds = useContextSelector(WorkflowContext, (ctx) => ctx.filterAppIds);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { const {
isOpen: isOpenSelectApp, isOpen: isOpenSelectApp,
@ -20,7 +22,7 @@ const SelectAppRender = ({ item, nodeId }: RenderInputProps) => {
const value = item.value as SelectAppItemType | undefined; const value = item.value as SelectAppItemType | undefined;
const filterAppString = useMemo(() => filterAppIds.join(','), [filterAppIds]); const filterAppString = useMemo(() => filterAppIds?.join(',') || '', [filterAppIds]);
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (

View File

@ -1,6 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import { Box, Button, Flex, Grid, useDisclosure, useTheme } from '@chakra-ui/react'; import { Box, Button, Flex, Grid, useDisclosure, useTheme } from '@chakra-ui/react';
import { useDatasetStore } from '@/web/core/dataset/store/dataset'; import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { SelectedDatasetType } from '@fastgpt/global/core/workflow/api'; import { SelectedDatasetType } from '@fastgpt/global/core/workflow/api';
@ -8,19 +7,19 @@ import Avatar from '@/components/Avatar';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants'; 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 dynamic from 'next/dynamic';
import MyIcon from '@fastgpt/web/components/common/Icon'; 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 DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) => { const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const theme = useTheme();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [data, setData] = useState({ const [data, setData] = useState({
searchMode: DatasetSearchModeEnum.embedding, searchMode: DatasetSearchModeEnum.embedding,
limit: 5, limit: 5,

View File

@ -1,6 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react'; import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants'; 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 DatasetParamsModal, { DatasetParamsProps } from '@/components/core/app/DatasetParamsModal';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip'; 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 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 { t } = useTranslation();
const { llmModelList } = useSystemStore(); const { llmModelList } = useSystemStore();

View File

@ -3,11 +3,12 @@ import type { RenderInputProps } from '../type';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants'; import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
import AIModelSelector from '@/components/Select/AIModelSelector'; 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 SelectAiModelRender = ({ item, nodeId }: RenderInputProps) => {
const { llmModelList } = useSystemStore(); const { llmModelList } = useSystemStore();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const modelList = useMemo( const modelList = useMemo(
() => () =>

View File

@ -1,12 +1,13 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d'; import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d';
import SettingLLMModel from '@/components/core/ai/SettingLLMModel'; import SettingLLMModel from '@/components/core/ai/SettingLLMModel';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; 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 SelectAiModelRender = ({ item, inputs = [], nodeId }: RenderInputProps) => {
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const onChangeModel = useCallback( const onChangeModel = useCallback(
(e: SettingAIDataType) => { (e: SettingAIDataType) => {

View File

@ -1,7 +1,6 @@
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { Box, BoxProps, Button, Flex, ModalFooter, useDisclosure } from '@chakra-ui/react'; import { Box, BoxProps, Button, Flex, ModalFooter, useDisclosure } from '@chakra-ui/react';
import { useFlowProviderStore } from '../../../../FlowProvider';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type'; 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 Reference from './Reference';
import { getSystemVariables } from '@/web/core/app/utils'; import { getSystemVariables } from '@/web/core/app/utils';
import ValueTypeLabel from '../../ValueTypeLabel'; 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 = { const LabelStyles: BoxProps = {
fontSize: ['sm', 'md'] fontSize: ['sm', 'md']
@ -38,7 +41,9 @@ const SettingQuotePrompt = (props: RenderInputProps) => {
const { inputs = [], nodeId } = props; const { inputs = [], nodeId } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const { isOpen, onOpen, onClose } = useDisclosure(); 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({ const { watch, setValue, handleSubmit } = useForm({
defaultValues: { defaultValues: {
quoteTemplate: inputs.find((input) => input.key === 'quoteTemplate')?.value || '', quoteTemplate: inputs.find((input) => input.key === 'quoteTemplate')?.value || '',
@ -48,14 +53,10 @@ const SettingQuotePrompt = (props: RenderInputProps) => {
const aiChatQuoteTemplate = watch('quoteTemplate'); const aiChatQuoteTemplate = watch('quoteTemplate');
const aiChatQuotePrompt = watch('quotePrompt'); const aiChatQuotePrompt = watch('quotePrompt');
const variables = useMemo(() => { const variables = useCreation(() => {
const globalVariables = formatEditorVariablePickerIcon( const globalVariables = getWorkflowGlobalVariables(nodeList, t);
splitGuideModule(getGuideModule(nodeList))?.variableModules || []
);
const systemVariables = getSystemVariables(t); return globalVariables;
return [...globalVariables, ...systemVariables];
}, [nodeList, t]); }, [nodeList, t]);
const [selectTemplateData, setSelectTemplateData] = useState<{ const [selectTemplateData, setSelectTemplateData] = useState<{

View File

@ -1,13 +1,14 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import MySlider from '@/components/Slider'; import MySlider from '@/components/Slider';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/components/core/workflow/context';
const SliderRender = ({ item, nodeId }: RenderInputProps) => { const SliderRender = ({ item, nodeId }: RenderInputProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (

View File

@ -1,10 +1,11 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { Switch } from '@chakra-ui/react'; 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 SwitchRender = ({ item, nodeId }: RenderInputProps) => {
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (

View File

@ -1,10 +1,11 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { Input } from '@chakra-ui/react'; 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 TextInput = ({ item, nodeId }: RenderInputProps) => {
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (

View File

@ -1,24 +1,22 @@
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { useFlowProviderStore } from '../../../../FlowProvider';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor'; import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
formatEditorVariablePickerIcon, import { useContextSelector } from 'use-context-selector';
getGuideModule, import { WorkflowContext } from '@/components/core/workflow/context';
splitGuideModule import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
} from '@fastgpt/global/core/workflow/utils'; import { useCreation } from 'ahooks';
import { getSystemVariables } from '@/web/core/app/utils';
const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => { const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { nodeList, onChangeNode } = useFlowProviderStore(); const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
// get variable // get variable
const variables = useMemo(() => { const variables = useCreation(() => {
const globalVariables = formatEditorVariablePickerIcon( const globalVariables = getWorkflowGlobalVariables(nodeList, t);
splitGuideModule(getGuideModule(nodeList))?.variableModules || []
);
const moduleVariables = formatEditorVariablePickerIcon( const moduleVariables = formatEditorVariablePickerIcon(
inputs inputs
.filter((input) => input.canEdit) .filter((input) => input.canEdit)
@ -28,9 +26,7 @@ const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
})) }))
); );
const systemVariables = getSystemVariables(t); return [...globalVariables, ...moduleVariables];
return [...globalVariables, ...moduleVariables, ...systemVariables];
}, [nodeList, inputs, t]); }, [nodeList, inputs, t]);
const onChange = useCallback( const onChange = useCallback(

View File

@ -11,7 +11,8 @@ import VariableTable from '../VariableTable';
import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type'; import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType'; import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
import { getNanoid } from '@fastgpt/global/common/string/tools'; 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: { const RenderList: {
types: `${FlowNodeOutputTypeEnum}`[]; types: `${FlowNodeOutputTypeEnum}`[];
@ -26,7 +27,7 @@ const RenderOutput = ({
flowOutputList: FlowNodeOutputItemType[]; flowOutputList: FlowNodeOutputItemType[];
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const outputString = useMemo(() => JSON.stringify(flowOutputList), [flowOutputList]); const outputString = useMemo(() => JSON.stringify(flowOutputList), [flowOutputList]);
const copyOutputs = useMemo(() => { const copyOutputs = useMemo(() => {

View File

@ -17,8 +17,9 @@ import { defaultEditFormData } from './constants';
import MySelect from '@fastgpt/web/components/common/MySelect'; import MySelect from '@fastgpt/web/components/common/MySelect';
import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useFlowProviderStore } from '../../../FlowProvider';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; 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 = ({ const EditFieldModal = ({
defaultValue = defaultEditFormData, defaultValue = defaultEditFormData,
@ -27,7 +28,7 @@ const EditFieldModal = ({
}: EditFieldModalProps) => { }: EditFieldModalProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { register, setValue, handleSubmit, watch } = useForm<FlowNodeInputItemType>({ const { register, setValue, handleSubmit, watch } = useForm<FlowNodeInputItemType>({
defaultValues: defaultValue defaultValues: defaultValue

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react'; import React, { useMemo, useState } from 'react';
import type { import type {
FlowNodeInputItemType, FlowNodeInputItemType,
FlowNodeOutputItemType FlowNodeOutputItemType
@ -19,7 +19,8 @@ import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { defaultEditFormData } from './constants'; 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 EditFieldModal = dynamic(() => import('./EditFieldModal'));
const RenderToolInput = ({ const RenderToolInput = ({
@ -32,8 +33,9 @@ const RenderToolInput = ({
canEdit?: boolean; canEdit?: boolean;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { onChangeNode } = useFlowProviderStore(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [editField, setEditField] = React.useState<FlowNodeInputItemType>();
const [editField, setEditField] = useState<FlowNodeInputItemType>();
return ( return (
<> <>

View File

@ -1,58 +1,89 @@
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';
import React, {
type SetStateAction,
type Dispatch,
useContext,
useCallback,
createContext,
useRef,
useMemo,
useState,
useEffect
} 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 { postWorkflowDebug } from '@/web/core/workflow/api';
import { storeEdgesRenderEdge, storeNode2FlowNode } from '@/web/core/workflow/utils';
import { getErrText } from '@fastgpt/global/common/error/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 {
FlowNodeItemType,
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, {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useRef,
useState
} from 'react';
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 { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils';
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus'; import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
import { delay } from '@fastgpt/global/common/system/utils';
type OnChange<ChangesType> = (changes: ChangesType[]) => void; type OnChange<ChangesType> = (changes: ChangesType[]) => void;
export type useFlowProviderStoreType = { type WorkflowContextType = {
// connect
connectingEdge: OnConnectStartParams | undefined;
setConnectingEdge: React.Dispatch<React.SetStateAction<OnConnectStartParams | undefined>>;
// nodes
basicNodeTemplates: FlowNodeTemplateType[];
reactFlowWrapper: null | React.RefObject<HTMLDivElement>;
mode: 'app' | 'plugin'; mode: 'app' | 'plugin';
filterAppIds: string[]; basicNodeTemplates: FlowNodeTemplateType[];
filterAppIds?: string[];
reactFlowWrapper: React.RefObject<HTMLDivElement> | null;
// nodes
nodes: Node<FlowNodeItemType, string | undefined>[]; nodes: Node<FlowNodeItemType, string | undefined>[];
nodeList: FlowNodeItemType[]; nodeList: FlowNodeItemType[];
setNodes: Dispatch<SetStateAction<Node<FlowNodeItemType, string | undefined>[]>>; setNodes: Dispatch<SetStateAction<Node<FlowNodeItemType, string | undefined>[]>>;
onNodesChange: OnChange<NodeChange>; onNodesChange: OnChange<NodeChange>;
hasToolNode: boolean;
hoverNodeId?: string;
setHoverNodeId: React.Dispatch<React.SetStateAction<string | undefined>>;
onUpdateNodeError: (node: string, isError: Boolean) => void;
onResetNode: (e: { id: string; module: FlowNodeTemplateType }) => void;
onChangeNode: (e: FlowNodeChangeProps) => void;
// edges
edges: Edge<any>[];
setEdges: Dispatch<SetStateAction<Edge<any>[]>>;
onEdgesChange: OnChange<EdgeChange>;
onDelEdge: (e: {
nodeId: string;
sourceHandle?: string | undefined;
targetHandle?: string | undefined;
}) => void;
// connect
connectingEdge?: OnConnectStartParams;
setConnectingEdge: React.Dispatch<React.SetStateAction<OnConnectStartParams | undefined>>;
// common function
onFixView: () => void;
splitToolInputs: (
inputs: FlowNodeInputItemType[],
nodeId: string
) => {
isTool: boolean;
toolInputs: FlowNodeInputItemType[];
commonInputs: FlowNodeInputItemType[];
};
initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => Promise<void>;
// debug
// debug // debug
workflowDebugData: workflowDebugData:
| { | {
@ -72,67 +103,66 @@ export type useFlowProviderStoreType = {
runtimeEdges: RuntimeEdgeItemType[]; runtimeEdges: RuntimeEdgeItemType[];
}) => Promise<void>; }) => Promise<void>;
onStopNodeDebug: () => void; onStopNodeDebug: () => void;
edges: Edge<any>[];
setEdges: Dispatch<SetStateAction<Edge<any>[]>>;
onEdgesChange: OnChange<EdgeChange>;
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<void>;
splitToolInputs: (
inputs: FlowNodeInputItemType[],
nodeId: string
) => {
isTool: boolean;
toolInputs: FlowNodeInputItemType[];
commonInputs: FlowNodeInputItemType[];
};
hasToolNode: boolean;
hoverNodeId: string | undefined;
setHoverNodeId: React.Dispatch<React.SetStateAction<string | undefined>>;
onUpdateNodeError: (node: string, isError: Boolean) => void;
}; };
const StateContext = createContext<useFlowProviderStoreType>({ type ContextValueProps = Pick<
reactFlowWrapper: null, WorkflowContextType,
'mode' | 'basicNodeTemplates' | 'filterAppIds'
> & {
appId?: string;
pluginId?: string;
};
type DebugDataType = {
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
nextRunNodes: RuntimeNodeItemType[];
};
export const WorkflowContext = createContext<WorkflowContextType>({
mode: 'app', mode: 'app',
filterAppIds: [], setConnectingEdge: function (
value: React.SetStateAction<OnConnectStartParams | undefined>
): void {
throw new Error('Function not implemented.');
},
onFixView: function (): void {
throw new Error('Function not implemented.');
},
basicNodeTemplates: [],
reactFlowWrapper: null,
nodes: [], nodes: [],
nodeList: [],
setNodes: function ( setNodes: function (
value: React.SetStateAction<Node<FlowNodeItemType, string | undefined>[]> value: React.SetStateAction<Node<FlowNodeItemType, string | undefined>[]>
): void { ): void {
return; throw new Error('Function not implemented.');
}, },
onNodesChange: function (changes: NodeChange[]): void { onNodesChange: function (changes: NodeChange[]): void {
return; throw new Error('Function not implemented.');
},
hasToolNode: false,
setHoverNodeId: function (value: React.SetStateAction<string | undefined>): void {
throw new Error('Function not implemented.');
},
onUpdateNodeError: function (node: string, isError: Boolean): void {
throw new Error('Function not implemented.');
}, },
edges: [], edges: [],
setEdges: function (value: React.SetStateAction<Edge<any>[]>): void { setEdges: function (value: React.SetStateAction<Edge<any>[]>): void {
return; throw new Error('Function not implemented.');
}, },
onEdgesChange: function (changes: EdgeChange[]): void { onEdgesChange: function (changes: EdgeChange[]): void {
return; throw new Error('Function not implemented.');
}, },
onFixView: function (): void { onResetNode: function (e: { id: string; module: FlowNodeTemplateType }): void {
return; throw new Error('Function not implemented.');
},
onChangeNode: function (e: FlowNodeChangeProps): void {
return;
}, },
onDelEdge: function (e: { onDelEdge: function (e: {
nodeId: string; nodeId: string;
sourceHandle?: string | undefined; sourceHandle?: string | undefined;
targetHandle?: string | undefined; targetHandle?: string | undefined;
}): void { }): void {
return;
},
onResetNode: function (e): void {
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
}, },
splitToolInputs: function ( splitToolInputs: function (
@ -145,23 +175,12 @@ const StateContext = createContext<useFlowProviderStoreType>({
} { } {
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
}, },
hasToolNode: false,
connectingEdge: undefined,
basicNodeTemplates: [],
initData: function (e: { initData: function (e: {
nodes: StoreNodeItemType[]; nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[]; edges: StoreEdgeItemType[];
}): Promise<void> { }): Promise<void> {
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
}, },
hoverNodeId: undefined,
setHoverNodeId: function (value: React.SetStateAction<string | undefined>): void {
throw new Error('Function not implemented.');
},
onUpdateNodeError: function (nodeId: string, isError: Boolean): void {
throw new Error('Function not implemented.');
},
nodeList: [],
workflowDebugData: undefined, workflowDebugData: undefined,
onNextNodeDebug: function (): Promise<void> { onNextNodeDebug: function (): Promise<void> {
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
@ -180,55 +199,24 @@ const StateContext = createContext<useFlowProviderStoreType>({
onStopNodeDebug: function (): void { onStopNodeDebug: function (): void {
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
}, },
setConnectingEdge: function ( onChangeNode: function (e: FlowNodeChangeProps): void {
value: React.SetStateAction<OnConnectStartParams | undefined>
): void {
throw new Error('Function not implemented.'); throw new Error('Function not implemented.');
} }
}); });
export const useFlowProviderStore = () => useContext(StateContext);
export const FlowProvider = ({ const WorkflowContextProvider = ({
mode,
basicNodeTemplates = [],
filterAppIds = [],
children, children,
appId, value
pluginId
}: { }: {
mode: useFlowProviderStoreType['mode'];
basicNodeTemplates: FlowNodeTemplateType[];
filterAppIds?: string[];
children: React.ReactNode; children: React.ReactNode;
appId?: string; value: ContextValueProps;
pluginId?: string;
}) => { }) => {
const reactFlowWrapper = useRef<HTMLDivElement>(null); const { appId, pluginId } = value;
const { toast } = useToast(); const { toast } = useToast();
const [nodes = [], setNodes, onNodesChange] = useNodesState<FlowNodeItemType>([]); const reactFlowWrapper = useRef<HTMLDivElement>(null);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [hoverNodeId, setHoverNodeId] = useState<string>();
const [connectingEdge, setConnectingEdge] = useState<OnConnectStartParams>();
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);
}, []);
/* edge */ /* edge */
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const onDelEdge = useCallback( const onDelEdge = useCallback(
({ ({
nodeId, nodeId,
@ -252,9 +240,34 @@ export const FlowProvider = ({
[setEdges] [setEdges]
); );
/* connect */
const [connectingEdge, setConnectingEdge] = useState<OnConnectStartParams>();
/* node */ /* node */
const [nodes = [], setNodes, onNodesChange] = useNodesState<FlowNodeItemType>([]);
const [hoverNodeId, setHoverNodeId] = useState<string>();
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 // reset a node data. delete edge and replace it
const onResetNode = useCallback( const onResetNode = useMemoizedFn(
({ id, module }: { id: string; module: FlowNodeTemplateType }) => { ({ id, module }: { id: string; module: FlowNodeTemplateType }) => {
setNodes((state) => setNodes((state) =>
state.map((node) => { state.map((node) => {
@ -277,11 +290,10 @@ export const FlowProvider = ({
return node; return node;
}) })
); );
}, }
[onDelEdge, setNodes]
); );
const onChangeNode = useCallback(
(props: FlowNodeChangeProps) => { const onChangeNode = useMemoizedFn((props: FlowNodeChangeProps) => {
const { nodeId, type } = props; const { nodeId, type } = props;
setNodes((nodes) => setNodes((nodes) =>
nodes.map((node) => { nodes.map((node) => {
@ -377,31 +389,43 @@ export const FlowProvider = ({
}; };
}) })
); );
},
[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 item;
}); });
/* function */
const onFixView = useMemoizedFn(() => {
const btn = document.querySelector('.custom-workflow-fix_view') as HTMLButtonElement;
setTimeout(() => {
btn && btn.click();
}, 100);
}); });
},
[setNodes] /* 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
); );
/* Run workflow debug and get next runtime data */ return {
const [workflowDebugData, setWorkflowDebugData] = useState<{ isTool,
runtimeNodes: RuntimeNodeItemType[]; toolInputs: inputs.filter((item) => isTool && item.toolDescription),
runtimeEdges: RuntimeEdgeItemType[]; commonInputs: inputs.filter((item) => {
nextRunNodes: RuntimeNodeItemType[]; 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 })));
}
);
/* debug */
const [workflowDebugData, setWorkflowDebugData] = useState<DebugDataType>();
const onNextNodeDebug = useCallback( const onNextNodeDebug = useCallback(
async (debugData = workflowDebugData) => { async (debugData = workflowDebugData) => {
if (!debugData) return; if (!debugData) return;
@ -592,36 +616,6 @@ export const FlowProvider = ({
[onNextNodeDebug, onStopNodeDebug] [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(() => { useEffect(() => {
eventBus.on(EventNameEnum.requestWorkflowStore, () => { eventBus.on(EventNameEnum.requestWorkflowStore, () => {
eventBus.emit(EventNameEnum.receiveWorkflowStore, { eventBus.emit(EventNameEnum.receiveWorkflowStore, {
@ -633,43 +627,49 @@ export const FlowProvider = ({
}; };
}, [nodes]); }, [nodes]);
const value = { return (
<WorkflowContext.Provider
value={{
reactFlowWrapper, reactFlowWrapper,
mode, ...value,
filterAppIds, // node
edges,
setEdges,
onEdgesChange,
// nodes
nodes, nodes,
nodeList,
setNodes, setNodes,
onNodesChange, onNodesChange,
nodeList,
hasToolNode,
hoverNodeId, hoverNodeId,
setHoverNodeId, setHoverNodeId,
onUpdateNodeError, onUpdateNodeError,
onResetNode,
onChangeNode,
// edge
edges,
setEdges,
onEdgesChange,
connectingEdge,
setConnectingEdge,
onDelEdge,
// function
onFixView,
splitToolInputs,
initData,
// debug
workflowDebugData, workflowDebugData,
onNextNodeDebug, onNextNodeDebug,
onStartNodeDebug, onStartNodeDebug,
onStopNodeDebug, onStopNodeDebug
}}
basicNodeTemplates, >
// connect {children}
connectingEdge, </WorkflowContext.Provider>
setConnectingEdge, );
onFixView,
onChangeNode,
onResetNode,
onDelEdge,
initData,
splitToolInputs,
hasToolNode
};
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
}; };
export default React.memo(FlowProvider); export default WorkflowContextProvider;
type GetWorkflowStoreResponse = { type GetWorkflowStoreResponse = {
nodes: Node<FlowNodeItemType>[]; nodes: Node<FlowNodeItemType>[];

View File

@ -108,7 +108,9 @@ const LafAccountModal = ({
</Box> </Box>
<Box> <Box>
<Link textDecoration={'underline'} href={`${feConfigs.lafEnv}/`} isExternal> <Link textDecoration={'underline'} href={`${feConfigs.lafEnv}/`} isExternal>
{t('support.user.Go laf env', { env: feConfigs.lafEnv })} {t('support.user.Go laf env', {
env: feConfigs.lafEnv?.split('//')[1]
})}
</Link> </Link>
</Box> </Box>
</Box> </Box>

View File

@ -1,5 +1,5 @@
import { AppDetailType } from '@fastgpt/global/core/app/type.d'; 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 = { export const defaultApp: AppDetailType = {
_id: '', _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<FeishuType> = {
name: '',
limit: {
QPM: 100,
maxUsagePoints: -1
},
responseDetail: false
};
export enum TTSTypeEnum { export enum TTSTypeEnum {
none = 'none', none = 'none',
web = 'web', web = 'web',

View File

@ -1,8 +1,7 @@
import { Box, Card, Flex, Select } from '@chakra-ui/react'; import { Box, Card, Flex } from '@chakra-ui/react';
import React, { useCallback, useRef } from 'react'; import React, { useCallback } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { timezoneList } from '@fastgpt/global/common/time/timezone';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { UserType } from '@fastgpt/global/support/user/type'; import { UserType } from '@fastgpt/global/support/user/type';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';

View File

@ -28,10 +28,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
avatar: props.avatar, avatar: props.avatar,
parentId: props.parentId, parentId: props.parentId,
version: 'v2', version: 'v2',
...(modules && { ...(modules?.length && {
modules: modules modules: modules
}), }),
...(edges && { edges }), ...(edges?.length && { edges }),
metadata: props.metadata metadata: props.metadata
}; };

View File

@ -2,8 +2,6 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response'; import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { request } from 'https'; 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) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
@ -25,7 +23,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
throw new Error('lafEnv is empty'); throw new Error('lafEnv is empty');
} }
const parsedUrl = url.parse(lafEnv); const parsedUrl = new URL(lafEnv);
delete req.headers?.cookie; delete req.headers?.cookie;
delete req.headers?.host; delete req.headers?.host;
delete req.headers?.origin; delete req.headers?.origin;

View File

@ -3,7 +3,6 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { request } from 'http'; import { request } from 'http';
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants'; import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
import url from 'url';
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
@ -15,8 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
throw new Error('url is empty'); throw new Error('url is empty');
} }
const parsedUrl = url.parse(FastGPTProUrl); const parsedUrl = new URL(FastGPTProUrl);
delete req.headers?.rootkey; delete req.headers?.rootkey;
const requestResult = request({ const requestResult = request({
@ -37,6 +35,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
response.statusCode && res.writeHead(response.statusCode); response.statusCode && res.writeHead(response.statusCode);
response.pipe(res); response.pipe(res);
}); });
requestResult.on('error', (e) => { requestResult.on('error', (e) => {
res.send(e); res.send(e);
res.end(); res.end();

View File

@ -5,16 +5,17 @@ import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { authApp } from '@fastgpt/service/support/permission/auth/app'; import { authApp } from '@fastgpt/service/support/permission/auth/app';
import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import { customAlphabet } from 'nanoid'; 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); const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
/* create a shareChat */ /* create a shareChat */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
await connectToDatabase(); await connectToDatabase();
const { appId, ...props } = req.body as OutLinkEditType & { const { appId, ...props } = req.body as OutLinkEditType &
OutLinkEditType & {
appId: string; appId: string;
type: `${OutLinkTypeEnum}`; type: PublishChannelEnum;
}; };
const { teamId, tmbId } = await authApp({ req, authToken: true, appId, per: 'w' }); const { teamId, tmbId } = await authApp({ req, authToken: true, appId, per: 'w' });

View File

@ -9,15 +9,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
try { try {
await connectToDatabase(); await connectToDatabase();
const { appId } = req.query as { const { appId, type } = req.query as {
appId: string; appId: string;
type: string;
}; };
const { teamId, tmbId, isOwner } = await authApp({ req, authToken: true, appId, per: 'w' }); const { teamId, tmbId, isOwner } = await authApp({ req, authToken: true, appId, per: 'w' });
const data = await MongoOutLink.find({ const data = await MongoOutLink.find({
appId, appId,
...(isOwner ? { teamId } : { tmbId }) ...(isOwner ? { teamId } : { tmbId }),
type: type
}).sort({ }).sort({
_id: -1 _id: -1
}); });

View File

@ -9,10 +9,6 @@ import dynamic from 'next/dynamic';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import ChatTest, { type ChatTestComponentRef } from '@/components/core/workflow/Flow/ChatTest'; 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 { flowNode2StoreNodes } from '@/components/core/workflow/utils';
import { useAppStore } from '@/web/core/app/store/useAppStore'; import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useToast } from '@fastgpt/web/hooks/useToast'; 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 MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { formatTime2HM } from '@fastgpt/global/common/string/time'; 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')); 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 { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const { publishApp, updateAppDetail } = useAppStore(); const { publishApp, updateAppDetail } = useAppStore();
const { edges, onUpdateNodeError } = useFlowProviderStore(); const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [saveLabel, setSaveLabel] = useState(t('core.app.Onclick to save')); const [saveLabel, setSaveLabel] = useState(t('core.app.Onclick to save'));
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
const flowData2StoreDataAndCheck = useCallback(async () => { const flowData2StoreDataAndCheck = useCallback(async () => {
const { nodes } = await getWorkflowStore(); const { nodes } = await getWorkflowStore();
@ -101,13 +101,13 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
time: formatTime2HM() time: formatTime2HM()
}) })
); );
ChatTestRef.current?.resetChatTest(); // ChatTestRef.current?.resetChatTest();
} catch (error) {} } catch (error) {}
setIsSaving(false); setIsSaving(false);
return null; return null;
}, [updateAppDetail, app._id, edges, ChatTestRef, t]); }, [updateAppDetail, app._id, edges, t]);
const onclickPublish = useCallback(async () => { const onclickPublish = useCallback(async () => {
setIsSaving(true); setIsSaving(true);

View File

@ -2,10 +2,11 @@ import React, { useEffect, useMemo } from 'react';
import { AppSchema } from '@fastgpt/global/core/app/type.d'; import { AppSchema } from '@fastgpt/global/core/app/type.d';
import Header from './Header'; import Header from './Header';
import Flow from '@/components/core/workflow/Flow'; 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 { appSystemModuleTemplates } from '@fastgpt/global/core/workflow/template/constants';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { v1Workflow2V2 } from '@/web/core/workflow/adapt'; 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 }; type Props = { app: AppSchema; onClose: () => void };
@ -17,7 +18,7 @@ const Render = ({ app, onClose }: Props) => {
'检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大会导致许多工作流无法正常排布请重新手动连接工作流。如仍异常可尝试删除对应节点后重新添加。\n\n你可以直接点击测试进行调试无需点击保存点击保存为新版工作流。' '检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大会导致许多工作流无法正常排布请重新手动连接工作流。如仍异常可尝试删除对应节点后重新添加。\n\n你可以直接点击测试进行调试无需点击保存点击保存为新版工作流。'
}); });
const { initData } = useFlowProviderStore(); const initData = useContextSelector(WorkflowContext, (v) => v.initData);
const workflowStringData = JSON.stringify({ const workflowStringData = JSON.stringify({
nodes: app.modules || [], nodes: app.modules || [],
@ -53,13 +54,15 @@ export default React.memo(function FlowEdit(props: Props) {
const filterAppIds = useMemo(() => [props.app._id], [props.app._id]); const filterAppIds = useMemo(() => [props.app._id], [props.app._id]);
return ( return (
<FlowProvider <WorkflowContextProvider
appId={props.app._id} value={{
mode={'app'} appId: props.app._id,
filterAppIds={filterAppIds} mode: 'app',
basicNodeTemplates={appSystemModuleTemplates} filterAppIds,
basicNodeTemplates: appSystemModuleTemplates
}}
> >
<Render {...props} /> <Render {...props} />
</FlowProvider> </WorkflowContextProvider>
); );
}); });

View File

@ -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 (
<Box pt={[1, 5]}>
<Box fontWeight={'bold'} fontSize={['md', 'xl']} mb={2} px={[4, 8]}>
{t('core.app.navbar.Publish app')}
</Box>
<Box pb={[5, 7]} px={[4, 8]} borderBottom={theme.borders.base}>
<MyRadio
gridTemplateColumns={['repeat(1,1fr)', 'repeat(auto-fill, minmax(0, 400px))']}
iconSize={'20px'}
list={[
{
icon: '/imgs/modal/shareFill.svg',
title: t('core.app.Share link'),
desc: t('core.app.Share link desc'),
value: OutLinkTypeEnum.share
},
{
icon: 'support/outlink/apikeyFill',
title: t('core.app.Api request'),
desc: t('core.app.Api request desc'),
value: OutLinkTypeEnum.apikey
}
// {
// icon: 'support/outlink/iframeLight',
// title: '网页嵌入',
// desc: '嵌入到已有网页中,右下角会生成对话按键',
// value: OutLinkTypeEnum.iframe
// }
]}
value={linkType}
onChange={(e) => setLinkType(e as `${OutLinkTypeEnum}`)}
/>
</Box>
{linkType === OutLinkTypeEnum.share && <Share appId={appId} />}
{linkType === OutLinkTypeEnum.apikey && <API appId={appId} />}
</Box>
);
};
export default OutLink;

View File

@ -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<FeishuType>;
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<FeishuType>) => {
createShareChat({
...e,
appId,
type: PublishChannelEnum.feishu
});
},
errorToast: t('common.Create Failed'),
onSuccess: onCreate
});
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
mutationFn: (e: OutLinkEditType<FeishuType>) => {
return updateShareChat(e);
},
errorToast: t('common.Update Failed'),
onSuccess: onEdit
});
return (
<MyModal
isOpen={true}
iconSrc="/imgs/modal/shareFill.svg"
title={isEdit ? t('outlink.Edit Link') : t('outlink.Create Link')}
>
<ModalBody>
<Flex alignItems={'center'}>
<Box flex={'0 0 90px'}>{t('Name')}</Box>
<Input
placeholder={t('outlink.Feishu name') || 'Link Name'} // TODO: i18n
maxLength={20}
{...register('name', {
required: t('common.Name is empty') || 'Name is empty'
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
QPM
<MyTooltip label={t('outlink.QPM Tips' || '')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Input
max={1000}
{...register('limit.QPM', {
min: 0,
max: 1000,
valueAsNumber: true,
required: t('outlink.QPM is empty') || ''
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('support.outlink.Max usage points')}
<MyTooltip label={t('support.outlink.Max usage points tip')}>
<QuestionOutlineIcon ml={1} />
</MyTooltip>
</Flex>
<Input
{...register('limit.maxUsagePoints', {
min: -1,
max: 10000000,
valueAsNumber: true,
required: true
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('common.Expired Time')}
</Flex>
<Input
type="datetime-local"
defaultValue={
defaultData.limit?.expiredTime
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
: ''
}
onChange={(e) => {
setValue('limit.expiredTime', new Date(e.target.value));
}}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{/* TODO: i18n */}
</Flex>
<Input
placeholder={t('outlink.Default Response') || 'Link Name'}
maxLength={20}
{...register('defaultResponse', {
required: t('common.default Response is empty') || 'Name is empty'
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{/* TODO: i18n */}
</Flex>
<Input
placeholder={t('outlink.Default Response') || 'Link Name'}
maxLength={20}
{...register('immediateResponse', {
required: t('common.default Response is empty') || 'Name is empty'
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>{t('core.module.http.AppId')}</Box>
<Input
placeholder={t('core.module.http.appId') || 'Link Name'}
// maxLength={20}
{...register('app.appId', {
required: t('common.Name is empty') || 'Name is empty'
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>{t('core.module.http.AppSecret')}</Box>
<Input
placeholder={t('outlink.AppSecret') || 'Link Name'}
// maxLength={20}
{...register('app.appSecret', {
required: t('common.Name is empty') || 'Name is empty'
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>Encrypt Key</Box>
<Input
placeholder="Encrypt Key"
// maxLength={20}
{...register('app.encryptKey', {
required: t('common.Name is empty') || 'Name is empty'
})}
/>
</Flex>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 90px'}>Verification Token</Box>
<Input
placeholder="Verification Token"
// maxLength={20}
{...register('app.verificationToken', {
required: t('common.Name is empty') || 'Name is empty'
})}
/>
</Flex>
{/* <Flex alignItems={'center'} mt={4}> */}
{/* <Flex flex={'0 0 90px'} alignItems={'center'}> */}
{/* 限制回复 */}
{/* </Flex> */}
{/* <Switch {...register('wecomConfig.ReplyLimit')} size={'lg'} /> */}
{/* </Flex> */}
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
{t('common.Close')}
</Button>
<Button
isLoading={creating || updating}
onClick={submitShareChat((data) => (isEdit ? onclickUpdate(data) : onclickCreate(data)))}
>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default FeiShuEditModal;

View File

@ -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<OutLinkEditType<FeishuType>>();
const { toast } = useToast();
const {
isFetching,
data: shareChatList = [],
refetch: refetchShareChatList
} = useQuery(['initShareChatList', appId], () =>
getShareChatList<FeishuType>({ appId, type: PublishChannelEnum.feishu })
);
return (
<Box position={'relative'} pt={3} px={5} minH={'50vh'}>
<Flex justifyContent={'space-between'}>
<Box fontWeight={'bold'} fontSize={['md', 'xl']}>
{t('core.app.publish.Fei shu bot publish')}
</Box>
<Button
variant={'whitePrimary'}
colorScheme={'blue'}
size={['sm', 'md']}
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: t('core.app.share.Amount limit tip')
}
: {})}
onClick={() => setEditFeiShuLinkData(defaultFeishuOutLinkForm)}
>
{t('core.app.share.Create link')}
</Button>
</Flex>
<TableContainer mt={3}>
<Table variant={'simple'} w={'100%'} overflowX={'auto'} fontSize={'sm'}>
<Thead>
<Tr>
<Th>{t('common.Name')}</Th>
<Th>{t('support.outlink.Usage points')}</Th>
{feConfigs?.isPlus && (
<>
<Th>{t('core.app.share.Ip limit title')}</Th>
<Th>{t('common.Expired Time')}</Th>
</>
)}
<Th>{t('common.Last use time')}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{shareChatList.map((item) => (
<Tr key={item._id}>
<Td>{item.name}</Td>
<Td>
{Math.round(item.usagePoints)}
{feConfigs?.isPlus
? `${
item.limit?.maxUsagePoints && item.limit.maxUsagePoints > -1
? ` / ${item.limit.maxUsagePoints}`
: ` / ${t('common.Unlimited')}`
}`
: ''}
</Td>
{feConfigs?.isPlus && (
<>
<Td>{item?.limit?.QPM || '-'}</Td>
<Td>
{item?.limit?.expiredTime
? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
: '-'}
</Td>
</>
)}
<Td>
{item.lastTime ? t(formatTimeToChatTime(item.lastTime)) : t('common.Un used')}
</Td>
<Td display={'flex'} alignItems={'center'}>
<MyMenu
Button={
<MyIcon
name={'more'}
_hover={{ bg: 'myGray.100 ' }}
cursor={'pointer'}
borderRadius={'md'}
w={'14px'}
p={2}
/>
}
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);
}
}
]}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{editFeiShuLinkData && (
<FeiShuEditModal
appId={appId}
// type={'feishu' as PublishChannelEnum}
defaultData={editFeiShuLinkData}
onCreate={(id) => {
refetchShareChatList();
setEditFeiShuLinkData(undefined);
}}
onEdit={() => {
toast({
status: 'success',
title: t('common.Update Successful')
});
refetchShareChatList();
setEditFeiShuLinkData(undefined);
}}
onClose={() => setEditFeiShuLinkData(undefined)}
/>
)}
{shareChatList.length === 0 && !isFetching && (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
{t('core.app.share.Not share link')}
</Box>
</Flex>
)}
<Loading loading={isFetching} fixed={false} />
</Box>
);
};
export default React.memo(FeiShu);

View File

@ -32,8 +32,8 @@ import { useCopyData } from '@/web/common/hooks/useCopyData';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { defaultOutLinkForm } from '@/constants/app'; import { defaultOutLinkForm } from '@/constants/app';
import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d'; import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d';
import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useRequest } from '@/web/common/hooks/useRequest';
import { OutLinkTypeEnum } from '@fastgpt/global/support/outLink/constant'; import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
@ -47,7 +47,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
const SelectUsingWayModal = dynamic(() => import('./SelectUsingWayModal')); const SelectUsingWayModal = dynamic(() => import('./SelectUsingWayModal'));
const Share = ({ appId }: { appId: string }) => { const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { Loading, setIsLoading } = useLoading(); const { Loading, setIsLoading } = useLoading();
const { feConfigs } = useSystemStore(); const { feConfigs } = useSystemStore();
@ -64,7 +64,9 @@ const Share = ({ appId }: { appId: string }) => {
isFetching, isFetching,
data: shareChatList = [], data: shareChatList = [],
refetch: refetchShareChatList refetch: refetchShareChatList
} = useQuery(['initShareChatList', appId], () => getShareChatList(appId)); } = useQuery(['initShareChatList', appId], () =>
getShareChatList({ appId, type: PublishChannelEnum.share })
);
return ( return (
<Box position={'relative'} pt={3} px={5} minH={'50vh'}> <Box position={'relative'} pt={3} px={5} minH={'50vh'}>
@ -95,12 +97,16 @@ const Share = ({ appId }: { appId: string }) => {
<Thead> <Thead>
<Tr> <Tr>
<Th>{t('common.Name')}</Th> <Th>{t('common.Name')}</Th>
{feConfigs?.isPlus && (
<>
<Th>{t('common.Expired Time')}</Th>
</>
)}
<Th>{t('support.outlink.Usage points')}</Th> <Th>{t('support.outlink.Usage points')}</Th>
<Th>{t('core.app.share.Is response quote')}</Th> <Th>{t('core.app.share.Is response quote')}</Th>
{feConfigs?.isPlus && ( {feConfigs?.isPlus && (
<> <>
<Th>{t('core.app.share.Ip limit title')}</Th> <Th>{t('core.app.share.Ip limit title')}</Th>
<Th>{t('common.Expired Time')}</Th>
<Th>{t('core.app.share.Role check')}</Th> <Th>{t('core.app.share.Role check')}</Th>
</> </>
)} )}
@ -112,6 +118,15 @@ const Share = ({ appId }: { appId: string }) => {
{shareChatList.map((item) => ( {shareChatList.map((item) => (
<Tr key={item._id}> <Tr key={item._id}>
<Td>{item.name}</Td> <Td>{item.name}</Td>
{feConfigs?.isPlus && (
<>
<Td>
{item.limit?.expiredTime
? dayjs(item.limit.expiredTime).format('YYYY-MM-DD HH:mm')
: '-'}
</Td>
</>
)}
<Td> <Td>
{Math.round(item.usagePoints)} {Math.round(item.usagePoints)}
{feConfigs?.isPlus {feConfigs?.isPlus
@ -126,18 +141,14 @@ const Share = ({ appId }: { appId: string }) => {
{feConfigs?.isPlus && ( {feConfigs?.isPlus && (
<> <>
<Td>{item?.limit?.QPM || '-'}</Td> <Td>{item?.limit?.QPM || '-'}</Td>
<Td>
{item?.limit?.expiredTime
? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
: '-'}
</Td>
<Th>{item?.limit?.hookUrl ? '✔' : '✖'}</Th> <Th>{item?.limit?.hookUrl ? '✔' : '✖'}</Th>
</> </>
)} )}
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : t('common.Un used')}</Td> <Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : t('common.Un used')}</Td>
<Td display={'flex'} alignItems={'center'}> <Td display={'flex'} alignItems={'center'}>
<Button <Button
onClick={() => setSelectedLinkData(item)} onClick={() => setSelectedLinkData(item as OutLinkSchema)}
size={'sm'} size={'sm'}
mr={3} mr={3}
variant={'whitePrimary'} variant={'whitePrimary'}
@ -201,7 +212,7 @@ const Share = ({ appId }: { appId: string }) => {
{!!editLinkData && ( {!!editLinkData && (
<EditLinkModal <EditLinkModal
appId={appId} appId={appId}
type={'share'} type={PublishChannelEnum.share}
defaultData={editLinkData} defaultData={editLinkData}
onCreate={(id) => { onCreate={(id) => {
const url = `${location.origin}/chat/share?shareId=${id}`; const url = `${location.origin}/chat/share?shareId=${id}`;
@ -242,7 +253,7 @@ function EditLinkModal({
onEdit onEdit
}: { }: {
appId: string; appId: string;
type: `${OutLinkTypeEnum}`; type: PublishChannelEnum;
defaultData: OutLinkEditType; defaultData: OutLinkEditType;
onClose: () => void; onClose: () => void;
onCreate: (id: string) => void; onCreate: (id: string) => void;
@ -297,6 +308,22 @@ function EditLinkModal({
</Flex> </Flex>
{feConfigs?.isPlus && ( {feConfigs?.isPlus && (
<> <>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('common.Expired Time')}
</Flex>
<Input
type="datetime-local"
defaultValue={
defaultData.limit?.expiredTime
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
: ''
}
onChange={(e) => {
setValue('limit.expiredTime', new Date(e.target.value));
}}
/>
</Flex>
<Flex alignItems={'center'} mt={4}> <Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}> <Flex flex={'0 0 90px'} alignItems={'center'}>
QPM QPM
@ -330,22 +357,7 @@ function EditLinkModal({
})} })}
/> />
</Flex> </Flex>
<Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}>
{t('common.Expired Time')}
</Flex>
<Input
type="datetime-local"
defaultValue={
defaultData.limit?.expiredTime
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
: ''
}
onChange={(e) => {
setValue('limit.expiredTime', new Date(e.target.value));
}}
/>
</Flex>
<Flex alignItems={'center'} mt={4}> <Flex alignItems={'center'} mt={4}>
<Flex flex={'0 0 90px'} alignItems={'center'}> <Flex flex={'0 0 90px'} alignItems={'center'}>
{t('outlink.token auth')} {t('outlink.token auth')}

View File

@ -0,0 +1,64 @@
import React, { useRef, useState } from 'react';
import { Box, useTheme } from '@chakra-ui/react';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import dynamic from 'next/dynamic';
import MyRadio from '@/components/common/MyRadio';
import { useTranslation } from 'next-i18next';
import Link from './Link';
const API = dynamic(() => import('./API'));
const FeiShu = dynamic(() => import('./FeiShu'));
const OutLink = ({ appId }: { appId: string }) => {
const { t } = useTranslation();
const theme = useTheme();
const publishList = useRef([
{
icon: '/imgs/modal/shareFill.svg',
title: t('core.app.Share link'),
desc: t('core.app.Share link desc'),
value: PublishChannelEnum.share
},
{
icon: 'support/outlink/apikeyFill',
title: t('core.app.Api request'),
desc: t('core.app.Api request desc'),
value: PublishChannelEnum.apikey
}
// {
// icon: 'core/app/publish/lark',
// title: t('core.app.publish.Fei shu bot'),
// desc: t('core.app.publish.Fei Shu Bot Desc'),
// value: PublishChannelEnum.feishu
// }
]);
const [linkType, setLinkType] = useState<PublishChannelEnum>(PublishChannelEnum.share);
return (
<Box pt={[1, 5]}>
<Box fontWeight={'bold'} fontSize={['md', 'xl']} mb={2} px={[4, 8]}>
{t('core.app.navbar.Publish app')}
</Box>
<Box pb={[5, 7]} px={[4, 8]} borderBottom={theme.borders.base}>
<MyRadio
gridTemplateColumns={['repeat(1,1fr)', 'repeat(auto-fill, minmax(0, 300px))']}
iconSize={'20px'}
list={publishList.current}
value={linkType}
onChange={(e) => setLinkType(e as PublishChannelEnum)}
/>
</Box>
{linkType === PublishChannelEnum.share && (
<Link appId={appId} type={PublishChannelEnum.share} />
)}
{linkType === PublishChannelEnum.apikey && <API appId={appId} />}
{linkType === PublishChannelEnum.feishu && <FeiShu appId={appId} />}
</Box>
);
};
export default OutLink;

View File

@ -1,11 +1,9 @@
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { Box, Flex, IconButton } from '@chakra-ui/react'; import { Box, Flex, IconButton } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef } from 'react';
import ChatBox from '@/components/ChatBox'; import ChatBox from '@/components/ChatBox';
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d'; import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { streamFetch } from '@/web/common/api/fetch'; import { streamFetch } from '@/web/common/api/fetch';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
@ -13,27 +11,41 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { getGuideModule } from '@fastgpt/global/core/workflow/utils'; import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils'; import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { import {
getDefaultEntryNodeIds, getDefaultEntryNodeIds,
initWorkflowEdgeStatus, initWorkflowEdgeStatus,
storeNodes2RuntimeNodes storeNodes2RuntimeNodes
} from '@fastgpt/global/core/workflow/runtime/utils'; } from '@fastgpt/global/core/workflow/runtime/utils';
import { useCreation, useMemoizedFn, useSafeState } from 'ahooks';
import { UseFormReturn } from 'react-hook-form';
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { form2AppWorkflow } from '@/web/core/app/utils';
const ChatTest = ({ appId }: { appId: string }) => { const ChatTest = ({
editForm,
appId
}: {
editForm: UseFormReturn<AppSimpleEditFormType, any>;
appId: string;
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { userInfo } = useUserStore(); const { userInfo } = useUserStore();
const { appDetail } = useAppStore();
const ChatBoxRef = useRef<ComponentRef>(null); const ChatBoxRef = useRef<ComponentRef>(null);
const [workflowData, setWorkflowData] = useState<{ const { appDetail } = useAppStore();
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
}>({
nodes: [],
edges: []
});
const startChat = useCallback( const { watch } = editForm;
const [workflowData, setWorkflowData] = useSafeState({
nodes: appDetail.modules || [],
edges: appDetail.edges || []
});
const userGuideModule = useCreation(
() => getGuideModule(workflowData.nodes),
[workflowData.nodes]
);
const startChat = useMemoizedFn(
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => { async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
if (!workflowData) return Promise.reject('workflowData is empty'); if (!workflowData) return Promise.reject('workflowData is empty');
@ -72,8 +84,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
}); });
return { responseText, responseData }; return { responseText, responseData };
}, }
[workflowData, appId, appDetail.name]
); );
const resetChatBox = useCallback(() => { const resetChatBox = useCallback(() => {
@ -82,12 +93,15 @@ const ChatTest = ({ appId }: { appId: string }) => {
}, []); }, []);
useEffect(() => { useEffect(() => {
resetChatBox(); const wat = watch((data) => {
setWorkflowData({ const { nodes, edges } = form2AppWorkflow(data as AppSimpleEditFormType);
nodes: appDetail.modules || [], setWorkflowData({ nodes, edges });
edges: appDetail.edges || []
}); });
}, [appDetail, resetChatBox]);
return () => {
wat.unsubscribe();
};
}, []);
return ( return (
<Flex <Flex
@ -124,7 +138,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
appAvatar={appDetail.avatar} appAvatar={appDetail.avatar}
userAvatar={userInfo?.avatar} userAvatar={userInfo?.avatar}
showMarkIcon showMarkIcon
userGuideModule={getGuideModule(workflowData.nodes)} userGuideModule={userGuideModule}
showFileSelector={checkChatSupportSelectFileByModules(workflowData.nodes)} showFileSelector={checkChatSupportSelectFileByModules(workflowData.nodes)}
onStartChat={startChat} onStartChat={startChat}
onDelMessage={() => {}} onDelMessage={() => {}}

View File

@ -1,10 +1,8 @@
import React, { useMemo, useState, useTransition } from 'react'; import React, { useMemo, useTransition } from 'react';
import { Box, Flex, Grid, BoxProps, useTheme, useDisclosure, Button } from '@chakra-ui/react'; import { Box, Flex, Grid, BoxProps, useTheme, useDisclosure, Button } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { AddIcon, QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons'; import { AddIcon, QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
import { useForm, useFieldArray } from 'react-hook-form'; import { useFieldArray, UseFormReturn } from 'react-hook-form';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { appWorkflow2Form, getDefaultAppForm } from '@fastgpt/global/core/app/utils';
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d'; import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import { welcomeTextTip } from '@fastgpt/global/core/workflow/template/tip'; import { welcomeTextTip } from '@fastgpt/global/core/workflow/template/tip';
import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useRequest } from '@fastgpt/web/hooks/useRequest';
@ -51,26 +49,24 @@ const LabelStyles: BoxProps = {
}; };
const EditForm = ({ const EditForm = ({
editForm,
divRef, divRef,
isSticky isSticky
}: { }: {
editForm: UseFormReturn<AppSimpleEditFormType, any>;
divRef: React.RefObject<HTMLDivElement>; divRef: React.RefObject<HTMLDivElement>;
isSticky: boolean; isSticky: boolean;
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const router = useRouter(); const router = useRouter();
const { t } = useTranslation(); const { t } = useTranslation();
const { appDetail, publishApp } = useAppStore(); const { publishApp, appDetail } = useAppStore();
const { loadAllDatasets, allDatasets } = useDatasetStore(); const { allDatasets } = useDatasetStore();
const { isPc, llmModelList } = useSystemStore(); const { llmModelList } = useSystemStore();
const [refresh, setRefresh] = useState(false);
const [, startTst] = useTransition(); const [, startTst] = useTransition();
const { setValue, getValues, reset, handleSubmit, control, watch } = const { setValue, getValues, handleSubmit, control, watch } = editForm;
useForm<AppSimpleEditFormType>({
defaultValues: getDefaultAppForm()
});
const { fields: datasets, replace: replaceKbList } = useFieldArray({ const { fields: datasets, replace: replaceKbList } = useFieldArray({
control, control,
@ -107,6 +103,9 @@ const EditForm = ({
[t, variables] [t, variables]
); );
const searchMode = watch('dataset.searchMode'); const searchMode = watch('dataset.searchMode');
const tts = getValues('userGuide.tts');
const whisperConfig = getValues('userGuide.whisper');
const postQuestionGuide = getValues('userGuide.questionGuide');
const selectDatasets = useMemo( const selectDatasets = useMemo(
() => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)), () => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)),
@ -118,7 +117,7 @@ const EditForm = ({
}, [selectLLMModel, llmModelList]); }, [selectLLMModel, llmModelList]);
/* on save app */ /* on save app */
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({ const { mutate: onSubmitPublish, isLoading: isSaving } = useRequest({
mutationFn: async (data: AppSimpleEditFormType) => { mutationFn: async (data: AppSimpleEditFormType) => {
const { nodes, edges } = form2AppWorkflow(data); const { nodes, edges } = form2AppWorkflow(data);
@ -132,22 +131,6 @@ const EditForm = ({
errorToast: t('common.Save Failed') errorToast: t('common.Save Failed')
}); });
useQuery(
['init', appDetail],
() => {
const formatVal = appWorkflow2Form({
nodes: appDetail.modules
});
reset(formatVal);
setRefresh(!refresh);
return formatVal;
},
{
enabled: !!appDetail._id
}
);
useQuery(['loadAllDatasets'], loadAllDatasets);
return ( return (
<Box> <Box>
{/* title */} {/* title */}
@ -177,16 +160,23 @@ const EditForm = ({
<Button <Button
isLoading={isSaving} isLoading={isSaving}
size={['sm', 'md']} size={['sm', 'md']}
leftIcon={
appDetail.type === AppTypeEnum.simple ? (
<MyIcon name={'common/publishFill'} w={['14px', '16px']} />
) : undefined
}
variant={appDetail.type === AppTypeEnum.simple ? 'primary' : 'whitePrimary'} variant={appDetail.type === AppTypeEnum.simple ? 'primary' : 'whitePrimary'}
onClick={() => { onClick={() => {
if (appDetail.type !== AppTypeEnum.simple) { if (appDetail.type !== AppTypeEnum.simple) {
openConfirmSave(handleSubmit((data) => onSubmitSave(data)))(); openConfirmSave(handleSubmit((data) => onSubmitPublish(data)))();
} else { } else {
handleSubmit((data) => onSubmitSave(data))(); handleSubmit((data) => onSubmitPublish(data))();
} }
}} }}
> >
{isPc ? t('core.app.Save and preview') : t('common.Save')} {appDetail.type !== AppTypeEnum.simple
? t('core.app.Change to simple mode')
: t('core.app.Publish')}
</Button> </Button>
</Flex> </Flex>
@ -272,7 +262,7 @@ const EditForm = ({
{t('common.Params')} {t('common.Params')}
</Button> </Button>
</Flex> </Flex>
{getValues('dataset.datasets').length > 0 && ( {datasetSearchSetting.datasets?.length > 0 && (
<Box my={3}> <Box my={3}>
<SearchParamsTip <SearchParamsTip
searchMode={searchMode} searchMode={searchMode}
@ -382,7 +372,6 @@ const EditForm = ({
variables={variables} variables={variables}
onChange={(e) => { onChange={(e) => {
setValue('userGuide.variables', e); setValue('userGuide.variables', e);
setRefresh(!refresh);
}} }}
/> />
</Box> </Box>
@ -411,10 +400,9 @@ const EditForm = ({
{/* tts */} {/* tts */}
<Box {...BoxStyles}> <Box {...BoxStyles}>
<TTSSelect <TTSSelect
value={getValues('userGuide.tts')} value={tts}
onChange={(e) => { onChange={(e) => {
setValue('userGuide.tts', e); setValue('userGuide.tts', e);
setRefresh((state) => !state);
}} }}
/> />
</Box> </Box>
@ -422,11 +410,10 @@ const EditForm = ({
{/* whisper */} {/* whisper */}
<Box {...BoxStyles}> <Box {...BoxStyles}>
<WhisperConfig <WhisperConfig
isOpenAudio={getValues('userGuide.tts').type !== TTSTypeEnum.none} isOpenAudio={tts.type !== TTSTypeEnum.none}
value={getValues('userGuide.whisper')} value={whisperConfig}
onChange={(e) => { onChange={(e) => {
setValue('userGuide.whisper', e); setValue('userGuide.whisper', e);
setRefresh((state) => !state);
}} }}
/> />
</Box> </Box>
@ -434,12 +421,10 @@ const EditForm = ({
{/* question guide */} {/* question guide */}
<Box {...BoxStyles} borderBottom={'none'}> <Box {...BoxStyles} borderBottom={'none'}>
<QGSwitch <QGSwitch
isChecked={getValues('userGuide.questionGuide')} isChecked={postQuestionGuide}
size={'lg'} size={'lg'}
onChange={(e) => { onChange={(e) => {
const value = e.target.checked; setValue('userGuide.questionGuide', e.target.checked);
setValue('userGuide.questionGuide', value);
setRefresh((state) => !state);
}} }}
/> />
</Box> </Box>
@ -468,8 +453,6 @@ const EditForm = ({
...getValues('dataset'), ...getValues('dataset'),
...e ...e
}); });
setRefresh((state) => !state);
}} }}
/> />
)} )}

View File

@ -2,14 +2,33 @@ import React from 'react';
import { Box, Grid } from '@chakra-ui/react'; import { Box, Grid } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useSticky } from '@/web/common/hooks/useSticky'; 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 ChatTest from './ChatTest';
import AppCard from './AppCard'; import AppCard from './AppCard';
import EditForm from './EditForm'; import EditForm from './EditForm';
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
const SimpleEdit = ({ appId }: { appId: string }) => { const SimpleEdit = ({ appId }: { appId: string }) => {
const { isPc } = useSystemStore(); const { isPc } = useSystemStore();
const { parentRef, divRef, isSticky } = useSticky(); const { parentRef, divRef, isSticky } = useSticky();
const { loadAllDatasets } = useDatasetStore();
const { appDetail } = useAppStore();
const editForm = useForm<AppSimpleEditFormType>({
defaultValues: appWorkflow2Form({
nodes: appDetail.modules
})
});
// show selected dataset
useMount(() => {
loadAllDatasets();
});
return ( return (
<Grid gridTemplateColumns={['1fr', '560px 1fr']} h={'100%'}> <Grid gridTemplateColumns={['1fr', '560px 1fr']} h={'100%'}>
@ -25,10 +44,10 @@ const SimpleEdit = ({ appId }: { appId: string }) => {
<AppCard appId={appId} /> <AppCard appId={appId} />
<Box mt={2}> <Box mt={2}>
<EditForm divRef={divRef} isSticky={isSticky} /> <EditForm editForm={editForm} divRef={divRef} isSticky={isSticky} />
</Box> </Box>
</Box> </Box>
{isPc && <ChatTest appId={appId} />} {isPc && <ChatTest editForm={editForm} appId={appId} />}
</Grid> </Grid>
); );
}; };

View File

@ -21,7 +21,7 @@ import { useTranslation } from 'next-i18next';
const FlowEdit = dynamic(() => import('./components/FlowEdit'), { const FlowEdit = dynamic(() => import('./components/FlowEdit'), {
loading: () => <Loading /> loading: () => <Loading />
}); });
const OutLink = dynamic(() => import('./components/OutLink'), {}); const Publish = dynamic(() => import('./components/Publish'), {});
const Logs = dynamic(() => import('./components/Logs'), {}); const Logs = dynamic(() => import('./components/Logs'), {});
enum TabEnum { enum TabEnum {
@ -39,7 +39,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const { feConfigs } = useSystemStore(); const { feConfigs } = useSystemStore();
const { toast } = useToast(); const { toast } = useToast();
const { appId } = router.query as { appId: string }; const { appId } = router.query as { appId: string };
const { appDetail, loadAppDetail, clearAppModules } = useAppStore(); const { appDetail, loadAppDetail } = useAppStore();
const setCurrentTab = useCallback( const setCurrentTab = useCallback(
(tab: `${TabEnum}`) => { (tab: `${TabEnum}`) => {
@ -82,7 +82,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]); const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]);
useQuery([appId], () => loadAppDetail(appId, true), { const { isSuccess, isLoading } = useQuery([appId], () => loadAppDetail(appId, true), {
onError(err: any) { onError(err: any) {
toast({ toast({
title: err?.message || t('core.app.error.Get app failed'), title: err?.message || t('core.app.error.Get app failed'),
@ -100,7 +100,8 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
<Head> <Head>
<title>{appDetail.name}</title> <title>{appDetail.name}</title>
</Head> </Head>
<PageContainer> <PageContainer isLoading={isLoading}>
{isSuccess && (
<Flex flexDirection={['column', 'row']} h={'100%'}> <Flex flexDirection={['column', 'row']} h={'100%'}>
{/* pc tab */} {/* pc tab */}
<Box <Box
@ -179,9 +180,10 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
<FlowEdit app={appDetail} onClose={onCloseFlowEdit} /> <FlowEdit app={appDetail} onClose={onCloseFlowEdit} />
)} )}
{currentTab === TabEnum.logs && <Logs appId={appId} />} {currentTab === TabEnum.logs && <Logs appId={appId} />}
{currentTab === TabEnum.publish && <OutLink appId={appId} />} {currentTab === TabEnum.publish && <Publish appId={appId} />}
</Box> </Box>
</Flex> </Flex>
)}
</PageContainer> </PageContainer>
</> </>
); );

View File

@ -11,14 +11,12 @@ import { flowNode2StoreNodes } from '@/components/core/workflow/utils';
import { putUpdatePlugin } from '@/web/core/plugin/api'; import { putUpdatePlugin } from '@/web/core/plugin/api';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import MyMenu from '@fastgpt/web/components/common/MyMenu'; import MyMenu from '@fastgpt/web/components/common/MyMenu';
import {
getWorkflowStore,
useFlowProviderStore
} from '@/components/core/workflow/Flow/FlowProvider';
import { import {
checkWorkflowNodeAndConnection, checkWorkflowNodeAndConnection,
filterSensitiveNodesData filterSensitiveNodesData
} from '@/web/core/workflow/utils'; } 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')); const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
@ -29,11 +27,13 @@ const Header = ({ plugin, onClose }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
const { copyData } = useCopyData(); 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 { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const flowData2StoreDataAndCheck = useCallback(async () => { const flowData2StoreDataAndCheck = useCallback(async () => {
const { nodes } = await getWorkflowStore(); const { nodes } = await getWorkflowStore();
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges }); const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
if (!checkResults) { if (!checkResults) {
const storeNodes = flowNode2StoreNodes({ nodes, edges }); const storeNodes = flowNode2StoreNodes({ nodes, edges });

View File

@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Header from './Header'; import Header from './Header';
import Flow from '@/components/core/workflow/Flow'; 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 { pluginSystemModuleTemplates } from '@fastgpt/global/core/workflow/template/constants';
import { serviceSideProps } from '@/web/common/utils/i18n'; import { serviceSideProps } from '@/web/common/utils/i18n';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
@ -14,6 +13,8 @@ import { useTranslation } from 'next-i18next';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { v1Workflow2V2 } from '@/web/core/workflow/adapt'; import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload'; 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 }; type Props = { pluginId: string };
@ -21,7 +22,7 @@ const Render = ({ pluginId }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
const { toast } = useToast(); const { toast } = useToast();
const { initData } = useFlowProviderStore(); const initData = useContextSelector(WorkflowContext, (v) => v.initData);
const { data: pluginDetail } = useQuery( const { data: pluginDetail } = useQuery(
['getOnePlugin', pluginId], ['getOnePlugin', pluginId],
@ -78,9 +79,11 @@ const Render = ({ pluginId }: Props) => {
export default function FlowEdit(props: any) { export default function FlowEdit(props: any) {
return ( return (
<FlowProvider mode={'plugin'} basicNodeTemplates={pluginSystemModuleTemplates}> <WorkflowContextProvider
value={{ mode: 'plugin', basicNodeTemplates: pluginSystemModuleTemplates }}
>
<Render {...props} /> <Render {...props} />
</FlowProvider> </WorkflowContextProvider>
); );
} }

View File

@ -6,6 +6,7 @@ import { TTSTypeEnum } from '@/constants/app';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d'; import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
import { getToken } from '@/web/support/user/auth'; import { getToken } from '@/web/support/user/auth';
import { useMount } from 'ahooks';
const contentType = 'audio/mpeg'; const contentType = 'audio/mpeg';
const splitMarker = 'SPLIT_MARKER'; const splitMarker = 'SPLIT_MARKER';
@ -329,7 +330,7 @@ export const useAudioPlay = (props?: OutLinkChatAuthProps & { ttsConfig?: AppTTS
); );
// listen audio status // listen audio status
useEffect(() => { useMount(() => {
const audio = new Audio(); const audio = new Audio();
audioRef.current = audio; audioRef.current = audio;
@ -357,7 +358,7 @@ export const useAudioPlay = (props?: OutLinkChatAuthProps & { ttsConfig?: AppTTS
audio.remove(); audio.remove();
window.removeEventListener('beforeunload', listen); window.removeEventListener('beforeunload', listen);
}; };
}, []); });
return { return {
audio: audioRef.current, audio: audioRef.current,

View File

@ -1,10 +1,11 @@
import { useCallback, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { clientInitData } from '@/web/common/system/staticData'; import { clientInitData } from '@/web/common/system/staticData';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { FastGPTFeConfigsType } from '@fastgpt/global/common/system/types/index.d'; import type { FastGPTFeConfigsType } from '@fastgpt/global/common/system/types/index.d';
import { change2DefaultLng, setLngStore } from '@/web/common/utils/i18n'; import { change2DefaultLng, setLngStore } from '@/web/common/utils/i18n';
import { useMemoizedFn, useMount } from 'ahooks';
export const useInitApp = () => { export const useInitApp = () => {
const router = useRouter(); const router = useRouter();
@ -14,7 +15,7 @@ export const useInitApp = () => {
const [scripts, setScripts] = useState<FastGPTFeConfigsType['scripts']>([]); const [scripts, setScripts] = useState<FastGPTFeConfigsType['scripts']>([]);
const [title, setTitle] = useState(process.env.SYSTEM_NAME || 'AI'); const [title, setTitle] = useState(process.env.SYSTEM_NAME || 'AI');
const initFetch = useCallback(async () => { const initFetch = useMemoizedFn(async () => {
const { const {
feConfigs: { scripts, isPlus, show_git, systemTitle } feConfigs: { scripts, isPlus, show_git, systemTitle }
} = await clientInitData(); } = await clientInitData();
@ -35,18 +36,18 @@ export const useInitApp = () => {
setScripts(scripts || []); setScripts(scripts || []);
setInitd(); setInitd();
}, [loadGitStar, setInitd]); });
const initUserLanguage = useCallback(() => { const initUserLanguage = useMemoizedFn(() => {
// get default language // get default language
const targetLng = change2DefaultLng(i18n.language); const targetLng = change2DefaultLng(i18n.language);
if (targetLng) { if (targetLng) {
setLngStore(targetLng); setLngStore(targetLng);
router.replace(router.asPath, undefined, { locale: targetLng }); router.replace(router.asPath, undefined, { locale: targetLng });
} }
}, []); });
useEffect(() => { useMount(() => {
initFetch(); initFetch();
initUserLanguage(); initUserLanguage();
@ -67,7 +68,7 @@ export const useInitApp = () => {
return () => { return () => {
window.removeEventListener('error', errorTrack); window.removeEventListener('error', errorTrack);
}; };
}, []); });
useEffect(() => { useEffect(() => {
hiId && localStorage.setItem('inviterId', hiId); hiId && localStorage.setItem('inviterId', hiId);

View File

@ -704,7 +704,7 @@ export function form2AppWorkflow(data: AppSimpleEditFormType): WorkflowType {
...pluginTool.map((tool) => tool.edges).flat() ...pluginTool.map((tool) => tool.edges).flat()
] ]
}; };
console.log(config);
return config; return config;
} }
@ -724,23 +724,28 @@ export const getSystemVariables = (t: TFunction): EditorVariablePickerType[] =>
return [ return [
{ {
key: 'appId', key: 'appId',
label: t('core.module.http.AppId') label: t('core.module.http.AppId'),
valueType: WorkflowIOValueTypeEnum.string
}, },
{ {
key: 'chatId', key: 'chatId',
label: t('core.module.http.ChatId') label: t('core.module.http.ChatId'),
valueType: WorkflowIOValueTypeEnum.string
}, },
{ {
key: 'responseChatItemId', key: 'responseChatItemId',
label: t('core.module.http.ResponseChatItemId') label: t('core.module.http.ResponseChatItemId'),
valueType: WorkflowIOValueTypeEnum.string
}, },
{ {
key: 'histories', key: 'histories',
label: t('core.module.http.Histories') label: t('core.module.http.Histories'),
valueType: WorkflowIOValueTypeEnum.chatHistory
}, },
{ {
key: 'cTime', key: 'cTime',
label: t('core.module.http.Current time') label: t('core.module.http.Current time'),
valueType: WorkflowIOValueTypeEnum.string
} }
]; ];
}; };

View File

@ -16,7 +16,7 @@ import {
StoreNodeItemType StoreNodeItemType
} from '@fastgpt/global/core/workflow/type'; } from '@fastgpt/global/core/workflow/type';
import { VARIABLE_NODE_ID } from '@fastgpt/global/core/workflow/constants'; 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 { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { LLMModelTypeEnum } from '@fastgpt/global/core/ai/constants'; import { LLMModelTypeEnum } from '@fastgpt/global/core/ai/constants';
import { import {
@ -24,8 +24,10 @@ import {
FlowNodeOutputItemType FlowNodeOutputItemType
} from '@fastgpt/global/core/workflow/type/io'; } from '@fastgpt/global/core/workflow/type/io';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants'; 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 = { const template: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.globalVariable, id: FlowNodeTypeEnum.globalVariable,
templateType: FlowNodeTemplateTypeEnum.other, templateType: FlowNodeTemplateTypeEnum.other,
@ -41,17 +43,17 @@ export const systemConfigNode2VariableNode = (node: FlowNodeItemType) => {
outputs: [] outputs: []
}; };
const { variableModules } = splitGuideModule(node); const globalVariables = getWorkflowGlobalVariables(nodes, t);
const variableNode: FlowNodeItemType = { const variableNode: FlowNodeItemType = {
nodeId: VARIABLE_NODE_ID, nodeId: VARIABLE_NODE_ID,
...template, ...template,
outputs: variableModules.map((item) => ({ outputs: globalVariables.map((item) => ({
id: item.key, id: item.key,
type: FlowNodeOutputTypeEnum.dynamic, type: FlowNodeOutputTypeEnum.static,
label: item.label, label: item.label,
key: item.key, key: item.key,
valueType: WorkflowIOValueTypeEnum.any valueType: item.valueType || WorkflowIOValueTypeEnum.any
})) }))
}; };

View File

@ -13,9 +13,17 @@ import {
import { EmptyNode } from '@fastgpt/global/core/workflow/template/system/emptyNode'; import { EmptyNode } from '@fastgpt/global/core/workflow/template/system/emptyNode';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge'; import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { getNanoid } from '@fastgpt/global/common/string/tools'; 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 { VARIABLE_NODE_ID } from '@fastgpt/global/core/workflow/constants';
import { NodeInputKeyEnum, NodeOutputKeyEnum } 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 = ({ export const nodeTemplate2FlowNode = ({
template, template,
@ -97,11 +105,13 @@ export const storeEdgesRenderEdge = ({ edge }: { edge: StoreEdgeItemType }) => {
export const computedNodeInputReference = ({ export const computedNodeInputReference = ({
nodeId, nodeId,
nodes, nodes,
edges edges,
t
}: { }: {
nodeId: string; nodeId: string;
nodes: FlowNodeItemType[]; nodes: FlowNodeItemType[];
edges: Edge[]; edges: Edge[];
t: TFunction;
}) => { }) => {
// get current node // get current node
const node = nodes.find((item) => item.nodeId === nodeId); const node = nodes.find((item) => item.nodeId === nodeId);
@ -126,14 +136,7 @@ export const computedNodeInputReference = ({
}; };
findSourceNode(nodeId); findSourceNode(nodeId);
// add system config node sourceNodes.unshift(getGlobalVariableNode(nodes, t));
const systemConfigNode = nodes.find(
(item) => item.flowNodeType === FlowNodeTypeEnum.systemConfig
);
if (systemConfigNode) {
sourceNodes.unshift(systemConfigNode2VariableNode(systemConfigNode));
}
return sourceNodes; return sourceNodes;
}; };
@ -232,3 +235,17 @@ export const filterSensitiveNodesData = (nodes: StoreNodeItemType[]) => {
}); });
return cloneNodes; 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];
};

View File

@ -1,26 +1,40 @@
import { GET, POST, DELETE } from '@/web/common/api/request'; import { GET, POST, DELETE } from '@/web/common/api/request';
import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d'; import type { OutLinkEditType, OutLinkSchema } from '@fastgpt/global/support/outLink/type.d';
/** // create a shareChat
* create a shareChat export function createShareChat<T>(
*/ data: OutLinkEditType<T> & {
export const createShareChat = (
data: OutLinkEditType & {
appId: string; appId: string;
type: OutLinkSchema['type']; type: OutLinkSchema['type'];
} }
) => POST<string>(`/support/outLink/create`, data); ) {
return POST<string>(`/support/outLink/create`, data);
}
export const putShareChat = (data: OutLinkEditType) => export const putShareChat = (data: OutLinkEditType) =>
POST<string>(`/support/outLink/update`, data); POST<string>(`/support/outLink/update`, data);
/** // get shareChat
* get shareChat export function getShareChatList<T>(data: { appId: string; type: OutLinkSchema<T>['type'] }) {
*/ return GET<OutLinkSchema<T>[]>(`/support/outLink/list`, data);
export const getShareChatList = (appId: string) => }
GET<OutLinkSchema[]>(`/support/outLink/list`, { appId });
/** // delete a shareChat
* delete a shareChat export function delShareChatById(id: string) {
*/ return DELETE(`/support/outLink/delete?id=${id}`);
export const delShareChatById = (id: string) => DELETE(`/support/outLink/delete?id=${id}`); }
// update a shareChat
export function updateShareChat<T>(data: OutLinkEditType<T>) {
return POST<string>(`/support/outLink/update`, data);
}
// /**
// * create a shareChat
// */
// export const createWecomLinkChat = (
// data: OutLinkConfigEditType & {
// appId: string;
// type: OutLinkSchema['type'];
// }
// ) => POST<string>(`/support/outLink/create`, data);

View File

@ -57,7 +57,7 @@ export const useSendCode = () => {
} }
setCodeSending(false); setCodeSending(false);
}, },
[codeCountDown, toast] [codeCountDown, feConfigs?.googleClientVerKey, toast]
); );
return { return {