4.8 preview (#1288)

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

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

* perf: workflow ux

* system config

* Newflow (#89)

* docs: Add doc for Xinference (#1266)

Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>

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

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

* perf: workflow ux

* system config

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

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

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

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

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

This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be.

* rename code

* move code

* update flow

* input type selector

* perf: workflow runtime

* feat: node adapt newflow

* feat: adapt plugin

* feat: 360 connection

* check workflow

* perf: flow 性能

* change plugin input type (#81)

* change plugin input type

* plugin label mode

* perf: nodecard

* debug

* perf: debug ui

* connection ui

* change workflow ui (#82)

* feat: workflow debug

* adapt openAPI for new workflow (#83)

* adapt openAPI for new workflow

* i18n

* perf: plugin debug

* plugin input ui

* delete

* perf: global variable select

* fix rebase

* perf: workflow performance

* feat: input render type icon

* input icon

* adapt flow (#84)

* adapt newflow

* temp

* temp

* fix

* feat: app schedule trigger

* feat: app schedule trigger

* perf: schedule ui

* feat: ioslatevm run js code

* perf: workflow varialbe table ui

* feat: adapt simple mode

* feat: adapt input params

* output

* feat: adapt tamplate

* fix: ts

* add if-else module (#86)

* perf: worker

* if else node

* perf: tiktoken worker

* fix: ts

* perf: tiktoken

* fix if-else node (#87)

* fix if-else node

* type

* fix

* perf: audio render

* perf: Parallel worker

* log

* perf: if else node

* adapt plugin

* prompt

* perf: reference ui

* reference ui

* handle ux

* template ui and plugin tool

* adapt v1 workflow

* adapt v1 workflow completions

* perf: time variables

* feat: workflow keyboard shortcuts

* adapt v1 workflow

* update workflow example doc (#88)

* fix: simple mode select tool

---------

Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
Co-authored-by: Carson Yang <yangchuansheng33@gmail.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>

* doc

* perf: extract node

* extra node field

* update plugin version

* doc

* variable

* change doc & fix prompt editor (#90)

* fold workflow code

* value type label

---------

Signed-off-by: Carson Yang <yangchuansheng33@gmail.com>
Co-authored-by: Carson Yang <yangchuansheng33@gmail.com>
Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-04-25 17:51:20 +08:00
committed by GitHub
parent b08d81f887
commit 439c819ff1
505 changed files with 23570 additions and 18215 deletions

View File

@@ -11,11 +11,11 @@ import { UserUpdateParams } from '@/types/user';
import { langMap, setLngStore } from '@/web/common/utils/i18n';
import { useRouter } from 'next/router';
import MySelect from '@fastgpt/web/components/common/MySelect';
import TimezoneSelect from '@fastgpt/web/components/common/MySelect/TimezoneSelect';
const Individuation = () => {
const { t, i18n } = useTranslation();
const timezones = useRef(timezoneList());
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
const { userInfo, updateUserInfo } = useUserStore();
const { toast } = useToast();
const router = useRouter();
@@ -70,19 +70,13 @@ const Individuation = () => {
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '350px']}>
<Box flex={'0 0 80px'}>{t('user.Timezone')}:&nbsp;</Box>
<Select
<TimezoneSelect
value={userInfo?.timezone}
onChange={(e) => {
if (!userInfo) return;
onclickSave({ ...userInfo, timezone: e.target.value });
onclickSave({ ...userInfo, timezone: e });
}}
>
{timezones.current.map((item) => (
<option key={item.value} value={item.value}>
{item.name}
</option>
))}
</Select>
/>
</Flex>
</Card>
</Box>

View File

@@ -597,7 +597,7 @@ const Other = () => {
userSelect={'none'}
onClick={onOpenLaf}
>
<Image src="/imgs/module/laf.png" w={'18px'} alt="laf" />
<Image src="/imgs/workflow/laf.png" w={'18px'} alt="laf" />
<Box ml={2} flex={1}>
laf
</Box>

View File

@@ -1,45 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { connectToDatabase } from '@/service/mongo';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { limit = 50, maxSize = 3 } = req.body as { limit: number; maxSize: number };
await authCert({ req, authRoot: true });
await connectToDatabase();
try {
await PgClient.query(
`ALTER TABLE ${PgDatasetTableName} ADD COLUMN createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP;`
);
} catch (error) {
console.log(error);
}
try {
const result = await MongoChatItem.updateMany(
{ userFeedback: { $exists: true } },
{ $rename: { userFeedback: 'userBadFeedback' } }
);
console.log(result);
} catch (error) {
console.log(error);
}
jsonRes(res, {
data: {}
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -1,106 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { MongoImage } from '@fastgpt/service/common/file/image/schema';
import { MongoImageSchemaType } from '@fastgpt/global/common/file/image/type';
import { delay } from '@fastgpt/global/common/system/utils';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { getNanoid } from '@fastgpt/global/common/string/tools';
let success = 0;
let deleteImg = 0;
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { test = false } = req.body as { test: boolean };
await authCert({ req, authRoot: true });
await connectToDatabase();
success = 0;
deleteImg = 0;
// 取消 pg tmb_id 和 data_id 的null
await PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN tmb_id DROP NOT NULL;`);
await PgClient.query(`ALTER TABLE ${PgDatasetTableName} ALTER COLUMN data_id DROP NOT NULL;`);
// 重新绑定 images 和 collections
const images = await MongoImage.find(
{ 'metadata.fileId': { $exists: true } },
'_id metadata'
).lean();
// 去除 fileId 相同的数据
const fileIdMap = new Map<string, MongoImageSchemaType>();
images.forEach((image) => {
// @ts-ignore
const fileId = image.metadata?.fileId;
if (!fileIdMap.has(fileId) && fileId) {
fileIdMap.set(fileId, image);
}
});
const images2 = Array.from(fileIdMap.values());
console.log('total image list', images2.length);
for await (const image of images2) {
await initImages(image, test);
}
jsonRes(res, {
data: success,
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
export const initImages = async (image: MongoImageSchemaType, test: boolean): Promise<any> => {
try {
//@ts-ignore
const fileId = image.metadata.fileId as string;
if (!fileId) return;
// 找到集合
const collection = await MongoDatasetCollection.findOne({ fileId }, '_id metadata').lean();
if (!collection) {
deleteImg++;
console.log('deleteImg', deleteImg);
if (test) return;
return MongoImage.deleteOne({ _id: image._id });
}
const relatedImageId = getNanoid(24);
// update image
if (!test) {
await Promise.all([
MongoImage.updateMany(
{ 'metadata.fileId': fileId },
{ $set: { 'metadata.relatedId': relatedImageId } }
),
MongoDatasetCollection.findByIdAndUpdate(collection._id, {
$set: {
'metadata.relatedImgId': relatedImageId
}
})
]);
}
success++;
console.log('success', success);
} catch (error) {
console.log(error);
await delay(1000);
return initImages(image, test);
}
};

View File

@@ -1,99 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
import { PgDatasetTableName } from '@fastgpt/global/common/vectorStore/constants';
import { MongoImage } from '@fastgpt/service/common/file/image/schema';
import { MongoImageSchemaType } from '@fastgpt/global/common/file/image/type';
import { delay } from '@fastgpt/global/common/system/utils';
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { DYNAMIC_INPUT_KEY, ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
let success = 0;
let deleteImg = 0;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
// 设置所有app为 inited = false
const result = await MongoApp.updateMany({}, { $set: { inited: false } });
console.log(result);
await initApp();
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}
const systemKeys: string[] = [
ModuleInputKeyEnum.switch,
ModuleInputKeyEnum.httpMethod,
ModuleInputKeyEnum.httpReqUrl,
ModuleInputKeyEnum.httpHeaders,
DYNAMIC_INPUT_KEY,
ModuleInputKeyEnum.addInputParam
];
const initApp = async (): Promise<any> => {
const app = await MongoApp.findOne({ inited: false }).sort({ updateTime: -1 });
if (!app) {
return;
}
try {
const modules = JSON.parse(JSON.stringify(app.modules)) as ModuleItemType[];
let update = false;
// 找到http模块
modules.forEach((module) => {
if (module.flowType === 'httpRequest') {
const method = module.inputs.find((input) => input.key === ModuleInputKeyEnum.httpMethod);
if (method?.value === 'POST') {
module.inputs.forEach((input) => {
// 更新非系统字段的key
if (!systemKeys.includes(input.key)) {
// 更新output的target
modules.forEach((item) => {
item.outputs.forEach((output) => {
output.targets.forEach((target) => {
if (target.moduleId === module.moduleId && target.key === input.key) {
target.key = `data.${input.key}`;
}
});
});
});
// 更新key
input.key = `data.${input.key}`;
update = true;
}
});
}
}
});
if (update) {
console.log('update http app');
app.modules = modules;
}
app.inited = true;
await app.save();
console.log(++success);
return initApp();
} catch (error) {
console.log(error);
await delay(1000);
return initApp();
}
};

View File

@@ -14,7 +14,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
name = 'APP',
avatar,
type = AppTypeEnum.advanced,
modules
modules,
edges
} = req.body as CreateAppParams;
if (!name || !Array.isArray(modules)) {
@@ -34,7 +35,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
teamId,
tmbId,
modules,
type
edges,
type,
version: 'v2'
});
jsonRes(res, {

View File

@@ -4,16 +4,27 @@ import { connectToDatabase } from '@/service/mongo';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import type { AppUpdateParams } from '@fastgpt/global/core/app/api';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { getLLMModel } from '@fastgpt/service/core/ai/model';
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
import { getScheduleTriggerApp } from '@/service/core/app/utils';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { name, avatar, type, intro, modules, permission, teamTags } =
req.body as AppUpdateParams;
const {
name,
avatar,
type,
intro,
modules: nodes,
edges,
permission,
teamTags
} = req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
if (!appId) {
@@ -23,18 +34,18 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 凭证校验
await authApp({ req, authToken: true, appId, per: permission ? 'owner' : 'w' });
// check modules
// format nodes data
// 1. dataset search limit, less than model quoteMaxToken
if (modules) {
if (nodes) {
let maxTokens = 3000;
modules.forEach((item) => {
nodes.forEach((item) => {
if (
item.flowType === FlowNodeTypeEnum.chatNode ||
item.flowType === FlowNodeTypeEnum.tools
item.flowNodeType === FlowNodeTypeEnum.chatNode ||
item.flowNodeType === FlowNodeTypeEnum.tools
) {
const model =
item.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || '';
const chatModel = getLLMModel(model);
const quoteMaxToken = chatModel.quoteMaxToken || 3000;
@@ -42,10 +53,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
});
modules.forEach((item) => {
if (item.flowType === FlowNodeTypeEnum.datasetSearchNode) {
nodes.forEach((item) => {
if (item.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) {
item.inputs.forEach((input) => {
if (input.key === ModuleInputKeyEnum.datasetMaxTokens) {
if (input.key === NodeInputKeyEnum.datasetMaxTokens) {
const val = input.value as number;
if (val > maxTokens) {
input.value = maxTokens;
@@ -55,6 +66,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
});
}
// 2. get schedule plan
const { scheduledTriggerConfig } = splitGuideModule(getGuideModule(nodes || []));
// 更新模型
await MongoApp.updateOne(
@@ -67,13 +80,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
avatar,
intro,
permission,
version: 'v2',
teamTags: teamTags,
...(modules && {
modules
})
...(nodes && {
modules: nodes
}),
...(edges && {
edges
}),
scheduledTriggerConfig,
scheduledTriggerNextTime: scheduledTriggerConfig
? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig)
: null
}
);
getScheduleTriggerApp();
jsonRes(res);
} catch (err) {
jsonRes(res, {

View File

@@ -4,8 +4,8 @@ import { connectToDatabase } from '@/service/mongo';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import type { AppUpdateParams } from '@fastgpt/global/core/app/api';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { getLLMModel } from '@fastgpt/service/core/ai/model';
/* 获取我的模型 */
@@ -29,9 +29,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
let maxTokens = 3000;
modules.forEach((item) => {
if (item.flowType === FlowNodeTypeEnum.chatNode) {
if (item.flowNodeType === FlowNodeTypeEnum.chatNode) {
const model =
item.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
item.inputs.find((item) => item.key === NodeInputKeyEnum.aiModel)?.value || '';
const chatModel = getLLMModel(model);
const quoteMaxToken = chatModel.quoteMaxToken || 3000;
@@ -40,9 +40,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
modules.forEach((item) => {
if (item.flowType === FlowNodeTypeEnum.datasetSearchNode) {
if (item.flowNodeType === FlowNodeTypeEnum.datasetSearchNode) {
item.inputs.forEach((input) => {
if (input.key === ModuleInputKeyEnum.datasetMaxTokens) {
if (input.key === NodeInputKeyEnum.datasetMaxTokens) {
const val = input.value as number;
if (val > maxTokens) {
input.value = maxTokens;

View File

@@ -1,9 +1,8 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { sseErrRes } from '@fastgpt/service/common/response';
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { responseWrite } from '@fastgpt/service/common/response';
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { pushChatUsage } from '@/service/support/wallet/usage/push';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import type { ChatItemType, ChatItemValueItemType } from '@fastgpt/global/core/chat/type';
@@ -11,13 +10,15 @@ import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatch/utils';
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
import { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
export type Props = {
history: ChatItemType[];
prompt: ChatItemValueItemType[];
modules: ModuleItemType[];
nodes: RuntimeNodeItemType[];
edges: RuntimeEdgeItemType[];
variables: Record<string, any>;
appId: string;
appName: string;
@@ -32,14 +33,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
res.end();
});
let { modules = [], history = [], prompt, variables = {}, appName, appId } = req.body as Props;
const {
nodes = [],
edges = [],
history = [],
prompt,
variables = {},
appName,
appId
} = req.body as Props;
try {
await connectToDatabase();
if (!history || !modules || !prompt || prompt.length === 0) {
if (!history || !nodes || !prompt || prompt.length === 0) {
throw new Error('Prams Error');
}
if (!Array.isArray(modules)) {
throw new Error('history is not array');
if (!Array.isArray(nodes)) {
throw new Error('Nodes is not array');
}
if (!Array.isArray(edges)) {
throw new Error('Edges is not array');
}
/* user auth */
@@ -64,13 +76,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
tmbId,
user,
appId,
modules: setEntryEntries(modules),
variables,
inputFiles: files,
histories: history,
startParams: {
runtimeNodes: nodes,
runtimeEdges: edges,
variables: {
...variables,
userChatInput: text
},
inputFiles: files,
histories: history,
stream: true,
detail: true,
maxRunTimes: 200

View File

@@ -2,13 +2,13 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {

View File

@@ -2,9 +2,9 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { InitChatResponse, InitOutLinkChatProps } from '@/global/core/chat/api.d';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
import { authOutLink } from '@/service/support/permission/auth/outLink';

View File

@@ -1,9 +1,9 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { InitChatResponse, InitTeamChatProps } from '@/global/core/chat/api.d';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoApp } from '@fastgpt/service/core/app/schema';

View File

@@ -2,14 +2,14 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getChatModelNameListByModules } from '@/service/core/app/module';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {

View File

@@ -6,7 +6,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
import { countPromptTokens } from '@fastgpt/service/common/string/tiktoken/index';
import { getVectorModel } from '@fastgpt/service/core/ai/model';
import { hasSameValue } from '@/service/core/dataset/data/utils';
import { insertData2Dataset } from '@/service/core/dataset/data/controller';
@@ -60,7 +60,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}));
// token check
const token = countPromptTokens(formatQ, 'system');
const token = await countPromptTokens(formatQ + formatA, '');
const vectorModelData = getVectorModel(vectorModel);
if (token > vectorModelData.maxToken) {

View File

@@ -24,7 +24,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...body,
parentId: null,
teamId,
tmbId
tmbId,
version: 'v2'
}
],
{ session }
@@ -43,7 +44,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
pluginUid: item.name
},
teamId,
tmbId
tmbId,
version: 'v2'
})),
{
session
@@ -59,7 +61,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const { _id } = await MongoPlugin.create({
...body,
teamId,
tmbId
tmbId,
version: 'v2'
});
jsonRes(res, {
data: _id

View File

@@ -15,7 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
if (!pluginId) {
throw new Error('缺少参数');
}
await authPluginCrud({ req, authToken: true, id: pluginId, per: 'owner' });
await authPluginCrud({ req, authToken: true, pluginId, per: 'owner' });
await mongoSessionRun(async (session) => {
await MongoPlugin.deleteMany(

View File

@@ -7,7 +7,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
try {
const { id } = req.query as { id: string };
await connectToDatabase();
const { plugin } = await authPluginCrud({ req, authToken: true, id, per: 'r' });
const { plugin } = await authPluginCrud({ req, authToken: true, pluginId: id, per: 'r' });
jsonRes(res, {
data: plugin

View File

@@ -4,9 +4,9 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { getPluginPreviewModule } from '@fastgpt/service/core/plugin/controller';
import { getPluginPreviewNode } from '@fastgpt/service/core/plugin/controller';
import { authPluginCanUse } from '@fastgpt/service/support/permission/auth/plugin';
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -17,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await authPluginCanUse({ id, teamId, tmbId });
jsonRes<FlowNodeTemplateType>(res, {
data: await getPluginPreviewModule({ id })
data: await getPluginPreviewNode({ id })
});
} catch (err) {
jsonRes(res, {

View File

@@ -2,9 +2,9 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -14,8 +14,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const data: FlowNodeTemplateType[] =
global.communityPlugins?.map((plugin) => ({
id: plugin.id,
pluginId: plugin.id,
templateType: plugin.templateType ?? FlowNodeTemplateTypeEnum.other,
flowType: FlowNodeTypeEnum.pluginModule,
flowNodeType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,

View File

@@ -3,9 +3,9 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoPlugin } from '@fastgpt/service/core/plugin/schema';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/module/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -39,9 +39,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
const data: FlowNodeTemplateType[] = userPlugins.map((plugin) => ({
id: String(plugin._id),
parentId: String(plugin.parentId),
pluginId: String(plugin._id),
pluginType: plugin.type,
templateType: FlowNodeTemplateTypeEnum.personalPlugin,
flowType: FlowNodeTypeEnum.pluginModule,
flowNodeType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,

View File

@@ -13,19 +13,25 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await connectToDatabase();
const body = req.body as UpdatePluginParams;
const { id, ...props } = body;
const { id, modules, edges, ...props } = body;
const { teamId, tmbId } = await authPluginCrud({ req, authToken: true, id, per: 'owner' });
const { teamId, tmbId } = await authPluginCrud({
req,
authToken: true,
pluginId: id,
per: 'owner'
});
const updateData = {
name: props.name,
intro: props.intro,
avatar: props.avatar,
parentId: props.parentId,
...(props.modules &&
props.modules.length > 0 && {
modules: props.modules
}),
version: 'v2',
...(modules && {
modules: modules
}),
...(edges && { edges }),
metadata: props.metadata
};
@@ -98,7 +104,8 @@ const updateHttpChildrenPlugin = async ({
pluginUid: plugin.name
},
teamId,
tmbId
tmbId,
version: 'v2'
}
],
{
@@ -111,7 +118,14 @@ const updateHttpChildrenPlugin = async ({
for await (const plugin of schemaPlugins) {
const dbPlugin = dbPlugins.find((p) => plugin.name === p.metadata?.pluginUid);
if (dbPlugin) {
await MongoPlugin.findByIdAndUpdate(dbPlugin._id, plugin, { session });
await MongoPlugin.findByIdAndUpdate(
dbPlugin._id,
{
...plugin,
version: 'v2'
},
{ session }
);
}
}
};

View File

@@ -0,0 +1,97 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { jsonRes } from '@fastgpt/service/common/response';
import { pushChatUsage } from '@/service/support/wallet/usage/push';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
import { PostWorkflowDebugProps, PostWorkflowDebugResponse } from '@/global/core/workflow/api';
import { authPluginCrud } from '@fastgpt/service/support/permission/auth/plugin';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const {
nodes = [],
edges = [],
variables = {},
appId,
pluginId
} = req.body as PostWorkflowDebugProps;
try {
await connectToDatabase();
if (!nodes) {
throw new Error('Prams Error');
}
if (!Array.isArray(nodes)) {
throw new Error('Nodes is not array');
}
if (!Array.isArray(edges)) {
throw new Error('Edges is not array');
}
/* user auth */
const [{ teamId, tmbId }] = await Promise.all([
authCert({
req,
authToken: true
}),
appId && authApp({ req, authToken: true, appId, per: 'r' }),
pluginId && authPluginCrud({ req, authToken: true, pluginId, per: 'r' })
]);
// auth balance
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
/* start process */
const { flowUsages, flowResponses, debugResponse } = await dispatchWorkFlow({
res,
mode: 'debug',
teamId,
tmbId,
user,
appId,
runtimeNodes: nodes,
runtimeEdges: edges,
variables: {
...variables,
userChatInput: ''
},
inputFiles: [],
histories: [],
stream: false,
detail: true,
maxRunTimes: 200
});
pushChatUsage({
appName: '工作流Debug',
appId,
teamId,
tmbId,
source: UsageSourceEnum.fastgpt,
flowUsages
});
jsonRes<PostWorkflowDebugResponse>(res, {
data: {
...debugResponse,
flowResponses
}
});
} catch (err: any) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export const config = {
api: {
bodyParser: {
sizeLimit: '10mb'
},
responseLimit: '20mb'
}
};

View File

@@ -1,7 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import ChatCompletion from '@/pages/api/v1/chat/completions';
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
return ChatCompletion(req, res);
});

View File

@@ -1,71 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { HttpBodyType } from '@fastgpt/global/core/module/api.d';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { authRequestFromLocal } from '@fastgpt/service/support/permission/auth/common';
type Props = HttpBodyType<{
input: string;
rule?: string;
}>;
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { input, rule = '' } = req.body as Props;
await authRequestFromLocal({ req });
const result = (() => {
if (typeof input === 'string') {
const defaultReg: any[] = [
'',
undefined,
'undefined',
null,
'null',
false,
'false',
0,
'0',
'none'
];
const customReg = rule.split('\n');
defaultReg.push(...customReg);
return !defaultReg.find((item) => {
const reg = typeof item === 'string' ? stringToRegex(item) : null;
if (reg) {
return reg.test(input);
}
return input === item;
});
}
return !!input;
})();
res.json({
...(result
? {
true: true
}
: {
false: false
})
});
} catch (err) {
console.log(err);
res.status(500).send(getErrText(err));
}
}
function stringToRegex(str: string) {
const regexFormat = /^\/(.+)\/([gimuy]*)$/;
const match = str.match(regexFormat);
if (match) {
const [, pattern, flags] = match;
return new RegExp(pattern, flags);
} else {
return null;
}
}

View File

@@ -1,35 +1,25 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { HttpBodyType } from '@fastgpt/global/core/module/api.d';
import type { HttpBodyType } from '@fastgpt/global/core/workflow/api.d';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { addCustomFeedbacks } from '@fastgpt/service/core/chat/controller';
import { authRequestFromLocal } from '@fastgpt/service/support/permission/auth/common';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
type Props = HttpBodyType<{
appId: string;
chatId?: string;
responseChatItemId?: string;
defaultFeedback: string;
customFeedback: string;
}>;
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const {
appId,
chatId,
responseChatItemId: chatItemId,
defaultFeedback,
customFeedback
customFeedback,
system_addInputParam: { appId, chatId, responseChatItemId: chatItemId }
} = req.body as Props;
await authRequestFromLocal({ req });
const feedback = customFeedback || defaultFeedback;
if (!feedback) {
return res.json({
response: ''
});
if (!customFeedback) {
return res.json({});
}
// wait the chat finish
@@ -38,19 +28,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
appId,
chatId,
chatItemId,
feedbacks: [feedback]
feedbacks: [customFeedback]
});
}, 60000);
if (!chatId || !chatItemId) {
return res.json({
response: `\\n\\n**自动反馈调试**: ${feedback}\\n\\n`
[NodeOutputKeyEnum.answerText]: `\\n\\n**自动反馈调试**: "${customFeedback}"\\n\\n`
});
}
return res.json({
response: ''
});
return res.json({});
} catch (err) {
console.log(err);
res.status(500).send(getErrText(err));

View File

@@ -1,8 +1,9 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { HttpBodyType } from '@fastgpt/global/core/module/api.d';
import type { HttpBodyType } from '@fastgpt/global/core/workflow/api.d';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { authRequestFromLocal } from '@fastgpt/service/support/permission/auth/common';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
type Props = HttpBodyType<{
text: string;
@@ -11,10 +12,7 @@ type Props = HttpBodyType<{
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const {
text,
DYNAMIC_INPUT_KEY: { ...obj }
} = req.body as Props;
const { text, [NodeInputKeyEnum.addInputParam]: obj } = req.body as Props;
await authRequestFromLocal({ req });

View File

@@ -8,7 +8,7 @@ import { getAIApi } from '@fastgpt/service/core/ai/config';
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/module/utils';
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
const upload = getUploadModel({

View File

@@ -5,11 +5,16 @@ import { sseErrRes, jsonRes } from '@fastgpt/service/common/response';
import { addLog } from '@fastgpt/service/common/system/log';
import { withNextCors } from '@fastgpt/service/common/middle/cors';
import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d';
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
import { textAdaptGptResponse } from '@fastgpt/global/core/module/runtime/utils';
import {
getDefaultEntryNodeIds,
initWorkflowEdgeStatus,
storeNodes2RuntimeNodes,
textAdaptGptResponse
} from '@fastgpt/global/core/workflow/runtime/utils';
import { GPTMessages2Chats, chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { saveChat } from '@/service/utils/chat/saveChat';
@@ -32,9 +37,11 @@ import { AuthOutLinkChatProps } from '@fastgpt/global/support/outLink/api';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatch/utils';
import { UserChatItemType } from '@fastgpt/global/core/chat/type';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { dispatchWorkFlowV1 } from '@fastgpt/service/core/workflow/dispatchV1';
import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatchV1/utils';
type FastGptWebChatProps = {
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
@@ -167,51 +174,79 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId;
/* start flow controller */
const { flowResponses, flowUsages, assistantResponses } = await dispatchWorkFlow({
res,
mode: 'chat',
user,
teamId: String(teamId),
tmbId: String(tmbId),
appId: String(app._id),
chatId,
responseChatItemId,
modules: setEntryEntries(app.modules),
variables,
inputFiles: files,
histories: concatHistories,
startParams: {
userChatInput: text
},
stream,
detail,
maxRunTimes: 200
});
const { flowResponses, flowUsages, assistantResponses } = await (async () => {
if (app.version === 'v2') {
return dispatchWorkFlow({
res,
mode: 'chat',
user,
teamId: String(teamId),
tmbId: String(tmbId),
appId: String(app._id),
chatId,
responseChatItemId,
runtimeNodes: storeNodes2RuntimeNodes(app.modules, getDefaultEntryNodeIds(app.modules)),
runtimeEdges: initWorkflowEdgeStatus(app.edges),
variables: {
...variables,
userChatInput: text
},
inputFiles: files,
histories: concatHistories,
stream,
detail,
maxRunTimes: 200
});
}
return dispatchWorkFlowV1({
res,
mode: 'chat',
user,
teamId: String(teamId),
tmbId: String(tmbId),
appId: String(app._id),
chatId,
responseChatItemId,
//@ts-ignore
modules: setEntryEntries(app.modules),
variables,
inputFiles: files,
histories: concatHistories,
startParams: {
userChatInput: text
},
stream,
detail,
maxRunTimes: 200
});
})();
// save chat
if (chatId) {
const isOwnerUse = !shareId && !spaceTeamId && String(tmbId) === String(app.tmbId);
const source = (() => {
if (shareId) {
return ChatSourceEnum.share;
}
if (authType === 'apikey') {
return ChatSourceEnum.api;
}
if (spaceTeamId) {
return ChatSourceEnum.team;
}
return ChatSourceEnum.online;
})();
await saveChat({
chatId,
appId: app._id,
teamId,
tmbId: tmbId,
variables,
updateUseTime: isOwnerUse, // owner update use time
isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time
shareId,
outLinkUid: outLinkUserId,
source: (() => {
if (shareId) {
return ChatSourceEnum.share;
}
if (authType === 'apikey') {
return ChatSourceEnum.api;
}
if (spaceTeamId) {
return ChatSourceEnum.team;
}
return ChatSourceEnum.online;
})(),
source,
content: [
question,
{

View File

@@ -1,7 +1,6 @@
import React, { useCallback, useRef, useState } from 'react';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { Box, Flex, IconButton, useTheme, useDisclosure, Button } from '@chakra-ui/react';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import { useTranslation } from 'next-i18next';
import { useCopyData } from '@/web/common/hooks/useCopyData';
@@ -9,30 +8,43 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import dynamic from 'next/dynamic';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@/components/MyTooltip';
import ChatTest, { type ChatTestComponentRef } from '@/components/core/module/Flow/ChatTest';
import { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import { flowNode2Modules, filterExportModules } from '@/components/core/module/utils';
import ChatTest, { type ChatTestComponentRef } from '@/components/core/workflow/Flow/ChatTest';
import {
getWorkflowStore,
useFlowProviderStore
} from '@/components/core/workflow/Flow/FlowProvider';
import { flowNode2StoreNodes } from '@/components/core/workflow/utils';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { getErrText } from '@fastgpt/global/common/error/utils';
import MyMenu from '@/components/MyMenu';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import {
checkWorkflowNodeAndConnection,
filterSensitiveNodesData
} from '@/web/core/workflow/utils';
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
type Props = { app: AppSchema; onClose: () => void };
const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
app,
ChatTestRef,
testModules,
setTestModules,
setWorkflowTestData,
onClose
}: Props & {
ChatTestRef: React.RefObject<ChatTestComponentRef>;
testModules?: ModuleItemType[];
setTestModules: React.Dispatch<ModuleItemType[] | undefined>;
setWorkflowTestData: React.Dispatch<
React.SetStateAction<
| {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
}
| undefined
>
>;
}) {
const theme = useTheme();
const { toast } = useToast();
@@ -43,48 +55,37 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
});
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const { updateAppDetail } = useAppStore();
const { nodes, edges, splitToolInputs } = useFlowProviderStore();
const { edges, onUpdateNodeError } = useFlowProviderStore();
const [isSaving, setIsSaving] = useState(false);
const flow2ModulesAndCheck = useCallback(async () => {
const modules = flowNode2Modules({ nodes, edges });
// check required connect
for (let i = 0; i < modules.length; i++) {
const item = modules[i];
const flowData2StoreDataAndCheck = useCallback(async () => {
const { nodes } = await getWorkflowStore();
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
const { isTool } = splitToolInputs(item.inputs, item.moduleId);
if (!checkResults) {
const storeNodes = flowNode2StoreNodes({ nodes, edges });
const unconnected = item.inputs.find((input) => {
if (!input.required || input.connected || (isTool && input.toolDescription)) {
return false;
}
if (input.value === undefined || input.value === '' || input.value?.length === 0) {
return true;
}
return false;
return storeNodes;
} else {
checkResults.forEach((nodeId) => onUpdateNodeError(nodeId, true));
toast({
status: 'warning',
title: t('core.workflow.Check Failed')
});
if (unconnected) {
const msg = t('core.module.Unlink tip', { name: t(item.name) });
toast({
status: 'warning',
title: msg
});
return false;
}
}
return modules;
}, [edges, nodes, splitToolInputs, t, toast]);
}, [edges, onUpdateNodeError, t, toast]);
const onclickSave = useCallback(
async (modules: ModuleItemType[]) => {
async ({ nodes, edges }: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => {
setIsSaving(true);
try {
await updateAppDetail(app._id, {
modules: modules,
modules: nodes,
edges,
type: AppTypeEnum.advanced,
permission: undefined
permission: undefined,
//@ts-ignore
version: 'v2'
});
toast({
status: 'success',
@@ -104,9 +105,9 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const saveAndBack = useCallback(async () => {
try {
const modules = await flow2ModulesAndCheck();
if (modules) {
await onclickSave(modules);
const data = await flowData2StoreDataAndCheck();
if (data) {
await onclickSave(data);
}
onClose();
} catch (error) {
@@ -115,95 +116,129 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
title: getErrText(error)
});
}
}, [flow2ModulesAndCheck, onClose, onclickSave, toast]);
}, [flowData2StoreDataAndCheck, onClose, onclickSave, toast]);
return (
<>
<Flex
py={3}
px={[2, 5, 8]}
borderBottom={theme.borders.base}
alignItems={'center'}
userSelect={'none'}
bg={'myGray.25'}
>
<IconButton
size={'smSquare'}
icon={<MyIcon name={'common/backFill'} w={'14px'} />}
borderRadius={'50%'}
w={'26px'}
h={'26px'}
borderColor={'myGray.300'}
variant={'whiteBase'}
aria-label={''}
isLoading={isSaving}
onClick={openConfirmOut(saveAndBack, onClose)}
/>
<Box ml={[3, 6]} fontSize={['md', '2xl']} flex={1}>
{app.name}
</Box>
const Render = useMemo(() => {
return (
<>
<Flex
py={3}
px={[2, 5, 8]}
borderBottom={theme.borders.base}
alignItems={'center'}
userSelect={'none'}
bg={'myGray.25'}
>
<IconButton
size={'smSquare'}
icon={<MyIcon name={'common/backFill'} w={'14px'} />}
borderRadius={'50%'}
w={'26px'}
h={'26px'}
borderColor={'myGray.300'}
variant={'whiteBase'}
aria-label={''}
isLoading={isSaving}
onClick={openConfirmOut(saveAndBack, onClose)}
/>
<Box ml={[3, 6]} fontSize={['md', '2xl']} flex={1}>
{app.name}
</Box>
<MyMenu
Button={
<IconButton
mr={[3, 5]}
icon={<MyIcon name={'more'} w={'14px'} p={2} />}
aria-label={''}
size={'sm'}
variant={'whitePrimary'}
/>
}
menuList={[
{ label: t('app.Import Configs'), icon: 'common/importLight', onClick: onOpenImport },
{
label: t('app.Export Configs'),
icon: 'export',
onClick: async () => {
const modules = await flow2ModulesAndCheck();
if (modules) {
copyData(filterExportModules(modules), t('app.Export Config Successful'));
<MyMenu
Button={
<IconButton
mr={[3, 5]}
icon={<MyIcon name={'more'} w={'14px'} p={2} />}
aria-label={''}
size={'sm'}
variant={'whitePrimary'}
/>
}
menuList={[
{
label: t('app.Import Configs'),
icon: 'common/importLight',
onClick: onOpenImport
},
{
label: t('app.Export Configs'),
icon: 'export',
onClick: async () => {
const data = await flowData2StoreDataAndCheck();
if (data) {
copyData(
JSON.stringify(
{
nodes: filterSensitiveNodesData(data.nodes),
edges: data.edges
},
null,
2
),
t('app.Export Config Successful')
);
}
}
}
}
]}
/>
]}
/>
{!testModules && (
<Button
mr={[3, 5]}
size={'sm'}
leftIcon={<MyIcon name={'core/chat/chatLight'} w={['14px', '16px']} />}
variant={'whitePrimary'}
onClick={async () => {
const modules = await flow2ModulesAndCheck();
if (modules) {
setTestModules(modules);
const data = await flowData2StoreDataAndCheck();
if (data) {
setWorkflowTestData(data);
}
}}
>
{t('core.Chat test')}
</Button>
)}
<Button
size={'sm'}
isLoading={isSaving}
leftIcon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
onClick={async () => {
const modules = await flow2ModulesAndCheck();
if (modules) {
onclickSave(modules);
}
}}
>
{t('common.Save')}
</Button>
</Flex>
<Button
size={'sm'}
isLoading={isSaving}
leftIcon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
onClick={async () => {
const modules = await flowData2StoreDataAndCheck();
if (modules) {
onclickSave(modules);
}
}}
>
{t('common.Save')}
</Button>
</Flex>
<ConfirmModal
closeText={t('core.app.edit.UnSave')}
confirmText={t('core.app.edit.Save and out')}
/>
</>
);
}, [
ConfirmModal,
app.name,
copyData,
flowData2StoreDataAndCheck,
isSaving,
onClose,
onOpenImport,
onclickSave,
openConfirmOut,
saveAndBack,
setWorkflowTestData,
t,
theme.borders.base
]);
return (
<>
{Render}
{isOpenImport && <ImportSettings onClose={onCloseImport} />}
<ConfirmModal
closeText={t('core.app.edit.UnSave')}
confirmText={t('core.app.edit.Save and out')}
/>
</>
);
});
@@ -212,21 +247,23 @@ const Header = (props: Props) => {
const { app } = props;
const ChatTestRef = useRef<ChatTestComponentRef>(null);
const [testModules, setTestModules] = useState<ModuleItemType[]>();
const [workflowTestData, setWorkflowTestData] = useState<{
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
}>();
return (
<>
<RenderHeaderContainer
{...props}
ChatTestRef={ChatTestRef}
testModules={testModules}
setTestModules={setTestModules}
setWorkflowTestData={setWorkflowTestData}
/>
<ChatTest
ref={ChatTestRef}
modules={testModules}
{...workflowTestData}
app={app}
onClose={() => setTestModules(undefined)}
onClose={() => setWorkflowTestData(undefined)}
/>
</>
);

View File

@@ -1,58 +1,66 @@
import React, { useEffect, useMemo } from 'react';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import Header from './Header';
import Flow from '@/components/core/module/Flow';
import FlowProvider, { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import type { FlowNodeTemplateType } from '@fastgpt/global/core/module/type.d';
import { appSystemModuleTemplates } from '@fastgpt/global/core/module/template/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
import Flow from '@/components/core/workflow/Flow';
import FlowProvider, { useFlowProviderStore } from '@/components/core/workflow/Flow/FlowProvider';
import { appSystemModuleTemplates } from '@fastgpt/global/core/workflow/template/constants';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
type Props = { app: AppSchema; onClose: () => void };
const Render = ({ app, onClose }: Props) => {
const { nodes, initData } = useFlowProviderStore();
const { setBasicNodeTemplates } = useWorkflowStore();
const isV2Workflow = app?.version === 'v2';
const { openConfirm, ConfirmModal } = useConfirm({
showCancel: false,
content:
'检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大会导致许多工作流无法正常排布请重新手动连接工作流。如仍异常可尝试删除对应节点后重新添加。\n\n你可以直接点击测试进行调试无需点击保存点击保存为新版工作流。'
});
const { initData } = useFlowProviderStore();
useEffect(() => {
initData(JSON.parse(JSON.stringify(app.modules)));
}, [app.modules]);
if (!isV2Workflow) return;
initData(
JSON.parse(
JSON.stringify({
nodes: app.modules || [],
edges: app.edges || []
})
)
);
}, [isV2Workflow, app.edges, app.modules]);
useEffect(() => {
const concatTemplates = [...appSystemModuleTemplates];
const copyTemplates: FlowNodeTemplateType[] = JSON.parse(JSON.stringify(concatTemplates));
const filterType: Record<string, 1> = {
[FlowNodeTypeEnum.userGuide]: 1
};
// filter some template, There can only be one
nodes.forEach((node) => {
if (node.type && filterType[node.type]) {
copyTemplates.forEach((module, index) => {
if (module.flowType === node.type) {
copyTemplates.splice(index, 1);
}
});
}
});
setBasicNodeTemplates(copyTemplates);
}, [nodes, setBasicNodeTemplates]);
if (!isV2Workflow) {
openConfirm(() => {
initData(JSON.parse(JSON.stringify(v1Workflow2V2((app.modules || []) as any))));
})();
}
}, [app.modules, isV2Workflow, openConfirm]);
const memoRender = useMemo(() => {
return <Flow Header={<Header app={app} onClose={onClose} />} />;
}, [app, onClose]);
return memoRender;
return (
<>
{memoRender}
{!isV2Workflow && <ConfirmModal countDown={0} />}
</>
);
};
export default React.memo(function FlowEdit(props: Props) {
const filterAppIds = useMemo(() => [props.app._id], [props.app._id]);
return (
<FlowProvider mode={'app'} filterAppIds={filterAppIds}>
<FlowProvider
appId={props.app._id}
mode={'app'}
filterAppIds={filterAppIds}
basicNodeTemplates={appSystemModuleTemplates}
>
<Render {...props} />
</FlowProvider>
);

View File

@@ -122,7 +122,7 @@ const InfoModal = ({
<MyModal
isOpen={true}
onClose={onClose}
iconSrc="/imgs/module/ai.svg"
iconSrc="/imgs/workflow/ai.svg"
title={t('core.app.setting')}
>
<ModalBody>

View File

@@ -25,7 +25,7 @@ import ChatBox from '@/components/ChatBox';
import type { ComponentRef } from '@/components/ChatBox/type.d';
import { useQuery } from '@tanstack/react-query';
import { getInitChatInfo } from '@/web/core/chat/api';
import Tag from '@/components/Tag';
import Tag from '@fastgpt/web/components/common/Tag/index';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { addDays } from 'date-fns';
import MyBox from '@/components/common/MyBox';
@@ -255,6 +255,7 @@ const DetailLogsModal = ({
const { text } = formatChatValue2InputType(history[history.length - 2]?.value);
return text?.slice(0, 8);
}, [history]);
const chatModels = chat?.app?.chatModels;
return (
<>
@@ -292,10 +293,10 @@ const DetailLogsModal = ({
<MyIcon name={'history'} w={'14px'} />
<Box ml={1}>{`${history.length}条记录`}</Box>
</Tag>
{!!chat?.app?.chatModels && (
{!!chatModels && chatModels.length > 0 && (
<Tag ml={2} colorSchema={'green'}>
<MyIcon name={'core/chat/chatModelTag'} w={'14px'} />
<Box ml={1}>{chat.app.chatModels.join(',')}</Box>
<Box ml={1}>{chatModels.join(',')}</Box>
</Tag>
)}
<Box flex={1} />

View File

@@ -42,7 +42,7 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
import dayjs from 'dayjs';
import { getDocPath } from '@/web/common/system/doc';
import dynamic from 'next/dynamic';
import MyMenu from '@/components/MyMenu';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
const SelectUsingWayModal = dynamic(() => import('./SelectUsingWayModal'));

View File

@@ -5,31 +5,45 @@ import { useTranslation } from 'next-i18next';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import ChatBox from '@/components/ChatBox';
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { streamFetch } from '@/web/common/api/fetch';
import MyTooltip from '@/components/MyTooltip';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { getGuideModule } from '@fastgpt/global/core/module/utils';
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import {
getDefaultEntryNodeIds,
initWorkflowEdgeStatus,
storeNodes2RuntimeNodes
} from '@fastgpt/global/core/workflow/runtime/utils';
const ChatTest = ({ appId }: { appId: string }) => {
const { t } = useTranslation();
const { userInfo } = useUserStore();
const { appDetail } = useAppStore();
const ChatBoxRef = useRef<ComponentRef>(null);
const [modules, setModules] = useState<ModuleItemType[]>([]);
const [workflowData, setWorkflowData] = useState<{
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
}>({
nodes: [],
edges: []
});
const startChat = useCallback(
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
let historyMaxLen = 0;
if (!workflowData) return Promise.reject('workflowData is empty');
modules.forEach((module) => {
module.inputs.forEach((input) => {
/* get histories */
let historyMaxLen = 6;
workflowData?.nodes.forEach((node) => {
node.inputs.forEach((input) => {
if (
(input.key === ModuleInputKeyEnum.history ||
input.key === ModuleInputKeyEnum.historyMaxAmount) &&
(input.key === NodeInputKeyEnum.history ||
input.key === NodeInputKeyEnum.historyMaxAmount) &&
typeof input.value === 'number'
) {
historyMaxLen = Math.max(historyMaxLen, input.value);
@@ -44,7 +58,11 @@ const ChatTest = ({ appId }: { appId: string }) => {
data: {
history,
prompt: chatList[chatList.length - 2].value,
modules,
nodes: storeNodes2RuntimeNodes(
workflowData.nodes,
getDefaultEntryNodeIds(workflowData.nodes)
),
edges: initWorkflowEdgeStatus(workflowData.edges),
variables,
appId,
appName: `调试-${appDetail.name}`
@@ -55,7 +73,7 @@ const ChatTest = ({ appId }: { appId: string }) => {
return { responseText, responseData };
},
[modules, appId, appDetail.name]
[workflowData, appId, appDetail.name]
);
const resetChatBox = useCallback(() => {
@@ -65,7 +83,10 @@ const ChatTest = ({ appId }: { appId: string }) => {
useEffect(() => {
resetChatBox();
setModules(appDetail.modules);
setWorkflowData({
nodes: appDetail.modules || [],
edges: appDetail.edges || []
});
}, [appDetail, resetChatBox]);
return (
@@ -103,8 +124,8 @@ const ChatTest = ({ appId }: { appId: string }) => {
appAvatar={appDetail.avatar}
userAvatar={userInfo?.avatar}
showMarkIcon
userGuideModule={getGuideModule(modules)}
showFileSelector={checkChatSupportSelectFileByModules(modules)}
userGuideModule={getGuideModule(workflowData.nodes)}
showFileSelector={checkChatSupportSelectFileByModules(workflowData.nodes)}
onStartChat={startChat}
onDelMessage={() => {}}
/>

View File

@@ -4,9 +4,9 @@ import { useQuery } from '@tanstack/react-query';
import { AddIcon, QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
import { useForm, useFieldArray } from 'react-hook-form';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { appModules2Form, getDefaultAppForm } from '@fastgpt/global/core/app/utils';
import { appWorkflow2Form, getDefaultAppForm } from '@fastgpt/global/core/app/utils';
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import { welcomeTextTip } from '@fastgpt/global/core/module/template/tip';
import { welcomeTextTip } from '@fastgpt/global/core/workflow/template/tip';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useRouter } from 'next/router';
@@ -14,7 +14,7 @@ import { useTranslation } from 'next-i18next';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { postForm2Modules } from '@/web/core/app/utils';
import { form2AppWorkflow } from '@/web/core/app/utils';
import dynamic from 'next/dynamic';
import MyTooltip from '@/components/MyTooltip';
@@ -23,15 +23,16 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import VariableEdit from '@/components/core/app/VariableEdit';
import MyTextarea from '@/components/common/Textarea/MyTextarea/index';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/module/utils';
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
import SettingLLMModel from '@/components/core/ai/SettingLLMModel';
import { SettingAIDataType } from '@fastgpt/global/core/module/node/type';
import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d';
import DeleteIcon, { hoverDeleteStyles } from '@fastgpt/web/components/common/Icon/delete';
import { TTSTypeEnum } from '@/constants/app';
import { getSystemVariables } from '@/web/core/app/utils';
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
const ToolSelectModal = dynamic(() => import('./ToolSelectModal'));
const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect'));
const QGSwitch = dynamic(() => import('@/components/core/app/QGSwitch'));
@@ -60,6 +61,7 @@ const EditForm = ({
const router = useRouter();
const { t } = useTranslation();
const { appDetail, updateAppDetail } = useAppStore();
const { loadAllDatasets, allDatasets } = useDatasetStore();
const { isPc, llmModelList } = useSystemStore();
const [refresh, setRefresh] = useState(false);
@@ -100,7 +102,10 @@ const EditForm = ({
const selectLLMModel = watch('aiSettings.model');
const datasetSearchSetting = watch('dataset');
const variables = watch('userGuide.variables');
const formatVariables = useMemo(() => formatEditorVariablePickerIcon(variables), [variables]);
const formatVariables = useMemo(
() => formatEditorVariablePickerIcon([...getSystemVariables(t), ...variables]),
[t, variables]
);
const searchMode = watch('dataset.searchMode');
const selectDatasets = useMemo(
@@ -115,10 +120,11 @@ const EditForm = ({
/* on save app */
const { mutate: onSubmitSave, isLoading: isSaving } = useRequest({
mutationFn: async (data: AppSimpleEditFormType) => {
const modules = await postForm2Modules(data);
const { nodes, edges } = form2AppWorkflow(data);
await updateAppDetail(appDetail._id, {
modules,
modules: nodes,
edges,
type: AppTypeEnum.simple,
permission: undefined
});
@@ -130,8 +136,8 @@ const EditForm = ({
useQuery(
['init', appDetail],
() => {
const formatVal = appModules2Form({
modules: appDetail.modules
const formatVal = appWorkflow2Form({
nodes: appDetail.modules
});
reset(formatVal);
setRefresh(!refresh);
@@ -475,7 +481,7 @@ const EditForm = ({
onRemoveTool={(e) => {
setValue(
'selectedTools',
selectedTools.filter((item) => item.id !== e.id)
selectedTools.filter((item) => item.pluginId !== e.pluginId)
);
}}
onClose={onCloseToolsSelect}

View File

@@ -59,7 +59,7 @@ const TagsEditModal = ({ onClose }: { onClose: () => void }) => {
style={{ width: '900px' }}
isOpen
onClose={onClose}
iconSrc="/imgs/module/ai.svg"
iconSrc="/imgs/workflow/ai.svg"
title={t('core.app.Team tags')}
>
<ModalBody>

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
@@ -9,25 +9,35 @@ import {
Input,
InputGroup,
InputLeftElement,
ModalBody
ModalBody,
ModalFooter,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Switch,
Textarea
} from '@chakra-ui/react';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useQuery } from '@tanstack/react-query';
import EmptyTip from '@/components/EmptyTip';
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type';
import { useToast } from '@fastgpt/web/hooks/useToast';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
import Avatar from '@/components/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { AddIcon } from '@chakra-ui/icons';
import { getPreviewPluginModule } from '@/web/core/plugin/api';
import MyBox from '@/components/common/MyBox';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
import ParentPaths from '@/components/common/ParentPaths';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
import { debounce } from 'lodash';
import { useForm } from 'react-hook-form';
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
type Props = {
selectedTools: FlowNodeTemplateType[];
@@ -102,6 +112,7 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void })
h={['90vh', '80vh']}
overflow={'none'}
>
{/* Header: row and search */}
<Box px={[3, 6]} pt={4} display={'flex'} justifyContent={'space-between'} w={'full'}>
<RowTabs
list={[
@@ -132,6 +143,7 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void })
/>
</InputGroup>
</Box>
{/* route components */}
{templateType === TemplateTypeEnum.teamPlugin && !searchKey && currentParent && (
<Flex mt={2} px={[3, 6]}>
<ParentPaths
@@ -171,45 +183,52 @@ const RenderList = React.memo(function RenderList({
setCurrentParent: (e: { parentId: string; parentName: string }) => void;
}) {
const { t } = useTranslation();
const { toast } = useToast();
const [configTool, setConfigTool] = useState<FlowNodeTemplateType>();
const onCloseConfigTool = useCallback(() => setConfigTool(undefined), []);
const { register, getValues, setValue, handleSubmit, reset } = useForm<Record<string, any>>({});
const checkToolInputValid = useCallback((tool: FlowNodeTemplateType) => {
for (const input of tool.inputs) {
const renderType = input.renderTypeList?.[input.selectedTypeIndex || 0];
if (renderType === FlowNodeInputTypeEnum.addInputParam) {
return false;
}
}
return true;
}, []);
const filterValidTools = useMemo(
() => templates.filter(checkToolInputValid),
[checkToolInputValid, templates]
);
const { mutate: onClickAdd, isLoading } = useRequest({
mutationFn: async (template: FlowNodeTemplateType) => {
const res = await getPreviewPluginModule(template.id);
// check inputs valid
for (const input of res.inputs) {
if (
[
ModuleInputKeyEnum.switch,
ModuleInputKeyEnum.pluginStart,
ModuleInputKeyEnum.pluginId
].includes(input.key as any)
) {
continue;
}
if (!input.toolDescription) {
return toast({
status: 'warning',
title: t('core.app.ToolCall.This plugin cannot be called as a tool')
});
}
if (!checkToolInputValid(res)) {
return Promise.reject(t('core.app.ToolCall.This plugin cannot be called as a tool'));
}
// All input is tool params
if (res.inputs.every((input) => input.toolDescription)) {
onAddTool(res);
} else {
reset();
setConfigTool(res);
}
return res;
},
onSuccess(res: FlowNodeTemplateType) {
res && onAddTool(res);
},
errorToast: t('core.module.templates.Load plugin error')
});
return templates.length === 0 && !isLoadingData ? (
return filterValidTools.length === 0 && !isLoadingData ? (
<EmptyTip text={t('core.app.ToolCall.No plugin')} />
) : (
<MyBox>
{templates.map((item, i) => {
const selected = !!selectedTools.find((tool) => tool.id === item.id);
{filterValidTools.map((item, i) => {
const selected = selectedTools.some((tool) => tool.pluginId === item.pluginId);
return (
<Flex
key={item.id}
@@ -231,9 +250,11 @@ const RenderList = React.memo(function RenderList({
/>
<Box ml={5} flex={'1 0 0'}>
<Box color={'black'}>{t(item.name)}</Box>
<Box className="textEllipsis3" color={'myGray.500'} fontSize={['xs', 'sm']}>
{t(item.intro)}
</Box>
{item.intro && (
<Box className="textEllipsis3" color={'myGray.500'} fontSize={['xs', 'sm']}>
{t(item.intro)}
</Box>
)}
</Box>
{selected ? (
<Button
@@ -266,6 +287,101 @@ const RenderList = React.memo(function RenderList({
</Flex>
);
})}
{!!configTool && (
<MyModal
isOpen
title={t('core.app.ToolCall.Parameter setting')}
iconSrc="core/app/toolCall"
overflow={'auto'}
>
<ModalBody>
{configTool.inputs
.filter((item) => !item.toolDescription)
.map((input) => {
const required = input.required || false;
return (
<Box key={input.key} _notLast={{ mb: 4 }} px={1}>
<Flex position={'relative'} mb={1} alignItems={'center'}>
{t(input.debugLabel || input.label)}
{input.description && <QuestionTip label={input.description} ml={1} />}
</Flex>
{(() => {
if (input.valueType === WorkflowIOValueTypeEnum.string) {
return (
<Textarea
{...register(input.key, {
required
})}
placeholder={t(input.placeholder || '')}
bg={'myGray.50'}
/>
);
}
if (input.valueType === WorkflowIOValueTypeEnum.number) {
return (
<NumberInput
step={input.step}
min={input.min}
max={input.max}
bg={'myGray.50'}
>
<NumberInputField
{...register(input.key, {
required: input.required,
min: input.min,
max: input.max,
valueAsNumber: true
})}
/>
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
);
}
if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
return <Switch size={'lg'} {...register(input.key, { required })} />;
}
return (
<JsonEditor
bg={'myGray.50'}
placeholder={t(input.placeholder || '')}
resize
value={getValues(input.key)}
onChange={(e) => {
setValue(input.key, e);
}}
/>
);
})()}
</Box>
);
})}
</ModalBody>
<ModalFooter gap={6}>
<Button onClick={onCloseConfigTool} variant={'whiteBase'}>
{t('common.Cancel')}
</Button>
<Button
variant={'primary'}
onClick={handleSubmit((data) => {
onAddTool({
...configTool,
inputs: configTool.inputs.map((input) => ({
...input,
value: data[input.key] ?? input.value
}))
});
onCloseConfigTool();
})}
>
{t('common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
)}
</MyBox>
);
});

View File

@@ -38,18 +38,19 @@ type FormType = {
const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) => {
const { t } = useTranslation();
const [refresh, setRefresh] = useState(false);
const { toast } = useToast();
const router = useRouter();
const theme = useTheme();
const { isPc, feConfigs } = useSystemStore();
const { register, setValue, getValues, handleSubmit } = useForm<FormType>({
const { register, setValue, watch, handleSubmit } = useForm<FormType>({
defaultValues: {
avatar: '/icon/logo.svg',
avatar: '',
name: '',
templateId: appTemplates[0].id
}
});
const avatar = watch('avatar');
const templateId = watch('templateId');
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png',
@@ -68,7 +69,6 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
maxH: 300
});
setValue('avatar', src);
setRefresh((state) => !state);
} catch (err: any) {
toast({
title: getErrText(err, t('common.error.Select avatar failed')),
@@ -86,10 +86,11 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
return Promise.reject(t('core.dataset.error.Template does not exist'));
}
return postCreateApp({
avatar: data.avatar,
avatar: data.avatar || template.avatar,
name: data.name,
type: template.type,
modules: template.modules || []
modules: template.modules || [],
edges: template.edges || []
});
},
onSuccess(id: string) {
@@ -103,7 +104,7 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
return (
<MyModal
iconSrc="/imgs/module/ai.svg"
iconSrc="/imgs/workflow/ai.svg"
title={t('core.app.create app')}
isOpen
onClose={onClose}
@@ -117,7 +118,7 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
<MyTooltip label={t('common.Set Avatar')}>
<Avatar
flexShrink={0}
src={getValues('avatar')}
src={avatar}
w={['28px', '32px']}
h={['28px', '32px']}
cursor={'pointer'}
@@ -153,9 +154,10 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
borderRadius={'md'}
cursor={'pointer'}
boxShadow={'sm'}
{...(getValues('templateId') === item.id
{...(templateId === item.id
? {
bg: 'myWhite.600'
bg: 'primary.50',
borderColor: 'primary.500'
}
: {
_hover: {
@@ -164,7 +166,6 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
})}
onClick={() => {
setValue('templateId', item.id);
setRefresh((state) => !state);
}}
>
<Flex alignItems={'center'}>

View File

@@ -8,7 +8,7 @@ import type { ChatItemType } from '@fastgpt/global/core/chat/type';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import FillTag from '@fastgpt/web/components/common/Tag/Fill';
import FillTag from '@fastgpt/web/components/common/Tag/index';
const ChatHeader = ({
history,

View File

@@ -5,7 +5,7 @@ import { Box, IconButton } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router';
import MyMenu from '@/components/MyMenu';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
const { t } = useTranslation();

View File

@@ -34,10 +34,10 @@ import dayjs from 'dayjs';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyMenu from '@/components/MyMenu';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d';
import EmptyTip from '@/components/EmptyTip';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import {
DatasetCollectionTypeEnum,
TrainingModeEnum,

View File

@@ -25,7 +25,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { Prompt_AgentQA } from '@fastgpt/global/core/ai/prompt/agent';
import Preview from '../components/Preview';
import Tag from '@/components/Tag';
import Tag from '@fastgpt/web/components/common/Tag/index';
function DataProcess({
showPreviewChunks = true,

View File

@@ -26,8 +26,7 @@ import {
postCreateDatasetLinkCollection,
postCreateDatasetTextCollection
} from '@/web/core/dataset/api';
import { getErrText } from '@fastgpt/global/common/error/utils';
import Tag from '@/components/Tag';
import Tag from '@fastgpt/web/components/common/Tag/index';
const Upload = () => {
const { t } = useTranslation();

View File

@@ -4,7 +4,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import { useImportStore } from '../Provider';
import MyMenu from '@/components/MyMenu';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { ImportSourceItemType } from '@/web/core/dataset/type';
import dynamic from 'next/dynamic';
const PreviewRawText = dynamic(() => import('./PreviewRawText'));

View File

@@ -15,7 +15,6 @@ import MyTooltip from '@/components/MyTooltip';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { countPromptTokens } from '@fastgpt/global/common/string/tiktoken';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { getDefaultIndex } from '@fastgpt/global/core/dataset/utils';
import { DatasetDataIndexItemType } from '@fastgpt/global/core/dataset/type';
@@ -145,7 +144,9 @@ const InputDataModal = ({
setCurrentTab(TabEnum.content);
return Promise.reject(t('dataset.data.input is empty'));
}
if (countPromptTokens(e.q) >= maxToken) {
const totalLength = e.q.length + (e.a?.length || 0);
if (totalLength >= maxToken * 1.4) {
return Promise.reject(t('core.dataset.data.Too Long'));
}

View File

@@ -28,7 +28,7 @@ import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
type FormType = {
inputText: string;

View File

@@ -85,7 +85,7 @@ const CreateModal = ({ onClose, parentId }: { onClose: () => void; parentId?: st
return (
<MyModal
iconSrc="/imgs/module/db.png"
iconSrc="/imgs/workflow/db.png"
title={t('core.dataset.Create dataset')}
isOpen
onClose={onClose}

View File

@@ -20,7 +20,7 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
import dynamic from 'next/dynamic';
import { DatasetTypeEnum, DatasetTypeMap } from '@fastgpt/global/core/dataset/constants';
import { FolderImgUrl, FolderIcon } from '@fastgpt/global/common/file/image/constants';
import MyMenu from '@/components/MyMenu';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
@@ -149,7 +149,7 @@ const Kb = () => {
}))}
FirstPathDom={
<Flex flex={1} alignItems={'center'}>
<Image src={'/imgs/module/db.png'} alt={''} mr={2} h={'24px'} />
<Image src={'/imgs/workflow/db.png'} alt={''} mr={2} h={'24px'} />
<Box className="textlg" letterSpacing={1} fontSize={'24px'} fontWeight={'bold'}>
{t('core.dataset.My Dataset')}
</Box>
@@ -189,7 +189,7 @@ const Kb = () => {
{
label: (
<Flex>
<Image src={'/imgs/module/db.png'} alt={''} w={'20px'} mr={1} />
<Image src={'/imgs/workflow/db.png'} alt={''} w={'20px'} mr={1} />
{t('core.dataset.Dataset')}
</Flex>
),

View File

@@ -1,4 +1,3 @@
import Divider from '@/components/core/module/Flow/components/modules/Divider';
import { LoginPageTypeEnum } from '@/constants/user';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { AbsoluteCenter, Box, Button, Flex, Image } from '@chakra-ui/react';
@@ -9,6 +8,7 @@ import { customAlphabet } from 'nanoid';
import { useRouter } from 'next/router';
import { Dispatch, useRef } from 'react';
import { useTranslation } from 'next-i18next';
import Divider from '@/components/core/workflow/Flow/components/Divider';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
interface Props {

View File

@@ -7,17 +7,22 @@ import { useCopyData } from '@/web/common/hooks/useCopyData';
import dynamic from 'next/dynamic';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTooltip from '@/components/MyTooltip';
import { getFlowStore } from '@/components/core/module/Flow/FlowProvider';
import { filterExportModules, flowNode2Modules } from '@/components/core/module/utils';
import { filterExportModules, flowNode2StoreNodes } from '@/components/core/workflow/utils';
import { putUpdatePlugin } from '@/web/core/plugin/api';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import { ModuleItemType } from '@fastgpt/global/core/module/type';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import MyMenu from '@/components/MyMenu';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import {
getWorkflowStore,
useFlowProviderStore
} from '@/components/core/workflow/Flow/FlowProvider';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import {
checkWorkflowNodeAndConnection,
filterSensitiveNodesData
} from '@/web/core/workflow/utils';
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
const PreviewPlugin = dynamic(() => import('./Preview'));
const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
type Props = { plugin: PluginItemSchema; onClose: () => void };
@@ -26,92 +31,31 @@ const Header = ({ plugin, onClose }: Props) => {
const { t } = useTranslation();
const { toast } = useToast();
const { copyData } = useCopyData();
const { edges, onUpdateNodeError } = useFlowProviderStore();
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
const [previewModules, setPreviewModules] = React.useState<ModuleItemType[]>();
const flow2ModulesAndCheck = useCallback(async () => {
const { nodes, edges } = await getFlowStore();
const flowData2StoreDataAndCheck = useCallback(async () => {
const { nodes } = await getWorkflowStore();
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
if (!checkResults) {
const storeNodes = flowNode2StoreNodes({ nodes, edges });
const modules = flowNode2Modules({ nodes, edges });
// check required connect
for (let i = 0; i < modules.length; i++) {
const item = modules[i];
// update custom input connected
if (item.flowType === FlowNodeTypeEnum.pluginInput) {
item.inputs.forEach((item) => {
item.connected = true;
});
if (
item.outputs.find(
(output) =>
output.key !== ModuleOutputKeyEnum.pluginStart && output.targets.length === 0
)
) {
toast({
status: 'warning',
title: t('module.Plugin input must connect')
});
return false;
}
}
if (
item.flowType === FlowNodeTypeEnum.pluginOutput &&
item.inputs.find((input) => !input.connected)
) {
toast({
status: 'warning',
title: t('core.module.Plugin output must connect')
});
return false;
}
if (
item.inputs.find((input) => {
if (!input.required || input.connected) return false;
if (input.value === undefined || input.value === '' || input.value?.length === 0) {
return true;
}
return false;
})
) {
toast({
status: 'warning',
title: `${item.name}】存在未填或未连接参数`
});
return false;
}
}
// plugin must have input
const pluginInputModule = modules.find(
(item) => item.flowType === FlowNodeTypeEnum.pluginInput
);
if (!pluginInputModule) {
return storeNodes;
} else {
checkResults.forEach((nodeId) => onUpdateNodeError(nodeId, true));
toast({
status: 'warning',
title: t('module.Plugin input is required')
title: t('core.workflow.Check Failed')
});
return false;
}
if (pluginInputModule.inputs.length < 1) {
toast({
status: 'warning',
title: t('module.Plugin input is not value')
});
return false;
}
return modules;
}, [t, toast]);
}, [edges, onUpdateNodeError, t, toast]);
const { mutate: onclickSave, isLoading } = useRequest({
mutationFn: (modules: ModuleItemType[]) => {
mutationFn: ({ nodes, edges }: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => {
return putUpdatePlugin({
id: plugin._id,
modules
modules: nodes,
edges
});
},
successToast: '保存配置成功',
@@ -158,15 +102,25 @@ const Header = ({ plugin, onClose }: Props) => {
label: t('app.Export Configs'),
icon: 'export',
onClick: async () => {
const modules = await flow2ModulesAndCheck();
if (modules) {
copyData(filterExportModules(modules), t('app.Export Config Successful'));
const data = await flowData2StoreDataAndCheck();
if (data) {
copyData(
JSON.stringify(
{
nodes: filterSensitiveNodesData(data.nodes),
edges: data.edges
},
null,
2
),
t('app.Export Config Successful')
);
}
}
}
]}
/>
<MyTooltip label={t('module.Preview Plugin')}>
{/* <MyTooltip label={t('module.Preview Plugin')}>
<IconButton
mr={[3, 5]}
icon={<MyIcon name={'core/modules/previewLight'} w={['14px', '16px']} />}
@@ -174,19 +128,19 @@ const Header = ({ plugin, onClose }: Props) => {
aria-label={'save'}
variant={'whitePrimary'}
onClick={async () => {
const modules = await flow2ModulesAndCheck();
const modules = await flowData2StoreDataAndCheck();
if (modules) {
setPreviewModules(modules);
}
}}
/>
</MyTooltip>
</MyTooltip> */}
<Button
size={'sm'}
isLoading={isLoading}
leftIcon={<MyIcon name={'common/saveFill'} w={['14px', '16px']} />}
onClick={async () => {
const modules = await flow2ModulesAndCheck();
const modules = await flowData2StoreDataAndCheck();
if (modules) {
onclickSave(modules);
}
@@ -196,13 +150,6 @@ const Header = ({ plugin, onClose }: Props) => {
</Button>
</Flex>
{isOpenImport && <ImportSettings onClose={onCloseImport} />}
{!!previewModules && (
<PreviewPlugin
plugin={plugin}
modules={previewModules}
onClose={() => setPreviewModules(undefined)}
/>
)}
</>
);
};

View File

@@ -1,73 +0,0 @@
import React, { useEffect } from 'react';
import ReactFlow, { Background, ReactFlowProvider, useNodesState } from 'reactflow';
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import dynamic from 'next/dynamic';
import { plugin2ModuleIO } from '@fastgpt/global/core/module/utils';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { PluginItemSchema } from '@fastgpt/global/core/plugin/type';
import { appModule2FlowNode } from '@/utils/adapt';
const nodeTypes = {
[FlowNodeTypeEnum.pluginModule]: dynamic(
() => import('@/components/core/module/Flow/components/nodes/NodeSimple')
)
};
const PreviewPlugin = ({
plugin,
modules,
onClose
}: {
plugin: PluginItemSchema;
modules: ModuleItemType[];
onClose: () => void;
}) => {
const { t } = useTranslation();
const [nodes = [], setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
useEffect(() => {
setNodes([
appModule2FlowNode({
item: {
moduleId: 'plugin',
flowType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,
...plugin2ModuleIO(plugin._id, modules)
}
})
]);
}, [modules, plugin, setNodes]);
return (
<MyModal
isOpen
title={t('module.Preview Plugin')}
iconSrc="/imgs/modal/preview.svg"
onClose={onClose}
isCentered
>
<Box h={'400px'} w={'400px'}>
<ReactFlowProvider>
<ReactFlow
fitView
nodes={nodes}
edges={[]}
minZoom={0.1}
maxZoom={1.5}
nodeTypes={nodeTypes}
onNodesChange={onNodesChange}
>
<Background />
</ReactFlow>
</ReactFlowProvider>
</Box>
</MyModal>
);
};
export default React.memo(PreviewPlugin);

View File

@@ -1,11 +1,9 @@
import React, { useEffect, useMemo } from 'react';
import React, { useEffect } from 'react';
import { useRouter } from 'next/router';
import Header from './Header';
import Flow from '@/components/core/module/Flow';
import FlowProvider, { useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
import { FlowNodeTemplateType } from '@fastgpt/global/core/module/type.d';
import { pluginSystemModuleTemplates } from '@fastgpt/global/core/module/template/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
import Flow from '@/components/core/workflow/Flow';
import FlowProvider, { useFlowProviderStore } from '@/components/core/workflow/Flow/FlowProvider';
import { pluginSystemModuleTemplates } from '@fastgpt/global/core/workflow/template/constants';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useQuery } from '@tanstack/react-query';
import { getOnePlugin } from '@/web/core/plugin/api';
@@ -13,7 +11,8 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import Loading from '@fastgpt/web/components/common/MyLoading';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'next-i18next';
import { useWorkflowStore } from '@/web/core/workflow/store/workflow';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
type Props = { pluginId: string };
@@ -21,8 +20,7 @@ const Render = ({ pluginId }: Props) => {
const { t } = useTranslation();
const router = useRouter();
const { toast } = useToast();
const { nodes, initData } = useFlowProviderStore();
const { setBasicNodeTemplates } = useWorkflowStore();
const { initData } = useFlowProviderStore();
const { data: pluginDetail } = useQuery(
['getOnePlugin', pluginId],
@@ -37,43 +35,39 @@ const Render = ({ pluginId }: Props) => {
}
}
);
const isV2Workflow = pluginDetail?.version === 'v2';
const { openConfirm, ConfirmModal } = useConfirm({
showCancel: false,
content:
'检测到您的高级编排为旧版,系统将为您自动格式化成新版工作流。\n\n由于版本差异较大会导致许多工作流无法正常排布请重新手动连接工作流。如仍异常可尝试删除对应节点后重新添加。\n\n你可以直接点击测试进行调试无需点击保存点击保存为新版工作流。'
});
useEffect(() => {
initData(JSON.parse(JSON.stringify(pluginDetail?.modules || [])));
}, [pluginDetail?.modules]);
if (isV2Workflow) {
initData(
JSON.parse(
JSON.stringify({
nodes: pluginDetail?.modules || [],
edges: pluginDetail?.edges || []
})
)
);
}
}, [isV2Workflow, pluginDetail?.edges, pluginDetail?.modules]);
useEffect(() => {
const concatTemplates = [...pluginSystemModuleTemplates];
const copyTemplates: FlowNodeTemplateType[] = JSON.parse(JSON.stringify(concatTemplates));
const filterType: Record<string, 1> = {
[FlowNodeTypeEnum.userGuide]: 1,
[FlowNodeTypeEnum.pluginInput]: 1,
[FlowNodeTypeEnum.pluginOutput]: 1
};
// filter some template
nodes.forEach((node) => {
if (node.type && filterType[node.type]) {
copyTemplates.forEach((module, index) => {
if (module.flowType === node.type) {
copyTemplates.splice(index, 1);
}
});
}
});
// filter hideInPlugin inputs
copyTemplates.forEach((template) => {
template.inputs = template.inputs.filter((input) => !input.hideInPlugin);
});
setBasicNodeTemplates(copyTemplates);
}, [nodes, setBasicNodeTemplates]);
if (!isV2Workflow && pluginDetail) {
openConfirm(() => {
initData(JSON.parse(JSON.stringify(v1Workflow2V2((pluginDetail.modules || []) as any))));
})();
}
}, [isV2Workflow, openConfirm, pluginDetail]);
return pluginDetail ? (
<Flow Header={<Header plugin={pluginDetail} onClose={() => router.back()} />} />
<>
<Flow Header={<Header plugin={pluginDetail} onClose={() => router.back()} />} />
{!isV2Workflow && <ConfirmModal countDown={0} />}
</>
) : (
<Loading />
);
@@ -81,7 +75,7 @@ const Render = ({ pluginId }: Props) => {
export default function FlowEdit(props: any) {
return (
<FlowProvider mode={'plugin'}>
<FlowProvider mode={'plugin'} basicNodeTemplates={pluginSystemModuleTemplates}>
<Render {...props} />
</FlowProvider>
);

View File

@@ -30,10 +30,10 @@ export const defaultForm: EditFormType = {
type: PluginTypeEnum.custom,
modules: [
{
moduleId: nanoid(),
nodeId: nanoid(),
name: '定义插件输入',
avatar: '/imgs/module/input.png',
flowType: 'pluginInput',
avatar: '/imgs/workflow/input.png',
flowNodeType: 'pluginInput',
showStatus: false,
position: {
x: 616.4226348688949,
@@ -43,10 +43,10 @@ export const defaultForm: EditFormType = {
outputs: []
},
{
moduleId: nanoid(),
nodeId: nanoid(),
name: '定义插件输出',
avatar: '/imgs/module/output.png',
flowType: 'pluginOutput',
avatar: '/imgs/workflow/output.png',
flowNodeType: 'pluginOutput',
showStatus: false,
position: {
x: 1607.7142331269126,

View File

@@ -40,7 +40,7 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
import { EditFormType } from './type';
import { FolderImgUrl } from '@fastgpt/global/common/file/image/constants';
import HttpInput from '@fastgpt/web/components/common/Input/HttpInput';
import { HttpHeaders } from '@/components/core/module/Flow/components/nodes/NodeHttp';
import { HttpHeaders } from '@/components/core/workflow/Flow/nodes/NodeHttp';
import { OpenApiJsonSchema } from '@fastgpt/global/core/plugin/httpPlugin/type';
export const defaultHttpPlugin: CreateOnePluginParams = {
@@ -150,7 +150,7 @@ const HttpPluginEditModal = ({
[setValue, t, toast]
);
const { mutate: onclickDelPlugin, isLoading: isDeleting } = useRequest({
const { mutate: onClickDelPlugin, isLoading: isDeleting } = useRequest({
mutationFn: async () => {
if (!defaultPlugin.id) return;
@@ -209,7 +209,7 @@ const HttpPluginEditModal = ({
<MyModal
isOpen
onClose={onClose}
iconSrc="/imgs/module/http.png"
iconSrc="/imgs/workflow/http.png"
title={isEdit ? t('plugin.Edit Http Plugin') : t('plugin.Import Plugin')}
w={['90vw', '600px']}
h={['90vh', '80vh']}
@@ -512,7 +512,7 @@ const HttpPluginEditModal = ({
isLoading={isDeleting}
onClick={(e) => {
e.stopPropagation();
openConfirm(onclickDelPlugin)();
openConfirm(onClickDelPlugin)();
}}
/>
)}

View File

@@ -11,13 +11,16 @@ import PageContainer from '@/components/PageContainer';
import Avatar from '@/components/Avatar';
import EditModal, { defaultForm } from './component/EditModal';
import { getPluginPaths, getUserPlugins } from '@/web/core/plugin/api';
import EmptyTip from '@/components/EmptyTip';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { useUserStore } from '@/web/support/user/useUserStore';
import MyMenu from '@/components/MyMenu';
import HttpPluginEditModal, { defaultHttpPlugin } from './component/HttpPluginEditModal';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { defaultHttpPlugin } from './component/HttpPluginEditModal';
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
import ParentPaths from '@/components/common/ParentPaths';
import { EditFormType } from './component/type';
import dynamic from 'next/dynamic';
const HttpPluginEditModal = dynamic(() => import('./component/HttpPluginEditModal'));
const TeamPlugins = () => {
const { t } = useTranslation();
@@ -58,7 +61,7 @@ const TeamPlugins = () => {
}))}
FirstPathDom={
<Flex flex={1} alignItems={'center'}>
<Image src={'/imgs/module/plugin.svg'} alt={''} mr={2} h={'24px'} />
<Image src={'/imgs/workflow/plugin.svg'} alt={''} mr={2} h={'24px'} />
<Box className="textlg" letterSpacing={1} fontSize={'24px'} fontWeight={'bold'}>
{t('plugin.My Plugins')}({t('common.Beta')})
</Box>
@@ -88,7 +91,7 @@ const TeamPlugins = () => {
{
label: (
<Flex>
<Image src={'/imgs/module/plugin.svg'} alt={''} w={'18px'} mr={1} />
<Image src={'/imgs/workflow/plugin.svg'} alt={''} w={'18px'} mr={1} />
{t('plugin.Custom Plugin')}
</Flex>
),
@@ -97,7 +100,7 @@ const TeamPlugins = () => {
{
label: (
<Flex display={'flex'} alignItems={'center'}>
<Image src={'/imgs/module/http.png'} alt={''} w={'18px'} h={'14px'} mr={1} />
<Image src={'/imgs/workflow/http.png'} alt={''} w={'18px'} h={'14px'} mr={1} />
{t('plugin.HTTP Plugin')}
</Flex>
),