mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-29 09:44:47 +00:00
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:
@@ -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')}: </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>
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
};
|
@@ -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();
|
||||
}
|
||||
};
|
@@ -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, {
|
||||
|
@@ -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, {
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -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';
|
||||
|
@@ -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';
|
||||
|
@@ -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 {
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
|
@@ -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(
|
||||
|
@@ -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
|
||||
|
@@ -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, {
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
97
projects/app/src/pages/api/core/workflow/debug.ts
Normal file
97
projects/app/src/pages/api/core/workflow/debug.ts
Normal 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'
|
||||
}
|
||||
};
|
@@ -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);
|
||||
});
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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));
|
||||
|
@@ -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 });
|
||||
|
||||
|
@@ -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({
|
||||
|
@@ -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,
|
||||
{
|
||||
|
@@ -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)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@@ -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>
|
||||
);
|
||||
|
@@ -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>
|
||||
|
@@ -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} />
|
||||
|
@@ -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'));
|
||||
|
@@ -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={() => {}}
|
||||
/>
|
||||
|
@@ -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}
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
);
|
||||
});
|
||||
|
@@ -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'}>
|
||||
|
@@ -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,
|
||||
|
@@ -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();
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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();
|
||||
|
@@ -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'));
|
||||
|
@@ -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'));
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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}
|
||||
|
@@ -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>
|
||||
),
|
||||
|
@@ -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 {
|
||||
|
@@ -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)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -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);
|
@@ -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>
|
||||
);
|
||||
|
@@ -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,
|
||||
|
@@ -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)();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@@ -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>
|
||||
),
|
||||
|
Reference in New Issue
Block a user