feat: http modules

This commit is contained in:
archer
2023-08-03 11:09:57 +08:00
parent b7934ecc27
commit 952da2a06e
31 changed files with 851 additions and 176 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -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.", "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 is invalid": "Connecting is invalid",
"Connection type is different": "Connection type is different", "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": { "chat": {
"Confirm to clear history": "Confirm to clear history?", "Confirm to clear history": "Confirm to clear history?",
@@ -39,28 +41,28 @@
"Tools": "Tools" "Tools": "Tools"
}, },
"user": { "user": {
"Account": "Account",
"Application Name": "Application Name",
"Avatar": "Avatar",
"Balance": "Balance",
"Bill Detail": "Bill Detail", "Bill Detail": "Bill Detail",
"Change": "Change",
"Notice": "Notice",
"Old password is error": "Old password is error", "Old password is error": "Old password is error",
"OpenAI Account Setting": "OpenAI Account Setting", "OpenAI Account Setting": "OpenAI Account Setting",
"Password": "Password",
"Pay": "Pay", "Pay": "Pay",
"Personal Information": "Personal",
"Recharge Record": "Recharge",
"Replace": "Replace",
"Set OpenAI Account Failed": "Set OpenAI account failed", "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": "Update Password",
"Update password failed": "Update password failed", "Update password failed": "Update password failed",
"Update password succseful": "Update password succseful", "Update password succseful": "Update password succseful",
"Personal Information": "Personal", "Usage Record": "Usage"
"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"
} }
} }

View File

@@ -9,7 +9,9 @@
"Confirm Save App Tip": "保存后将会覆盖高级编排配置,请确保该应用未使用高级编排功能。", "Confirm Save App Tip": "保存后将会覆盖高级编排配置,请确保该应用未使用高级编排功能。",
"Connection is invalid": "连接无效", "Connection is invalid": "连接无效",
"Connection type is different": "连接的类型不一致", "Connection type is different": "连接的类型不一致",
"My Apps": "我的应用" "Input Field Settings": "输入字段编辑",
"My Apps": "我的应用",
"Output Field Settings": "输出字段编辑"
}, },
"chat": { "chat": {
"Confirm to clear history": "确认清空该应用的聊天记录?", "Confirm to clear history": "确认清空该应用的聊天记录?",
@@ -39,28 +41,28 @@
"Tools": "工具" "Tools": "工具"
}, },
"user": { "user": {
"Account": "账号",
"Application Name": "应用名",
"Avatar": "头像",
"Balance": "余额",
"Bill Detail": "账单详情", "Bill Detail": "账单详情",
"Change": "变更",
"Notice": "通知",
"Old password is error": "旧密码错误", "Old password is error": "旧密码错误",
"OpenAI Account Setting": "OpenAI 账号配置", "OpenAI Account Setting": "OpenAI 账号配置",
"Password": "密码",
"Pay": "充值", "Pay": "充值",
"Personal Information": "个人信息",
"Recharge Record": "充值记录",
"Replace": "更换",
"Set OpenAI Account Failed": "设置 OpenAI 账号异常", "Set OpenAI Account Failed": "设置 OpenAI 账号异常",
"Sign Out": "登出",
"Source": "来源",
"Time": "时间",
"Total Amount": "总金额",
"Update Password": "修改密码", "Update Password": "修改密码",
"Update password failed": "修改密码异常", "Update password failed": "修改密码异常",
"Update password succseful": "修改密码成功", "Update password succseful": "修改密码成功",
"Personal Information": "个人信息", "Usage Record": "使用记录"
"Usage Record": "使用记录",
"Recharge Record": "充值记录",
"Notice": "通知",
"Sign Out": "登出",
"Avatar": "头像",
"Account": "账号",
"Balance": "余额",
"Time": "时间",
"Source": "来源",
"Application Name": "应用名",
"Total Amount": "总金额",
"Change": "变更",
"Password": "密码",
"Replace": "更换"
} }
} }

View 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

View File

@@ -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

View File

@@ -8,7 +8,6 @@ const map = {
copy: require('./icons/copy.svg').default, copy: require('./icons/copy.svg').default,
chatSend: require('./icons/chatSend.svg').default, chatSend: require('./icons/chatSend.svg').default,
delete: require('./icons/delete.svg').default, delete: require('./icons/delete.svg').default,
withdraw: require('./icons/withdraw.svg').default,
stop: require('./icons/stop.svg').default, stop: require('./icons/stop.svg').default,
collectionLight: require('./icons/collectionLight.svg').default, collectionLight: require('./icons/collectionLight.svg').default,
collectionSolid: require('./icons/collectionSolid.svg').default, collectionSolid: require('./icons/collectionSolid.svg').default,
@@ -72,7 +71,8 @@ const map = {
language_en: require('./icons/language/en.svg').default, language_en: require('./icons/language/en.svg').default,
language_zh: require('./icons/language/zh.svg').default, language_zh: require('./icons/language/zh.svg').default,
outlink_share: require('./icons/outlink/share.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; export type IconName = keyof typeof map;

View File

@@ -15,7 +15,7 @@ import {
Input_Template_TFSwitch, Input_Template_TFSwitch,
Input_Template_UserChatInput Input_Template_UserChatInput
} from './inputTemplate'; } from './inputTemplate';
import { ContextExtractEnum } from './flowField'; import { ContextExtractEnum, HttpPropsEnum } from './flowField';
export const ChatModelSystemTip = export const ChatModelSystemTip =
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}'; '模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{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 = { export const EmptyModule: FlowModuleTemplateType = {
logo: '/imgs/module/cq.png', logo: '/imgs/module/cq.png',
name: '该模块已被移除', name: '该模块已被移除',
@@ -477,7 +505,7 @@ export const ModuleTemplates = [
}, },
{ {
label: 'Agent', label: 'Agent',
list: [ClassifyQuestionModule, ContextExtractModule] list: [ClassifyQuestionModule, ContextExtractModule, HttpModule]
} }
]; ];
export const ModuleTemplatesFlat = ModuleTemplates.map((templates) => templates.list)?.flat(); export const ModuleTemplatesFlat = ModuleTemplates.map((templates) => templates.list)?.flat();

View File

@@ -6,3 +6,10 @@ export enum ContextExtractEnum {
failed = 'failed', failed = 'failed',
fields = 'fields' fields = 'fields'
} }
export enum HttpPropsEnum {
url = 'url',
finish = 'finish',
body = 'body',
response = 'response'
}

View File

@@ -31,7 +31,8 @@ export enum FlowModuleTypeEnum {
tfSwitchNode = 'tfSwitchNode', tfSwitchNode = 'tfSwitchNode',
answerNode = 'answerNode', answerNode = 'answerNode',
classifyQuestion = 'classifyQuestion', classifyQuestion = 'classifyQuestion',
contentExtract = 'contentExtract' contentExtract = 'contentExtract',
httpRequest = 'httpRequest'
} }
export enum SpecialInputKeyEnum { export enum SpecialInputKeyEnum {

View File

@@ -3,9 +3,9 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response'; import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth'; import { authUser } from '@/service/utils/auth';
import { connectToDatabase, App } from '@/service/mongo'; import { connectToDatabase, App } from '@/service/mongo';
import { AppModuleInputItemType } from '@/types/app';
import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow'; import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
import { TaskResponseKeyEnum } from '@/constants/chat'; import { TaskResponseKeyEnum } from '@/constants/chat';
import { FlowInputItemType } from '@/types/flow';
const chatModelInput = ({ const chatModelInput = ({
model, model,
@@ -21,7 +21,7 @@ const chatModelInput = ({
systemPrompt: string; systemPrompt: string;
limitPrompt: string; limitPrompt: string;
kbList: { kbId: string }[]; kbList: { kbId: string }[];
}): AppModuleInputItemType[] => [ }): FlowInputItemType[] => [
{ {
key: 'model', key: 'model',
value: model, value: model,

View File

@@ -27,7 +27,8 @@ const NodeCQNode = ({
CustomComponent={{ CustomComponent={{
[SpecialInputKeyEnum.agents]: ({ [SpecialInputKeyEnum.agents]: ({
key: agentKey, key: agentKey,
value: agents = [] value: agents = [],
...props
}: { }: {
key: string; key: string;
value?: ClassifyQuestionAgentItemType[]; value?: ClassifyQuestionAgentItemType[];
@@ -50,7 +51,11 @@ const NodeCQNode = ({
moduleId, moduleId,
type: 'inputs', type: 'inputs',
key: agentKey, key: agentKey,
value: newInputValue value: {
...props,
key: agentKey,
value: newInputValue
}
}); });
onChangeNode({ onChangeNode({
moduleId, moduleId,
@@ -77,8 +82,13 @@ const NodeCQNode = ({
); );
onChangeNode({ onChangeNode({
moduleId, moduleId,
type: 'inputs',
key: agentKey, key: agentKey,
value: newVal value: {
...props,
key: agentKey,
value: newVal
}
}); });
}} }}
/> />
@@ -97,11 +107,16 @@ const NodeCQNode = ({
type: FlowOutputItemTypeEnum.hidden, type: FlowOutputItemTypeEnum.hidden,
targets: [] targets: []
}); });
onChangeNode({ onChangeNode({
moduleId, moduleId,
type: 'inputs', type: 'inputs',
key: agentKey, key: agentKey,
value: newInputValue value: {
...props,
key: agentKey,
value: newInputValue
}
}); });
onChangeNode({ onChangeNode({
moduleId, moduleId,

View File

@@ -48,33 +48,32 @@ const NodeChat = ({
onchange={(e) => { onchange={(e) => {
onChangeNode({ onChangeNode({
moduleId, moduleId,
type: 'inputs',
key: inputItem.key, key: inputItem.key,
value: e value: {
...inputItem,
value: e
}
}); });
// update max tokens // update max tokens
const model = chatModelList.find((item) => item.model === e); const model =
chatModelList.find((item) => item.model === e) || chatModelList[0];
if (!model) return; if (!model) return;
onChangeNode({ onChangeNode({
moduleId, moduleId,
type: 'inputs',
key: 'maxToken', key: 'maxToken',
valueKey: 'markList', value: {
value: [ ...inputs.find((input) => input.key === 'maxToken'),
{ label: '100', value: 100 }, markList: [
{ label: `${model.contextMaxToken}`, value: model.contextMaxToken } { label: '100', value: 100 },
] { label: `${model.contextMaxToken}`, value: model.contextMaxToken }
}); ],
onChangeNode({ max: model.contextMaxToken,
moduleId, value: model.contextMaxToken / 2
key: 'maxToken', }
valueKey: 'max',
value: model.contextMaxToken
});
onChangeNode({
moduleId,
key: 'maxToken',
valueKey: 'value',
value: model.contextMaxToken / 2
}); });
}} }}
/> />
@@ -100,8 +99,12 @@ const NodeChat = ({
onChange={(e) => { onChange={(e) => {
onChangeNode({ onChangeNode({
moduleId, moduleId,
type: 'inputs',
key: inputItem.key, key: inputItem.key,
value: e value: {
...inputItem,
value: e
}
}); });
}} }}
/> />
@@ -115,7 +118,11 @@ const NodeChat = ({
<> <>
<Divider text="Output" /> <Divider text="Output" />
<Container> <Container>
<RenderOutput flowOutputList={outputs} /> <RenderOutput
onChangeNode={onChangeNode}
moduleId={moduleId}
flowOutputList={outputs}
/>
</Container> </Container>
</> </>
)} )}

View File

@@ -31,10 +31,9 @@ const NodeExtract = ({
flowInputList={inputs} flowInputList={inputs}
CustomComponent={{ CustomComponent={{
[ContextExtractEnum.extractKeys]: ({ [ContextExtractEnum.extractKeys]: ({
key, value: extractKeys = [],
value: extractKeys = [] ...props
}: { }: {
key: string;
value?: ContextExtractAgentItemType[]; value?: ContextExtractAgentItemType[];
}) => ( }) => (
<Box pt={2}> <Box pt={2}>
@@ -97,7 +96,10 @@ const NodeExtract = ({
moduleId, moduleId,
type: 'inputs', type: 'inputs',
key: ContextExtractEnum.extractKeys, key: ContextExtractEnum.extractKeys,
value: newInputValue value: {
...props,
value: newInputValue
}
}); });
onChangeNode({ onChangeNode({
moduleId, moduleId,
@@ -121,7 +123,7 @@ const NodeExtract = ({
</Container> </Container>
<Divider text="Output" /> <Divider text="Output" />
<Container> <Container>
<RenderOutput flowOutputList={outputs} /> <RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
</Container> </Container>
{!!editExtractFiled && ( {!!editExtractFiled && (
@@ -160,7 +162,10 @@ const NodeExtract = ({
moduleId, moduleId,
type: 'inputs', type: 'inputs',
key: ContextExtractEnum.extractKeys, key: ContextExtractEnum.extractKeys,
value: newInputs value: {
...inputs.find((input) => input.key === ContextExtractEnum.extractKeys),
value: newInputs
}
}); });
onChangeNode({ onChangeNode({
moduleId, moduleId,

View File

@@ -18,7 +18,11 @@ const NodeHistory = ({
</Container> </Container>
<Divider text="Output" /> <Divider text="Output" />
<Container> <Container>
<RenderOutput flowOutputList={outputs} /> <RenderOutput
onChangeNode={onChangeNode}
moduleId={props.moduleId}
flowOutputList={outputs}
/>
</Container> </Container>
</NodeCard> </NodeCard>
); );

View File

@@ -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);

View File

@@ -81,14 +81,19 @@ const NodeKbSearch = ({
onChangeNode={onChangeNode} onChangeNode={onChangeNode}
flowInputList={inputs} flowInputList={inputs}
CustomComponent={{ CustomComponent={{
kbList: ({ key, value }) => ( kbList: ({ key, value, ...props }) => (
<KBSelect <KBSelect
activeKbs={value} activeKbs={value}
onChange={(e) => { onChange={(e) => {
onChangeNode({ onChangeNode({
moduleId, moduleId,
key, key,
value: e type: 'inputs',
value: {
...props,
key,
value: e
}
}); });
}} }}
/> />
@@ -98,7 +103,7 @@ const NodeKbSearch = ({
</Container> </Container>
<Divider text="Output" /> <Divider text="Output" />
<Container> <Container>
<RenderOutput flowOutputList={outputs} /> <RenderOutput onChangeNode={onChangeNode} moduleId={moduleId} flowOutputList={outputs} />
</Container> </Container>
</NodeCard> </NodeCard>
); );

View File

@@ -9,7 +9,7 @@ import { FlowValueTypeEnum } from '@/constants/flow';
import SourceHandle from '../render/SourceHandle'; import SourceHandle from '../render/SourceHandle';
const QuestionInputNode = ({ const QuestionInputNode = ({
data: { inputs, outputs, onChangeNode, ...props } data: { inputs, outputs, ...props }
}: NodeProps<FlowModuleItemType>) => { }: NodeProps<FlowModuleItemType>) => {
return ( return (
<NodeCard minW={'240px'} {...props}> <NodeCard minW={'240px'} {...props}>

View File

@@ -14,7 +14,7 @@ const NodeUserGuide = ({
data: { inputs, outputs, onChangeNode, ...props } data: { inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => { }: NodeProps<FlowModuleItemType>) => {
const welcomeText = useMemo( const welcomeText = useMemo(
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText)?.value, () => inputs.find((item) => item.key === SystemInputEnum.welcomeText),
[inputs] [inputs]
); );
@@ -30,22 +30,27 @@ const NodeUserGuide = ({
<QuestionOutlineIcon display={['none', 'inline']} ml={1} /> <QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip> </MyTooltip>
</Flex> </Flex>
<Textarea {welcomeText && (
className="nodrag" <Textarea
rows={6} className="nodrag"
resize={'both'} rows={6}
defaultValue={welcomeText} resize={'both'}
bg={'myWhite.500'} defaultValue={welcomeText.value}
placeholder={welcomeTextTip} bg={'myWhite.500'}
onChange={(e) => { placeholder={welcomeTextTip}
onChangeNode({ onChange={(e) => {
moduleId: props.moduleId, onChangeNode({
key: SystemInputEnum.welcomeText, moduleId: props.moduleId,
type: 'inputs', key: SystemInputEnum.welcomeText,
value: e.target.value type: 'inputs',
}); value: {
}} ...welcomeText,
/> value: e.target.value
}
});
}}
/>
)}
</> </>
</Container> </Container>
</NodeCard> </NodeCard>

View File

@@ -40,10 +40,13 @@ const NodeUserGuide = ({
moduleId: props.moduleId, moduleId: props.moduleId,
key: SystemInputEnum.variables, key: SystemInputEnum.variables,
type: 'inputs', type: 'inputs',
value value: {
...inputs.find((item) => item.key === SystemInputEnum.variables),
value
}
}); });
}, },
[onChangeNode, props.moduleId] [inputs, onChangeNode, props.moduleId]
); );
const onclickSubmit = useCallback( const onclickSubmit = useCallback(

View File

@@ -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);

View File

@@ -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);

View File

@@ -1,4 +1,4 @@
import React from 'react'; import React, { useState } from 'react';
import type { FlowInputItemType, FlowModuleItemType } from '@/types/flow'; import type { FlowInputItemType, FlowModuleItemType } from '@/types/flow';
import { import {
Box, Box,
@@ -8,38 +8,129 @@ import {
NumberInputField, NumberInputField,
NumberInputStepper, NumberInputStepper,
NumberIncrementStepper, NumberIncrementStepper,
NumberDecrementStepper NumberDecrementStepper,
Flex
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { FlowInputItemTypeEnum } from '@/constants/flow'; import { FlowInputItemTypeEnum } from '@/constants/flow';
import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { QuestionOutlineIcon } from '@chakra-ui/icons';
import dynamic from 'next/dynamic';
import MySelect from '@/components/Select'; import MySelect from '@/components/Select';
import MySlider from '@/components/Slider'; import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import TargetHandle from './TargetHandle'; import TargetHandle from './TargetHandle';
import MyIcon from '@/components/Icon';
const SetInputFieldModal = dynamic(() => import('../modules/SetInputFieldModal'));
export const Label = ({ export const Label = ({
required = false, moduleId,
children, inputKey,
description onChangeNode,
}: { ...item
required?: boolean; }: FlowInputItemType & {
children: React.ReactNode | string; moduleId: string;
description?: string; inputKey: string;
}) => ( onChangeNode: FlowModuleItemType['onChangeNode'];
<Box as={'label'} display={'inline-block'} position={'relative'}> }) => {
{children} const { required = false, description, edit, label, type, valueType } = item;
{required && ( const [editField, setEditField] = useState<FlowInputItemType>();
<Box position={'absolute'} top={'-2px'} right={'-10px'} color={'red.500'} fontWeight={'bold'}>
* 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> </Box>
)}
{description && ( {(type === FlowInputItemTypeEnum.target || valueType) && (
<MyTooltip label={description} forceShow> <TargetHandle handleKey={inputKey} valueType={valueType} />
<QuestionOutlineIcon display={['none', 'inline']} ml={1} /> )}
</MyTooltip>
)} {edit && (
</Box> <>
); <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 = ({ const RenderBody = ({
flowInputList, flowInputList,
@@ -59,13 +150,12 @@ const RenderBody = ({
item.type !== FlowInputItemTypeEnum.hidden && ( item.type !== FlowInputItemTypeEnum.hidden && (
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}> <Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
{!!item.label && ( {!!item.label && (
<Label required={item.required} description={item.description}> <Label
{item.label} moduleId={moduleId}
onChangeNode={onChangeNode}
{(item.type === FlowInputItemTypeEnum.target || item.valueType) && ( inputKey={item.key}
<TargetHandle handleKey={item.key} valueType={item.valueType} /> {...item}
)} />
</Label>
)} )}
<Box mt={2} className={'nodrag'}> <Box mt={2} className={'nodrag'}>
{item.type === FlowInputItemTypeEnum.numberInput && ( {item.type === FlowInputItemTypeEnum.numberInput && (
@@ -76,8 +166,12 @@ const RenderBody = ({
onChange={(e) => { onChange={(e) => {
onChangeNode({ onChangeNode({
moduleId, moduleId,
type: 'inputs',
key: item.key, key: item.key,
value: Number(e) value: {
...item,
value: Number(e)
}
}); });
}} }}
> >
@@ -95,8 +189,12 @@ const RenderBody = ({
onChange={(e) => { onChange={(e) => {
onChangeNode({ onChangeNode({
moduleId, moduleId,
type: 'inputs',
key: item.key, key: item.key,
value: e.target.value value: {
...item,
value: e.target.value
}
}); });
}} }}
/> />
@@ -110,8 +208,12 @@ const RenderBody = ({
onChange={(e) => { onChange={(e) => {
onChangeNode({ onChangeNode({
moduleId, moduleId,
type: 'inputs',
key: item.key, key: item.key,
value: e.target.value value: {
...item,
value: e.target.value
}
}); });
}} }}
/> />
@@ -124,8 +226,12 @@ const RenderBody = ({
onchange={(e) => { onchange={(e) => {
onChangeNode({ onChangeNode({
moduleId, moduleId,
type: 'inputs',
key: item.key, key: item.key,
value: e value: {
...item,
value: e
}
}); });
}} }}
/> />
@@ -142,8 +248,12 @@ const RenderBody = ({
onChange={(e) => { onChange={(e) => {
onChangeNode({ onChangeNode({
moduleId, moduleId,
type: 'inputs',
key: item.key, key: item.key,
value: e value: {
...item,
value: e
}
}); });
}} }}
/> />

View File

@@ -1,37 +1,147 @@
import React from 'react'; import React, { useCallback, useState } from 'react';
import type { FlowOutputItemType } from '@/types/flow'; import type { FlowModuleItemType, FlowOutputItemType } from '@/types/flow';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { FlowOutputItemTypeEnum } from '@/constants/flow'; import { FlowOutputItemTypeEnum } from '@/constants/flow';
import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { Handle, Position } from 'reactflow'; import { Handle, Position } from 'reactflow';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import SourceHandle from './SourceHandle'; import SourceHandle from './SourceHandle';
import MyIcon from '@/components/Icon';
import dynamic from 'next/dynamic';
const SetOutputFieldModal = dynamic(() => import('../modules/SetOutputFieldModal'));
const Label = ({ const Label = ({
children, moduleId,
description outputKey,
}: { delOutputByKey,
children: React.ReactNode | string; updateOutput,
description?: string; onChangeNode,
}) => ( addUpdateOutput,
<Flex as={'label'} justifyContent={'right'} alignItems={'center'} position={'relative'}> ...item
{description && ( }: FlowOutputItemType & {
<MyTooltip label={description} forceShow> outputKey: string;
<QuestionOutlineIcon display={['none', 'inline']} mr={1} /> moduleId: string;
</MyTooltip> delOutputByKey: (key: string) => void;
)} updateOutput: (key: string, val: FlowOutputItemType) => void;
{children} addUpdateOutput: (val: FlowOutputItemType) => void;
</Flex> 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 ( return (
<> <>
{flowOutputList.map( {flowOutputList.map(
(item) => (item) =>
item.type !== FlowOutputItemTypeEnum.hidden && ( item.type !== FlowOutputItemTypeEnum.hidden && (
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}> <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'}> <Box mt={FlowOutputItemTypeEnum.answer ? 0 : 2} className={'nodrag'}>
{item.type === FlowOutputItemTypeEnum.source && ( {item.type === FlowOutputItemTypeEnum.source && (
<SourceHandle handleKey={item.key} valueType={item.valueType} /> <SourceHandle handleKey={item.key} valueType={item.valueType} />

View File

@@ -16,7 +16,7 @@ const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
valueType valueType
? FlowValueTypeStyle[valueType] ? FlowValueTypeStyle[valueType]
: (FlowValueTypeStyle[FlowValueTypeEnum.any] as any), : (FlowValueTypeStyle[FlowValueTypeEnum.any] as any),
[] [valueType]
); );
return ( return (

View File

@@ -71,6 +71,9 @@ const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'),
const NodeExtract = dynamic(() => import('./components/Nodes/NodeExtract'), { const NodeExtract = dynamic(() => import('./components/Nodes/NodeExtract'), {
ssr: false ssr: false
}); });
const NodeHttp = dynamic(() => import('./components/Nodes/NodeHttp'), {
ssr: false
});
import 'reactflow/dist/style.css'; import 'reactflow/dist/style.css';
import styles from './index.module.scss'; import styles from './index.module.scss';
@@ -87,7 +90,8 @@ const nodeTypes = {
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch, [FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
[FlowModuleTypeEnum.answerNode]: NodeAnswer, [FlowModuleTypeEnum.answerNode]: NodeAnswer,
[FlowModuleTypeEnum.classifyQuestion]: NodeCQNode, [FlowModuleTypeEnum.classifyQuestion]: NodeCQNode,
[FlowModuleTypeEnum.contentExtract]: NodeExtract [FlowModuleTypeEnum.contentExtract]: NodeExtract,
[FlowModuleTypeEnum.httpRequest]: NodeHttp
// [FlowModuleTypeEnum.empty]: EmptyModule // [FlowModuleTypeEnum.empty]: EmptyModule
}; };
const edgeTypes = { const edgeTypes = {
@@ -124,11 +128,10 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
const flow2AppModules = useCallback(() => { const flow2AppModules = useCallback(() => {
const modules: AppModuleItemType[] = nodes.map((item) => ({ const modules: AppModuleItemType[] = nodes.map((item) => ({
moduleId: item.data.moduleId, moduleId: item.data.moduleId,
position: item.position,
flowType: item.data.flowType, flowType: item.data.flowType,
position: item.position,
inputs: item.data.inputs.map((item) => ({ inputs: item.data.inputs.map((item) => ({
key: item.key, ...item,
value: item.value,
connected: item.type !== FlowInputItemTypeEnum.target connected: item.type !== FlowInputItemTypeEnum.target
})), })),
outputs: item.data.outputs.map((item) => ({ outputs: item.data.outputs.map((item) => ({
@@ -163,7 +166,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
return modules; return modules;
}, [edges, nodes]); }, [edges, nodes]);
const onChangeNode = useCallback( const onChangeNode = useCallback(
({ moduleId, key, type = 'inputs', value, valueKey = 'value' }: FlowModuleItemChangeProps) => { ({ moduleId, key, type = 'inputs', value }: FlowModuleItemChangeProps) => {
setNodes((nodes) => setNodes((nodes) =>
nodes.map((node) => { nodes.map((node) => {
if (node.id !== moduleId) return node; if (node.id !== moduleId) return node;
@@ -172,18 +175,43 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
...node, ...node,
data: { data: {
...node.data, ...node.data,
inputs: node.data.inputs.map((item) => { inputs: node.data.inputs.map((item) => (item.key === key ? value : item))
if (item.key === key) {
return {
...item,
[valueKey]: value
};
}
return 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 { return {
...node, ...node,

View File

@@ -59,7 +59,7 @@ const FileSelect = ({
)} )}
{isCsv && ( {isCsv && (
<Box <Box
my={3} mt={1}
cursor={'pointer'} cursor={'pointer'}
textDecoration={'underline'} textDecoration={'underline'}
color={'myBlue.600'} color={'myBlue.600'}

View 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
};
};

View File

@@ -51,6 +51,11 @@ export type ContextExtractAgentItemType = {
key: string; key: string;
required: boolean; required: boolean;
}; };
export type HttpFieldItemType = {
label: string;
key: string;
type: `${FlowValueTypeEnum}`;
};
export type VariableItemType = { export type VariableItemType = {
id: string; id: string;
@@ -63,12 +68,11 @@ export type VariableItemType = {
}; };
/* app module */ /* app module */
export type AppModuleInputItemType = { key: string; value?: any; connected?: boolean };
export type AppModuleItemType = { export type AppModuleItemType = {
moduleId: string; moduleId: string;
position?: XYPosition; position?: XYPosition;
flowType: `${FlowModuleTypeEnum}`; flowType: `${FlowModuleTypeEnum}`;
inputs: AppModuleInputItemType[]; inputs: FlowInputItemType[];
outputs: FlowOutputItemType[]; outputs: FlowOutputItemType[];
}; };

View File

@@ -10,10 +10,9 @@ import { FlowModuleTypeEnum } from '@/constants/flow';
export type FlowModuleItemChangeProps = { export type FlowModuleItemChangeProps = {
moduleId: string; moduleId: string;
type?: 'inputs' | 'outputs'; type: 'inputs' | 'outputs' | 'addInput' | 'delInput';
key: string; key: string;
value: any; value: any;
valueKey?: keyof FlowInputItemType & keyof FlowBodyItemType;
}; };
export type FlowInputItemType = { export type FlowInputItemType = {
@@ -22,6 +21,7 @@ export type FlowInputItemType = {
valueType?: `${FlowValueTypeEnum}`; valueType?: `${FlowValueTypeEnum}`;
type: `${FlowInputItemTypeEnum}`; type: `${FlowInputItemTypeEnum}`;
label: string; label: string;
edit?: boolean;
connected?: boolean; connected?: boolean;
description?: string; description?: string;
placeholder?: string; placeholder?: string;
@@ -40,6 +40,7 @@ export type FlowOutputTargetItemType = {
export type FlowOutputItemType = { export type FlowOutputItemType = {
key: string; // 字段名 key: string; // 字段名
label?: string; label?: string;
edit?: boolean;
description?: string; description?: string;
valueType?: `${FlowValueTypeEnum}`; valueType?: `${FlowValueTypeEnum}`;
type?: `${FlowOutputItemTypeEnum}`; type?: `${FlowOutputItemTypeEnum}`;

View File

@@ -75,11 +75,17 @@ export const appModule2FlowNode = ({
const template = const template =
ModuleTemplatesFlat.find((template) => template.flowType === item.flowType) || EmptyModule; 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 // replace item data
const moduleItem: FlowModuleItemType = { const moduleItem: FlowModuleItemType = {
...item, ...item,
...template, ...template,
inputs: template.inputs.map((templateInput) => { inputs: concatInputs.map((templateInput) => {
// use latest inputs // use latest inputs
const itemInput = item.inputs.find((item) => item.key === templateInput.key) || templateInput; const itemInput = item.inputs.find((item) => item.key === templateInput.key) || templateInput;
return { return {
@@ -87,7 +93,6 @@ export const appModule2FlowNode = ({
value: itemInput.value value: itemInput.value
}; };
}), }),
// 合并 template 和数据库,文案以 template 为准
outputs: item.outputs.map((output) => { outputs: item.outputs.map((output) => {
// unChange outputs // unChange outputs
const templateOutput = template.outputs.find((item) => item.key === output.key); const templateOutput = template.outputs.find((item) => item.key === output.key);

View File

@@ -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 { chatModelList, vectorModelList } from '@/store/static';
import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow'; import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app'; import { SystemInputEnum } from '@/constants/app';
import { TaskResponseKeyEnum } from '@/constants/chat'; import { TaskResponseKeyEnum } from '@/constants/chat';
import type { SelectedKbType } from '@/types/plugin'; import type { SelectedKbType } from '@/types/plugin';
import { FlowInputItemType } from '@/types/flow';
export type EditFormType = { export type EditFormType = {
chatModel: { chatModel: {
@@ -64,7 +65,7 @@ export const appModules2Form = (modules: AppModuleItemType[]) => {
key key
}: { }: {
formKey: string; formKey: string;
inputs: AppModuleInputItemType[]; inputs: FlowInputItemType[];
key: string; key: string;
}) => { }) => {
const propertyPath = formKey.split('.'); const propertyPath = formKey.split('.');
@@ -148,7 +149,7 @@ export const appModules2Form = (modules: AppModuleItemType[]) => {
return defaultAppForm; return defaultAppForm;
}; };
const chatModelInput = (formData: EditFormType): AppModuleInputItemType[] => [ const chatModelInput = (formData: EditFormType): FlowInputItemType[] => [
{ {
key: 'model', key: 'model',
value: formData.chatModel.model, value: formData.chatModel.model,