mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
feat: http modules
This commit is contained in:
BIN
client/public/imgs/module/http.png
Normal file
BIN
client/public/imgs/module/http.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
@@ -9,7 +9,9 @@
|
||||
"Confirm Save App Tip": "After saving, the advanced orchestration configuration will be overwritten. Make sure that the application does not use advanced orchestration.",
|
||||
"Connection is invalid": "Connecting is invalid",
|
||||
"Connection type is different": "Connection type is different",
|
||||
"My Apps": "My Apps"
|
||||
"Input Field Settings": "Input Field Settings",
|
||||
"My Apps": "My Apps",
|
||||
"Output Field Settings": "Output Field Settings"
|
||||
},
|
||||
"chat": {
|
||||
"Confirm to clear history": "Confirm to clear history?",
|
||||
@@ -39,28 +41,28 @@
|
||||
"Tools": "Tools"
|
||||
},
|
||||
"user": {
|
||||
"Account": "Account",
|
||||
"Application Name": "Application Name",
|
||||
"Avatar": "Avatar",
|
||||
"Balance": "Balance",
|
||||
"Bill Detail": "Bill Detail",
|
||||
"Change": "Change",
|
||||
"Notice": "Notice",
|
||||
"Old password is error": "Old password is error",
|
||||
"OpenAI Account Setting": "OpenAI Account Setting",
|
||||
"Password": "Password",
|
||||
"Pay": "Pay",
|
||||
"Personal Information": "Personal",
|
||||
"Recharge Record": "Recharge",
|
||||
"Replace": "Replace",
|
||||
"Set OpenAI Account Failed": "Set OpenAI account failed",
|
||||
"Sign Out": "Sign Out",
|
||||
"Source": "Source",
|
||||
"Time": "Time",
|
||||
"Total Amount": "Total Amount",
|
||||
"Update Password": "Update Password",
|
||||
"Update password failed": "Update password failed",
|
||||
"Update password succseful": "Update password succseful",
|
||||
"Personal Information": "Personal",
|
||||
"Usage Record": "Usage",
|
||||
"Recharge Record": "Recharge",
|
||||
"Notice": "Notice",
|
||||
"Sign Out": "Sign Out",
|
||||
"Avatar": "Avatar",
|
||||
"Account": "Account",
|
||||
"Balance": "Balance",
|
||||
"Time": "Time",
|
||||
"Source": "Source",
|
||||
"Application Name": "Application Name",
|
||||
"Total Amount": "Total Amount",
|
||||
"Change": "Change",
|
||||
"Password": "Password",
|
||||
"Replace": "Replace"
|
||||
"Usage Record": "Usage"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,7 +9,9 @@
|
||||
"Confirm Save App Tip": "保存后将会覆盖高级编排配置,请确保该应用未使用高级编排功能。",
|
||||
"Connection is invalid": "连接无效",
|
||||
"Connection type is different": "连接的类型不一致",
|
||||
"My Apps": "我的应用"
|
||||
"Input Field Settings": "输入字段编辑",
|
||||
"My Apps": "我的应用",
|
||||
"Output Field Settings": "输出字段编辑"
|
||||
},
|
||||
"chat": {
|
||||
"Confirm to clear history": "确认清空该应用的聊天记录?",
|
||||
@@ -39,28 +41,28 @@
|
||||
"Tools": "工具"
|
||||
},
|
||||
"user": {
|
||||
"Account": "账号",
|
||||
"Application Name": "应用名",
|
||||
"Avatar": "头像",
|
||||
"Balance": "余额",
|
||||
"Bill Detail": "账单详情",
|
||||
"Change": "变更",
|
||||
"Notice": "通知",
|
||||
"Old password is error": "旧密码错误",
|
||||
"OpenAI Account Setting": "OpenAI 账号配置",
|
||||
"Password": "密码",
|
||||
"Pay": "充值",
|
||||
"Personal Information": "个人信息",
|
||||
"Recharge Record": "充值记录",
|
||||
"Replace": "更换",
|
||||
"Set OpenAI Account Failed": "设置 OpenAI 账号异常",
|
||||
"Sign Out": "登出",
|
||||
"Source": "来源",
|
||||
"Time": "时间",
|
||||
"Total Amount": "总金额",
|
||||
"Update Password": "修改密码",
|
||||
"Update password failed": "修改密码异常",
|
||||
"Update password succseful": "修改密码成功",
|
||||
"Personal Information": "个人信息",
|
||||
"Usage Record": "使用记录",
|
||||
"Recharge Record": "充值记录",
|
||||
"Notice": "通知",
|
||||
"Sign Out": "登出",
|
||||
"Avatar": "头像",
|
||||
"Account": "账号",
|
||||
"Balance": "余额",
|
||||
"Time": "时间",
|
||||
"Source": "来源",
|
||||
"Application Name": "应用名",
|
||||
"Total Amount": "总金额",
|
||||
"Change": "变更",
|
||||
"Password": "密码",
|
||||
"Replace": "更换"
|
||||
"Usage Record": "使用记录"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1
client/src/components/Icon/icons/circle/add.svg
Normal file
1
client/src/components/Icon/icons/circle/add.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1690977807626" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9878" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M512 1024a512 512 0 1 1 512-512 512 512 0 0 1-512 512zM512 73.142857a438.857143 438.857143 0 1 0 438.857143 438.857143 438.857143 438.857143 0 0 0-438.857143-438.857143z" p-id="9879"></path><path d="M256 475.428571h512a36.571429 36.571429 0 0 1 36.571429 36.571429 36.571429 36.571429 0 0 1-36.571429 36.571429h-512A36.571429 36.571429 0 0 1 219.428571 512a36.571429 36.571429 0 0 1 36.571429-36.571429z" p-id="9880"></path><path d="M548.571429 256v512a36.571429 36.571429 0 0 1-73.142858 0v-512a36.571429 36.571429 0 0 1 73.142858 0z" p-id="9881"></path></svg>
|
After Width: | Height: | Size: 870 B |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1682079057126" class="icon" viewBox="0 0 1322 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2677" xmlns:xlink="http://www.w3.org/1999/xlink" width="36.1484375" height="28"><path d="M952.04654459 837.88839531H336.95615443A113.52706888 113.52706888 0 0 1 223.61160468 724.54384556v-419.79462838h728.43493991a113.52706888 113.52706888 0 0 1 113.34454973 113.34454975V724.54384556a113.52706888 113.52706888 0 0 1-113.34454973 113.34454975zM278.36742569 359.13999928v365.03880736a58.77124787 58.77124787 0 0 0 58.58872873 58.58872874h615.09039016a58.77124787 58.77124787 0 0 0 58.58872874-58.58872874V417.72872802a58.77124787 58.77124787 0 0 0-58.58872874-58.58872874z" p-id="2678"></path><path d="M278.36742569 350.37906772H223.61160468V297.44844068A111.51935577 111.51935577 0 0 1 334.94844068 186.11160469h334.01050924a111.51935577 111.51935577 0 0 1 111.33683598 111.33683599v49.09771996h-54.75582101V297.44844068A56.76353475 56.76353475 0 0 0 668.95894991 240.8674257H334.94844068A56.76353475 56.76353475 0 0 0 278.36742569 297.44844068zM1038.19570329 704.83175018H825.92563707A131.59649008 131.59649008 0 0 1 825.92563707 441.63877003h208.43715913v54.75582103H825.92563707a76.84066906 76.84066906 0 0 0 0 153.86385725h212.27006621z" p-id="2679"></path><path d="M889.80742792 600.43065117h-65.34194654a27.37791082 27.37791082 0 0 1 0-54.75582103h65.34194654a27.37791082 27.37791082 0 0 1-1e-8 54.75582103z" p-id="2680"></path></svg>
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -8,7 +8,6 @@ const map = {
|
||||
copy: require('./icons/copy.svg').default,
|
||||
chatSend: require('./icons/chatSend.svg').default,
|
||||
delete: require('./icons/delete.svg').default,
|
||||
withdraw: require('./icons/withdraw.svg').default,
|
||||
stop: require('./icons/stop.svg').default,
|
||||
collectionLight: require('./icons/collectionLight.svg').default,
|
||||
collectionSolid: require('./icons/collectionSolid.svg').default,
|
||||
@@ -72,7 +71,8 @@ const map = {
|
||||
language_en: require('./icons/language/en.svg').default,
|
||||
language_zh: require('./icons/language/zh.svg').default,
|
||||
outlink_share: require('./icons/outlink/share.svg').default,
|
||||
outlink_iframe: require('./icons/outlink/iframe.svg').default
|
||||
outlink_iframe: require('./icons/outlink/iframe.svg').default,
|
||||
addCircle: require('./icons/circle/add.svg').default
|
||||
};
|
||||
|
||||
export type IconName = keyof typeof map;
|
||||
|
@@ -15,7 +15,7 @@ import {
|
||||
Input_Template_TFSwitch,
|
||||
Input_Template_UserChatInput
|
||||
} from './inputTemplate';
|
||||
import { ContextExtractEnum } from './flowField';
|
||||
import { ContextExtractEnum, HttpPropsEnum } from './flowField';
|
||||
|
||||
export const ChatModelSystemTip =
|
||||
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}';
|
||||
@@ -448,6 +448,34 @@ export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
}
|
||||
]
|
||||
};
|
||||
export const HttpModule: FlowModuleTemplateType = {
|
||||
logo: '/imgs/module/http.png',
|
||||
name: 'HTTP模块',
|
||||
intro: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)',
|
||||
description: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)',
|
||||
flowType: FlowModuleTypeEnum.httpRequest,
|
||||
inputs: [
|
||||
{
|
||||
key: HttpPropsEnum.url,
|
||||
value: '',
|
||||
type: FlowInputItemTypeEnum.input,
|
||||
label: '请求地址',
|
||||
description: '请求目标地址',
|
||||
placeholder: 'https://api.fastgpt.run/getInventory',
|
||||
required: true
|
||||
},
|
||||
Input_Template_TFSwitch
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: HttpPropsEnum.finish,
|
||||
label: '请求结束',
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
export const EmptyModule: FlowModuleTemplateType = {
|
||||
logo: '/imgs/module/cq.png',
|
||||
name: '该模块已被移除',
|
||||
@@ -477,7 +505,7 @@ export const ModuleTemplates = [
|
||||
},
|
||||
{
|
||||
label: 'Agent',
|
||||
list: [ClassifyQuestionModule, ContextExtractModule]
|
||||
list: [ClassifyQuestionModule, ContextExtractModule, HttpModule]
|
||||
}
|
||||
];
|
||||
export const ModuleTemplatesFlat = ModuleTemplates.map((templates) => templates.list)?.flat();
|
||||
|
@@ -6,3 +6,10 @@ export enum ContextExtractEnum {
|
||||
failed = 'failed',
|
||||
fields = 'fields'
|
||||
}
|
||||
|
||||
export enum HttpPropsEnum {
|
||||
url = 'url',
|
||||
finish = 'finish',
|
||||
body = 'body',
|
||||
response = 'response'
|
||||
}
|
||||
|
@@ -31,7 +31,8 @@ export enum FlowModuleTypeEnum {
|
||||
tfSwitchNode = 'tfSwitchNode',
|
||||
answerNode = 'answerNode',
|
||||
classifyQuestion = 'classifyQuestion',
|
||||
contentExtract = 'contentExtract'
|
||||
contentExtract = 'contentExtract',
|
||||
httpRequest = 'httpRequest'
|
||||
}
|
||||
|
||||
export enum SpecialInputKeyEnum {
|
||||
|
@@ -3,9 +3,9 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { authUser } from '@/service/utils/auth';
|
||||
import { connectToDatabase, App } from '@/service/mongo';
|
||||
import { AppModuleInputItemType } from '@/types/app';
|
||||
import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
|
||||
import { TaskResponseKeyEnum } from '@/constants/chat';
|
||||
import { FlowInputItemType } from '@/types/flow';
|
||||
|
||||
const chatModelInput = ({
|
||||
model,
|
||||
@@ -21,7 +21,7 @@ const chatModelInput = ({
|
||||
systemPrompt: string;
|
||||
limitPrompt: string;
|
||||
kbList: { kbId: string }[];
|
||||
}): AppModuleInputItemType[] => [
|
||||
}): FlowInputItemType[] => [
|
||||
{
|
||||
key: 'model',
|
||||
value: model,
|
||||
|
@@ -27,7 +27,8 @@ const NodeCQNode = ({
|
||||
CustomComponent={{
|
||||
[SpecialInputKeyEnum.agents]: ({
|
||||
key: agentKey,
|
||||
value: agents = []
|
||||
value: agents = [],
|
||||
...props
|
||||
}: {
|
||||
key: string;
|
||||
value?: ClassifyQuestionAgentItemType[];
|
||||
@@ -50,7 +51,11 @@ const NodeCQNode = ({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: agentKey,
|
||||
value: newInputValue
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: newInputValue
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
@@ -77,8 +82,13 @@ const NodeCQNode = ({
|
||||
);
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: agentKey,
|
||||
value: newVal
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: newVal
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -97,11 +107,16 @@ const NodeCQNode = ({
|
||||
type: FlowOutputItemTypeEnum.hidden,
|
||||
targets: []
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: agentKey,
|
||||
value: newInputValue
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: newInputValue
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
|
@@ -48,33 +48,32 @@ const NodeChat = ({
|
||||
onchange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: inputItem.key,
|
||||
value: e
|
||||
value: {
|
||||
...inputItem,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
|
||||
// update max tokens
|
||||
const model = chatModelList.find((item) => item.model === e);
|
||||
const model =
|
||||
chatModelList.find((item) => item.model === e) || chatModelList[0];
|
||||
if (!model) return;
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: 'maxToken',
|
||||
valueKey: 'markList',
|
||||
value: [
|
||||
{ label: '100', value: 100 },
|
||||
{ label: `${model.contextMaxToken}`, value: model.contextMaxToken }
|
||||
]
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: 'maxToken',
|
||||
valueKey: 'max',
|
||||
value: model.contextMaxToken
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: 'maxToken',
|
||||
valueKey: 'value',
|
||||
value: model.contextMaxToken / 2
|
||||
value: {
|
||||
...inputs.find((input) => input.key === 'maxToken'),
|
||||
markList: [
|
||||
{ label: '100', value: 100 },
|
||||
{ label: `${model.contextMaxToken}`, value: model.contextMaxToken }
|
||||
],
|
||||
max: model.contextMaxToken,
|
||||
value: model.contextMaxToken / 2
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -100,8 +99,12 @@ const NodeChat = ({
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: inputItem.key,
|
||||
value: e
|
||||
value: {
|
||||
...inputItem,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -115,7 +118,11 @@ const NodeChat = ({
|
||||
<>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput flowOutputList={outputs} />
|
||||
<RenderOutput
|
||||
onChangeNode={onChangeNode}
|
||||
moduleId={moduleId}
|
||||
flowOutputList={outputs}
|
||||
/>
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
|
@@ -31,10 +31,9 @@ const NodeExtract = ({
|
||||
flowInputList={inputs}
|
||||
CustomComponent={{
|
||||
[ContextExtractEnum.extractKeys]: ({
|
||||
key,
|
||||
value: extractKeys = []
|
||||
value: extractKeys = [],
|
||||
...props
|
||||
}: {
|
||||
key: string;
|
||||
value?: ContextExtractAgentItemType[];
|
||||
}) => (
|
||||
<Box pt={2}>
|
||||
@@ -97,7 +96,10 @@ const NodeExtract = ({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
value: newInputValue
|
||||
value: {
|
||||
...props,
|
||||
value: newInputValue
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
@@ -121,7 +123,7 @@ const NodeExtract = ({
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput flowOutputList={outputs} />
|
||||
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
|
||||
{!!editExtractFiled && (
|
||||
@@ -160,7 +162,10 @@ const NodeExtract = ({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
value: newInputs
|
||||
value: {
|
||||
...inputs.find((input) => input.key === ContextExtractEnum.extractKeys),
|
||||
value: newInputs
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
|
@@ -18,7 +18,11 @@ const NodeHistory = ({
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput flowOutputList={outputs} />
|
||||
<RenderOutput
|
||||
onChangeNode={onChangeNode}
|
||||
moduleId={props.moduleId}
|
||||
flowOutputList={outputs}
|
||||
/>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
|
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/flow';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import { Box, Button } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
import { FlowInputItemTypeEnum, FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
const NodeHttp = ({
|
||||
data: { moduleId, inputs, outputs, onChangeNode, ...props }
|
||||
}: NodeProps<FlowModuleItemType>) => {
|
||||
return (
|
||||
<NodeCard minW={'350px'} moduleId={moduleId} {...props}>
|
||||
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
|
||||
<RenderInput moduleId={moduleId} onChangeNode={onChangeNode} flowInputList={inputs} />
|
||||
<Button
|
||||
variant={'base'}
|
||||
mt={5}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key,
|
||||
value: {
|
||||
key,
|
||||
value: '',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
label: 'New Param',
|
||||
edit: true
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加入参
|
||||
</Button>
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
|
||||
<Box textAlign={'right'} mt={5}>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key,
|
||||
value: outputs.concat([
|
||||
{
|
||||
key,
|
||||
label: '出参1',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
])
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加出参
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeHttp);
|
@@ -81,14 +81,19 @@ const NodeKbSearch = ({
|
||||
onChangeNode={onChangeNode}
|
||||
flowInputList={inputs}
|
||||
CustomComponent={{
|
||||
kbList: ({ key, value }) => (
|
||||
kbList: ({ key, value, ...props }) => (
|
||||
<KBSelect
|
||||
activeKbs={value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key,
|
||||
value: e
|
||||
type: 'inputs',
|
||||
value: {
|
||||
...props,
|
||||
key,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -98,7 +103,7 @@ const NodeKbSearch = ({
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput flowOutputList={outputs} />
|
||||
<RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
|
@@ -9,7 +9,7 @@ import { FlowValueTypeEnum } from '@/constants/flow';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
|
||||
const QuestionInputNode = ({
|
||||
data: { inputs, outputs, onChangeNode, ...props }
|
||||
data: { inputs, outputs, ...props }
|
||||
}: NodeProps<FlowModuleItemType>) => {
|
||||
return (
|
||||
<NodeCard minW={'240px'} {...props}>
|
||||
|
@@ -14,7 +14,7 @@ const NodeUserGuide = ({
|
||||
data: { inputs, outputs, onChangeNode, ...props }
|
||||
}: NodeProps<FlowModuleItemType>) => {
|
||||
const welcomeText = useMemo(
|
||||
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText)?.value,
|
||||
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText),
|
||||
[inputs]
|
||||
);
|
||||
|
||||
@@ -30,22 +30,27 @@ const NodeUserGuide = ({
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Textarea
|
||||
className="nodrag"
|
||||
rows={6}
|
||||
resize={'both'}
|
||||
defaultValue={welcomeText}
|
||||
bg={'myWhite.500'}
|
||||
placeholder={welcomeTextTip}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId: props.moduleId,
|
||||
key: SystemInputEnum.welcomeText,
|
||||
type: 'inputs',
|
||||
value: e.target.value
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{welcomeText && (
|
||||
<Textarea
|
||||
className="nodrag"
|
||||
rows={6}
|
||||
resize={'both'}
|
||||
defaultValue={welcomeText.value}
|
||||
bg={'myWhite.500'}
|
||||
placeholder={welcomeTextTip}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId: props.moduleId,
|
||||
key: SystemInputEnum.welcomeText,
|
||||
type: 'inputs',
|
||||
value: {
|
||||
...welcomeText,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
|
@@ -40,10 +40,13 @@ const NodeUserGuide = ({
|
||||
moduleId: props.moduleId,
|
||||
key: SystemInputEnum.variables,
|
||||
type: 'inputs',
|
||||
value
|
||||
value: {
|
||||
...inputs.find((item) => item.key === SystemInputEnum.variables),
|
||||
value
|
||||
}
|
||||
});
|
||||
},
|
||||
[onChangeNode, props.moduleId]
|
||||
[inputs, onChangeNode, props.moduleId]
|
||||
);
|
||||
|
||||
const onclickSubmit = useCallback(
|
||||
|
@@ -0,0 +1,116 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
FormControl
|
||||
} from '@chakra-ui/react';
|
||||
import type { ContextExtractAgentItemType, HttpFieldItemType } from '@/types/app';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyModal from '@/components/MyModal';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { FlowInputItemTypeEnum, FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import { FlowInputItemType } from '@/types/flow';
|
||||
|
||||
const typeSelectList = [
|
||||
{
|
||||
label: '字符串',
|
||||
value: FlowValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: '数字',
|
||||
value: FlowValueTypeEnum.number
|
||||
},
|
||||
{
|
||||
label: '布尔',
|
||||
value: FlowValueTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: '任意',
|
||||
value: FlowValueTypeEnum.any
|
||||
}
|
||||
];
|
||||
|
||||
const SetInputFieldModal = ({
|
||||
defaultField = {
|
||||
label: '',
|
||||
key: '',
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
description: '',
|
||||
required: false
|
||||
},
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
defaultField?: FlowInputItemType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: FlowInputItemType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, getValues, setValue, handleSubmit } = useForm<FlowInputItemType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose}>
|
||||
<ModalHeader display={'flex'} alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
|
||||
{t('app.Input Field Settings')}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>必填</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段类型</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={typeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: any) => {
|
||||
setValue('valueType', e);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段名</Box>
|
||||
<Input
|
||||
placeholder="预约字段/sql语句……"
|
||||
{...register('label', { required: '字段名不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段 key</Box>
|
||||
<Input
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SetInputFieldModal);
|
@@ -0,0 +1,105 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
FormControl
|
||||
} from '@chakra-ui/react';
|
||||
import type { ContextExtractAgentItemType, HttpFieldItemType } from '@/types/app';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyModal from '@/components/MyModal';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { FlowOutputItemTypeEnum, FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
import { FlowOutputItemType } from '@/types/flow';
|
||||
|
||||
const typeSelectList = [
|
||||
{
|
||||
label: '字符串',
|
||||
value: FlowValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: '数字',
|
||||
value: FlowValueTypeEnum.number
|
||||
},
|
||||
{
|
||||
label: '布尔',
|
||||
value: FlowValueTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: '任意',
|
||||
value: FlowValueTypeEnum.any
|
||||
}
|
||||
];
|
||||
|
||||
const SetInputFieldModal = ({
|
||||
defaultField,
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
defaultField: FlowOutputItemType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: FlowOutputItemType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, getValues, setValue, handleSubmit } = useForm<FlowOutputItemType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose}>
|
||||
<ModalHeader display={'flex'} alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
|
||||
{t('app.Output Field Settings')}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段类型</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={typeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: any) => {
|
||||
setValue('valueType', e);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段名</Box>
|
||||
<Input
|
||||
placeholder="预约字段/sql语句……"
|
||||
{...register('label', { required: '字段名不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex mt={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段 key</Box>
|
||||
<Input
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SetInputFieldModal);
|
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import type { FlowInputItemType, FlowModuleItemType } from '@/types/flow';
|
||||
import {
|
||||
Box,
|
||||
@@ -8,38 +8,129 @@ import {
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberDecrementStepper
|
||||
NumberDecrementStepper,
|
||||
Flex
|
||||
} from '@chakra-ui/react';
|
||||
import { FlowInputItemTypeEnum } from '@/constants/flow';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MySelect from '@/components/Select';
|
||||
import MySlider from '@/components/Slider';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import TargetHandle from './TargetHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
const SetInputFieldModal = dynamic(() => import('../modules/SetInputFieldModal'));
|
||||
|
||||
export const Label = ({
|
||||
required = false,
|
||||
children,
|
||||
description
|
||||
}: {
|
||||
required?: boolean;
|
||||
children: React.ReactNode | string;
|
||||
description?: string;
|
||||
}) => (
|
||||
<Box as={'label'} display={'inline-block'} position={'relative'}>
|
||||
{children}
|
||||
{required && (
|
||||
<Box position={'absolute'} top={'-2px'} right={'-10px'} color={'red.500'} fontWeight={'bold'}>
|
||||
*
|
||||
moduleId,
|
||||
inputKey,
|
||||
onChangeNode,
|
||||
...item
|
||||
}: FlowInputItemType & {
|
||||
moduleId: string;
|
||||
inputKey: string;
|
||||
onChangeNode: FlowModuleItemType['onChangeNode'];
|
||||
}) => {
|
||||
const { required = false, description, edit, label, type, valueType } = item;
|
||||
const [editField, setEditField] = useState<FlowInputItemType>();
|
||||
|
||||
return (
|
||||
<Flex alignItems={'center'} position={'relative'}>
|
||||
<Box position={'relative'}>
|
||||
{label}
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
{required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
right={'-8px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
||||
{(type === FlowInputItemTypeEnum.target || valueType) && (
|
||||
<TargetHandle handleKey={inputKey} valueType={valueType} />
|
||||
)}
|
||||
|
||||
{edit && (
|
||||
<>
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
...item,
|
||||
key: inputKey
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
{
|
||||
console.log(moduleId, inputKey, valueType);
|
||||
}
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: inputKey,
|
||||
value: ''
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!!editField && (
|
||||
<SetInputFieldModal
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(data) => {
|
||||
// same key
|
||||
if (editField.key === data.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: inputKey,
|
||||
value: data
|
||||
});
|
||||
} else {
|
||||
// diff key. del and add
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: editField.key,
|
||||
value: ''
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key: editField.key,
|
||||
value: data
|
||||
});
|
||||
}
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const RenderBody = ({
|
||||
flowInputList,
|
||||
@@ -59,13 +150,12 @@ const RenderBody = ({
|
||||
item.type !== FlowInputItemTypeEnum.hidden && (
|
||||
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
{!!item.label && (
|
||||
<Label required={item.required} description={item.description}>
|
||||
{item.label}
|
||||
|
||||
{(item.type === FlowInputItemTypeEnum.target || item.valueType) && (
|
||||
<TargetHandle handleKey={item.key} valueType={item.valueType} />
|
||||
)}
|
||||
</Label>
|
||||
<Label
|
||||
moduleId={moduleId}
|
||||
onChangeNode={onChangeNode}
|
||||
inputKey={item.key}
|
||||
{...item}
|
||||
/>
|
||||
)}
|
||||
<Box mt={2} className={'nodrag'}>
|
||||
{item.type === FlowInputItemTypeEnum.numberInput && (
|
||||
@@ -76,8 +166,12 @@ const RenderBody = ({
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: item.key,
|
||||
value: Number(e)
|
||||
value: {
|
||||
...item,
|
||||
value: Number(e)
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -95,8 +189,12 @@ const RenderBody = ({
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: item.key,
|
||||
value: e.target.value
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -110,8 +208,12 @@ const RenderBody = ({
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: item.key,
|
||||
value: e.target.value
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -124,8 +226,12 @@ const RenderBody = ({
|
||||
onchange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: item.key,
|
||||
value: e
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -142,8 +248,12 @@ const RenderBody = ({
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: item.key,
|
||||
value: e
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
@@ -1,37 +1,147 @@
|
||||
import React from 'react';
|
||||
import type { FlowOutputItemType } from '@/types/flow';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import type { FlowModuleItemType, FlowOutputItemType } from '@/types/flow';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { FlowOutputItemTypeEnum } from '@/constants/flow';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import SourceHandle from './SourceHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import dynamic from 'next/dynamic';
|
||||
const SetOutputFieldModal = dynamic(() => import('../modules/SetOutputFieldModal'));
|
||||
|
||||
const Label = ({
|
||||
children,
|
||||
description
|
||||
}: {
|
||||
children: React.ReactNode | string;
|
||||
description?: string;
|
||||
}) => (
|
||||
<Flex as={'label'} justifyContent={'right'} alignItems={'center'} position={'relative'}>
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
moduleId,
|
||||
outputKey,
|
||||
delOutputByKey,
|
||||
updateOutput,
|
||||
onChangeNode,
|
||||
addUpdateOutput,
|
||||
...item
|
||||
}: FlowOutputItemType & {
|
||||
outputKey: string;
|
||||
moduleId: string;
|
||||
delOutputByKey: (key: string) => void;
|
||||
updateOutput: (key: string, val: FlowOutputItemType) => void;
|
||||
addUpdateOutput: (val: FlowOutputItemType) => void;
|
||||
onChangeNode: FlowModuleItemType['onChangeNode'];
|
||||
}) => {
|
||||
const { label, description, edit } = item;
|
||||
const [editField, setEditField] = useState<FlowOutputItemType>();
|
||||
|
||||
return (
|
||||
<Flex as={'label'} justifyContent={'right'} alignItems={'center'} position={'relative'}>
|
||||
{edit && (
|
||||
<>
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
...item,
|
||||
key: outputKey
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => delOutputByKey(outputKey)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box>{label}</Box>
|
||||
|
||||
{!!editField && (
|
||||
<SetOutputFieldModal
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(data) => {
|
||||
console.log(data); // same key
|
||||
if (editField.key === data.key) {
|
||||
updateOutput(data.key, data);
|
||||
} else {
|
||||
delOutputByKey(editField.key);
|
||||
addUpdateOutput(data);
|
||||
}
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const RenderOutput = ({
|
||||
moduleId,
|
||||
flowOutputList,
|
||||
onChangeNode
|
||||
}: {
|
||||
moduleId: string;
|
||||
flowOutputList: FlowOutputItemType[];
|
||||
onChangeNode: FlowModuleItemType['onChangeNode'];
|
||||
}) => {
|
||||
const delOutputByKey = useCallback(
|
||||
(key: string) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: flowOutputList.filter((output) => output.key !== key)
|
||||
});
|
||||
},
|
||||
[moduleId, flowOutputList, onChangeNode]
|
||||
);
|
||||
const updateOutput = useCallback(
|
||||
(key: string, val: FlowOutputItemType) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: flowOutputList.map((output) => (output.key === key ? val : output))
|
||||
});
|
||||
},
|
||||
[flowOutputList, moduleId, onChangeNode]
|
||||
);
|
||||
const addUpdateOutput = useCallback(
|
||||
(val: FlowOutputItemType) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: flowOutputList.concat(val)
|
||||
});
|
||||
},
|
||||
[flowOutputList, moduleId, onChangeNode]
|
||||
);
|
||||
|
||||
const RenderOutput = ({ flowOutputList }: { flowOutputList: FlowOutputItemType[] }) => {
|
||||
return (
|
||||
<>
|
||||
{flowOutputList.map(
|
||||
(item) =>
|
||||
item.type !== FlowOutputItemTypeEnum.hidden && (
|
||||
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
<Label description={item.description}>{item.label}</Label>
|
||||
<Label
|
||||
moduleId={moduleId}
|
||||
onChangeNode={onChangeNode}
|
||||
outputKey={item.key}
|
||||
delOutputByKey={delOutputByKey}
|
||||
addUpdateOutput={addUpdateOutput}
|
||||
updateOutput={updateOutput}
|
||||
{...item}
|
||||
/>
|
||||
<Box mt={FlowOutputItemTypeEnum.answer ? 0 : 2} className={'nodrag'}>
|
||||
{item.type === FlowOutputItemTypeEnum.source && (
|
||||
<SourceHandle handleKey={item.key} valueType={item.valueType} />
|
||||
|
@@ -16,7 +16,7 @@ const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
|
||||
valueType
|
||||
? FlowValueTypeStyle[valueType]
|
||||
: (FlowValueTypeStyle[FlowValueTypeEnum.any] as any),
|
||||
[]
|
||||
[valueType]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@@ -71,6 +71,9 @@ const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'),
|
||||
const NodeExtract = dynamic(() => import('./components/Nodes/NodeExtract'), {
|
||||
ssr: false
|
||||
});
|
||||
const NodeHttp = dynamic(() => import('./components/Nodes/NodeHttp'), {
|
||||
ssr: false
|
||||
});
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
import styles from './index.module.scss';
|
||||
@@ -87,7 +90,8 @@ const nodeTypes = {
|
||||
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
|
||||
[FlowModuleTypeEnum.answerNode]: NodeAnswer,
|
||||
[FlowModuleTypeEnum.classifyQuestion]: NodeCQNode,
|
||||
[FlowModuleTypeEnum.contentExtract]: NodeExtract
|
||||
[FlowModuleTypeEnum.contentExtract]: NodeExtract,
|
||||
[FlowModuleTypeEnum.httpRequest]: NodeHttp
|
||||
// [FlowModuleTypeEnum.empty]: EmptyModule
|
||||
};
|
||||
const edgeTypes = {
|
||||
@@ -124,11 +128,10 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
|
||||
const flow2AppModules = useCallback(() => {
|
||||
const modules: AppModuleItemType[] = nodes.map((item) => ({
|
||||
moduleId: item.data.moduleId,
|
||||
position: item.position,
|
||||
flowType: item.data.flowType,
|
||||
position: item.position,
|
||||
inputs: item.data.inputs.map((item) => ({
|
||||
key: item.key,
|
||||
value: item.value,
|
||||
...item,
|
||||
connected: item.type !== FlowInputItemTypeEnum.target
|
||||
})),
|
||||
outputs: item.data.outputs.map((item) => ({
|
||||
@@ -163,7 +166,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
|
||||
return modules;
|
||||
}, [edges, nodes]);
|
||||
const onChangeNode = useCallback(
|
||||
({ moduleId, key, type = 'inputs', value, valueKey = 'value' }: FlowModuleItemChangeProps) => {
|
||||
({ moduleId, key, type = 'inputs', value }: FlowModuleItemChangeProps) => {
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
if (node.id !== moduleId) return node;
|
||||
@@ -172,18 +175,43 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
inputs: node.data.inputs.map((item) => {
|
||||
if (item.key === key) {
|
||||
return {
|
||||
...item,
|
||||
[valueKey]: value
|
||||
};
|
||||
}
|
||||
return item;
|
||||
})
|
||||
inputs: node.data.inputs.map((item) => (item.key === key ? value : item))
|
||||
}
|
||||
};
|
||||
}
|
||||
if (type === 'addInput') {
|
||||
const input = node.data.inputs.find((input) => input.key === value.key);
|
||||
if (input) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: 'key 重复'
|
||||
});
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
inputs: node.data.inputs
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
inputs: node.data.inputs.concat(value)
|
||||
}
|
||||
};
|
||||
}
|
||||
if (type === 'delInput') {
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
inputs: node.data.inputs.filter((item) => item.key !== key)
|
||||
}
|
||||
};
|
||||
}
|
||||
console.log(value);
|
||||
|
||||
return {
|
||||
...node,
|
||||
|
@@ -59,7 +59,7 @@ const FileSelect = ({
|
||||
)}
|
||||
{isCsv && (
|
||||
<Box
|
||||
my={3}
|
||||
mt={1}
|
||||
cursor={'pointer'}
|
||||
textDecoration={'underline'}
|
||||
color={'myBlue.600'}
|
||||
|
31
client/src/service/moduleDispatch/tools/httpRequest.ts
Normal file
31
client/src/service/moduleDispatch/tools/httpRequest.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { sseResponseEventEnum, TaskResponseKeyEnum } from '@/constants/chat';
|
||||
import { sseResponse } from '@/service/utils/tools';
|
||||
import { textAdaptGptResponse } from '@/utils/adapt';
|
||||
import type { NextApiResponse } from 'next';
|
||||
|
||||
export type AnswerProps = {
|
||||
res: NextApiResponse;
|
||||
text: string;
|
||||
stream: boolean;
|
||||
};
|
||||
export type AnswerResponse = {
|
||||
[TaskResponseKeyEnum.answerText]: string;
|
||||
};
|
||||
|
||||
export const dispatchAnswer = (props: Record<string, any>): AnswerResponse => {
|
||||
const { res, text = '', stream } = props as AnswerProps;
|
||||
|
||||
if (stream) {
|
||||
sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
text: text.replace(/\\n/g, '\n')
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
[TaskResponseKeyEnum.answerText]: text
|
||||
};
|
||||
};
|
8
client/src/types/app.d.ts
vendored
8
client/src/types/app.d.ts
vendored
@@ -51,6 +51,11 @@ export type ContextExtractAgentItemType = {
|
||||
key: string;
|
||||
required: boolean;
|
||||
};
|
||||
export type HttpFieldItemType = {
|
||||
label: string;
|
||||
key: string;
|
||||
type: `${FlowValueTypeEnum}`;
|
||||
};
|
||||
|
||||
export type VariableItemType = {
|
||||
id: string;
|
||||
@@ -63,12 +68,11 @@ export type VariableItemType = {
|
||||
};
|
||||
|
||||
/* app module */
|
||||
export type AppModuleInputItemType = { key: string; value?: any; connected?: boolean };
|
||||
export type AppModuleItemType = {
|
||||
moduleId: string;
|
||||
position?: XYPosition;
|
||||
flowType: `${FlowModuleTypeEnum}`;
|
||||
inputs: AppModuleInputItemType[];
|
||||
inputs: FlowInputItemType[];
|
||||
outputs: FlowOutputItemType[];
|
||||
};
|
||||
|
||||
|
5
client/src/types/flow.d.ts
vendored
5
client/src/types/flow.d.ts
vendored
@@ -10,10 +10,9 @@ import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
|
||||
export type FlowModuleItemChangeProps = {
|
||||
moduleId: string;
|
||||
type?: 'inputs' | 'outputs';
|
||||
type: 'inputs' | 'outputs' | 'addInput' | 'delInput';
|
||||
key: string;
|
||||
value: any;
|
||||
valueKey?: keyof FlowInputItemType & keyof FlowBodyItemType;
|
||||
};
|
||||
|
||||
export type FlowInputItemType = {
|
||||
@@ -22,6 +21,7 @@ export type FlowInputItemType = {
|
||||
valueType?: `${FlowValueTypeEnum}`;
|
||||
type: `${FlowInputItemTypeEnum}`;
|
||||
label: string;
|
||||
edit?: boolean;
|
||||
connected?: boolean;
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
@@ -40,6 +40,7 @@ export type FlowOutputTargetItemType = {
|
||||
export type FlowOutputItemType = {
|
||||
key: string; // 字段名
|
||||
label?: string;
|
||||
edit?: boolean;
|
||||
description?: string;
|
||||
valueType?: `${FlowValueTypeEnum}`;
|
||||
type?: `${FlowOutputItemTypeEnum}`;
|
||||
|
@@ -75,11 +75,17 @@ export const appModule2FlowNode = ({
|
||||
const template =
|
||||
ModuleTemplatesFlat.find((template) => template.flowType === item.flowType) || EmptyModule;
|
||||
|
||||
const concatInputs = template.inputs.concat(
|
||||
item.inputs.filter(
|
||||
(input) => input.label && !template.inputs.find((item) => item.key === input.key)
|
||||
)
|
||||
);
|
||||
|
||||
// replace item data
|
||||
const moduleItem: FlowModuleItemType = {
|
||||
...item,
|
||||
...template,
|
||||
inputs: template.inputs.map((templateInput) => {
|
||||
inputs: concatInputs.map((templateInput) => {
|
||||
// use latest inputs
|
||||
const itemInput = item.inputs.find((item) => item.key === templateInput.key) || templateInput;
|
||||
return {
|
||||
@@ -87,7 +93,6 @@ export const appModule2FlowNode = ({
|
||||
value: itemInput.value
|
||||
};
|
||||
}),
|
||||
// 合并 template 和数据库,文案以 template 为准
|
||||
outputs: item.outputs.map((output) => {
|
||||
// unChange outputs
|
||||
const templateOutput = template.outputs.find((item) => item.key === output.key);
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import type { AppModuleInputItemType, AppModuleItemType, VariableItemType } from '@/types/app';
|
||||
import type { AppModuleItemType, VariableItemType } from '@/types/app';
|
||||
import { chatModelList, vectorModelList } from '@/store/static';
|
||||
import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { TaskResponseKeyEnum } from '@/constants/chat';
|
||||
import type { SelectedKbType } from '@/types/plugin';
|
||||
import { FlowInputItemType } from '@/types/flow';
|
||||
|
||||
export type EditFormType = {
|
||||
chatModel: {
|
||||
@@ -64,7 +65,7 @@ export const appModules2Form = (modules: AppModuleItemType[]) => {
|
||||
key
|
||||
}: {
|
||||
formKey: string;
|
||||
inputs: AppModuleInputItemType[];
|
||||
inputs: FlowInputItemType[];
|
||||
key: string;
|
||||
}) => {
|
||||
const propertyPath = formKey.split('.');
|
||||
@@ -148,7 +149,7 @@ export const appModules2Form = (modules: AppModuleItemType[]) => {
|
||||
return defaultAppForm;
|
||||
};
|
||||
|
||||
const chatModelInput = (formData: EditFormType): AppModuleInputItemType[] => [
|
||||
const chatModelInput = (formData: EditFormType): FlowInputItemType[] => [
|
||||
{
|
||||
key: 'model',
|
||||
value: formData.chatModel.model,
|
||||
|
Reference in New Issue
Block a user