Perf input guide (#1557)

* perf: input guide code

* perf: input guide ui

* Chat input guide api

* Update app chat config store

* perf: app chat config field

* perf: app context

* perf: params

* fix: ts

* perf: filter private config

* perf: filter private config

* perf: import workflow

* perf: limit max tip amount
This commit is contained in:
Archer
2024-05-21 17:52:04 +08:00
committed by GitHub
parent 8e8ceb7439
commit fb368a581c
123 changed files with 2124 additions and 1805 deletions

View File

@@ -6,7 +6,7 @@ import { MongoApp } from '@fastgpt/service/core/app/schema';
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
import { NextAPI } from '@/service/middleware/entry';
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {

View File

@@ -5,9 +5,9 @@ import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
import { MongoAppQGuide } from '@fastgpt/service/core/app/qGuideSchema';
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
import { NextAPI } from '@/service/middleware/entry';
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { appId } = req.query as { appId: string };
@@ -47,7 +47,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
},
{ session }
);
await MongoAppQGuide.deleteMany(
await MongoChatInputGuide.deleteMany(
{
appId
},

View File

@@ -1,6 +1,4 @@
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 { NextAPI } from '@/service/middleware/entry';

View File

@@ -1,20 +1,20 @@
import { authUserNotVisitor } from '@fastgpt/service/support/permission/auth/user';
import { NextApiRequest, NextApiResponse } from 'next';
import { MongoAppQGuide } from '@fastgpt/service/core/app/qGuideSchema';
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
import axios from 'axios';
import { NextAPI } from '@/service/middleware/entry';
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { textList = [], appId, customURL } = req.body;
const { textList = [], appId, customUrl } = req.body;
if (!customURL) {
if (!customUrl) {
const { teamId } = await authUserNotVisitor({ req, authToken: true });
const currentQGuide = await MongoAppQGuide.find({ appId, teamId });
const currentQGuide = await MongoChatInputGuide.find({ appId, teamId });
const currentTexts = currentQGuide.map((item) => item.text);
const textsToDelete = currentTexts.filter((text) => !textList.includes(text));
await MongoAppQGuide.deleteMany({ text: { $in: textsToDelete }, appId, teamId });
await MongoChatInputGuide.deleteMany({ text: { $in: textsToDelete }, appId, teamId });
const newTexts = textList.filter((text: string) => !currentTexts.includes(text));
@@ -24,10 +24,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
teamId: teamId
}));
await MongoAppQGuide.insertMany(newDocuments);
await MongoChatInputGuide.insertMany(newDocuments);
} else {
try {
const response = await axios.post(customURL, {
const response = await axios.post(customUrl, {
textList,
appId
});

View File

@@ -1,48 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { MongoAppQGuide } from '@fastgpt/service/core/app/qGuideSchema';
import axios from 'axios';
import { PaginationProps } from '@fastgpt/web/common/fetch/type';
import { NextAPI } from '@/service/middleware/entry';
type Props = PaginationProps<{
appId: string;
customURL: string;
searchKey: string;
}>;
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { appId, customURL, current, pageSize, searchKey } = req.query as unknown as Props;
if (!customURL) {
const [result, total] = await Promise.all([
MongoAppQGuide.find({
appId,
...(searchKey && { text: { $regex: new RegExp(searchKey, 'i') } })
})
.sort({
time: -1
})
.skip((current - 1) * pageSize)
.limit(pageSize),
MongoAppQGuide.countDocuments({ appId })
]);
return {
list: result.map((item) => item.text) || [],
total
};
} else {
try {
const response = await axios.get(customURL as string, {
params: {
appid: appId
}
});
res.status(200).json(response.data);
} catch (error) {
res.status(500).json({ error });
}
}
}
export default NextAPI(handler);

View File

@@ -7,7 +7,7 @@ import { NextAPI } from '@/service/middleware/entry';
/* 获取我的模型 */
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const { name, avatar, type, intro, nodes, edges, permission, teamTags } =
const { name, avatar, type, intro, nodes, edges, chatConfig, permission, teamTags } =
req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
@@ -39,7 +39,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
}),
...(edges && {
edges
})
}),
...(chatConfig && { chatConfig })
}
);
}

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { NextAPI } from '@/service/middleware/entry';
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { AppVersionSchemaType } from '@fastgpt/global/core/app/version';

View File

@@ -1,11 +1,10 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { NextAPI } from '@/service/middleware/entry';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
import { PostPublishAppProps } from '@/global/core/app/api';
@@ -13,14 +12,12 @@ type Response = {};
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<{}> {
const { appId } = req.query as { appId: string };
const { nodes = [], edges = [], type } = req.body as PostPublishAppProps;
const { nodes = [], edges = [], chatConfig, type } = req.body as PostPublishAppProps;
await authApp({ appId, req, per: 'w', authToken: true });
const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes });
const { scheduledTriggerConfig } = splitGuideModule(getGuideModule(formatNodes || []));
await mongoSessionRun(async (session) => {
// create version histories
await MongoAppVersion.create(
@@ -28,7 +25,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
{
appId,
nodes: formatNodes,
edges
edges,
chatConfig
}
],
{ session }
@@ -38,12 +36,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
await MongoApp.findByIdAndUpdate(appId, {
modules: formatNodes,
edges,
chatConfig,
updateTime: new Date(),
version: 'v2',
type,
scheduledTriggerConfig,
scheduledTriggerNextTime: scheduledTriggerConfig
? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig)
scheduledTriggerConfig: chatConfig?.scheduledTriggerConfig,
scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig
? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig)
: null
});
});

View File

@@ -1,11 +1,10 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { NextAPI } from '@/service/middleware/entry';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoAppVersion } from '@fastgpt/service/core/app/versionSchema';
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
import { PostRevertAppProps } from '@/global/core/app/api';
@@ -28,7 +27,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
const { nodes: formatEditNodes } = beforeUpdateAppFormat({ nodes: editNodes });
const { scheduledTriggerConfig } = splitGuideModule(getGuideModule(version.nodes));
const scheduledTriggerConfig = version.chatConfig.scheduledTriggerConfig;
await mongoSessionRun(async (session) => {
// 为编辑中的数据创建一个版本

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { getGuideModule, replaceAppChatConfig } from '@fastgpt/global/core/workflow/utils';
import { getGuideModule, getAppChatConfig } 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';
@@ -61,10 +61,12 @@ async function handler(
variables: chat?.variables || {},
history,
app: {
userGuideModule: replaceAppChatConfig({
node: getGuideModule(nodes),
variableList: chat?.variableList,
welcomeText: chat?.welcomeText
chatConfig: getAppChatConfig({
chatConfig: app.chatConfig,
systemConfigNode: getGuideModule(nodes),
storeVariables: chat?.variableList,
storeWelcomeText: chat?.welcomeText,
isPublicFetch: false
}),
chatModels: getChatModelNameListByModules(nodes),
name: app.name,

View File

@@ -0,0 +1,30 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
export type countChatInputGuideTotalQuery = { appId: string };
export type countChatInputGuideTotalBody = {};
export type countChatInputGuideTotalResponse = { total: number };
async function handler(
req: ApiRequestProps<countChatInputGuideTotalBody, countChatInputGuideTotalQuery>,
res: ApiResponseType<any>
): Promise<countChatInputGuideTotalResponse> {
await authCert({ req, authToken: true });
const appId = req.query.appId;
if (!appId) {
return {
total: 0
};
}
return {
total: await MongoChatInputGuide.countDocuments({ appId })
};
}
export default NextAPI(handler);

View File

@@ -0,0 +1,45 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
export type createChatInputGuideQuery = {};
export type createInputGuideBody = {
appId: string;
textList: string[];
};
export type createInputGuideResponse = {
insertLength: number;
};
async function handler(
req: ApiRequestProps<createInputGuideBody, createChatInputGuideQuery>,
res: ApiResponseType<any>
): Promise<createInputGuideResponse> {
const { appId, textList } = req.body;
await authApp({ req, appId, authToken: true, per: 'r' });
try {
const result = await MongoChatInputGuide.insertMany(
textList.map((text) => ({
appId,
text
})),
{
ordered: false
}
);
return {
insertLength: result.length
};
} catch (error: any) {
const errLength = error.writeErrors?.length ?? textList.length;
return {
insertLength: textList.length - errLength
};
}
}
export default NextAPI(handler);

View File

@@ -0,0 +1,27 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
export type deleteChatInputGuideQuery = {};
export type deleteInputGuideBody = { appId: string; dataIdList: string[] };
export type deleteInputGuideResponse = {};
async function handler(
req: ApiRequestProps<deleteInputGuideBody, deleteChatInputGuideQuery>,
res: ApiResponseType<any>
): Promise<deleteInputGuideResponse> {
const { appId, dataIdList } = req.body;
await authApp({ req, appId, authToken: true, per: 'r' });
console.log(dataIdList);
await MongoChatInputGuide.deleteMany({
_id: { $in: dataIdList },
appId
});
return {};
}
export default NextAPI(handler);

View File

@@ -0,0 +1,42 @@
import type { NextApiResponse } from 'next';
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { ChatInputGuideSchemaType } from '@fastgpt/global/core/chat/inputGuide/type';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
export type ChatInputGuideProps = PaginationProps<{
appId: string;
searchKey: string;
}>;
export type ChatInputGuideResponse = PaginationResponse<ChatInputGuideSchemaType>;
async function handler(
req: ApiRequestProps<{}, ChatInputGuideProps>,
res: NextApiResponse<any>
): Promise<ChatInputGuideResponse> {
const { appId, pageSize, current, searchKey } = req.query;
await authApp({ req, appId, authToken: true, per: 'r' });
const params = {
appId,
...(searchKey && { text: { $regex: new RegExp(searchKey, 'i') } })
};
const [result, total] = await Promise.all([
MongoChatInputGuide.find(params)
.sort({ _id: -1 })
.skip(pageSize * (current - 1))
.limit(pageSize),
MongoChatInputGuide.countDocuments(params)
]);
return {
list: result,
total
};
}
export default NextAPI(handler);

View File

@@ -0,0 +1,34 @@
import type { NextApiResponse } from 'next';
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
export type QueryChatInputGuideProps = {
appId: string;
searchKey: string;
};
export type QueryChatInputGuideResponse = string[];
async function handler(
req: ApiRequestProps<{}, QueryChatInputGuideProps>,
res: NextApiResponse<any>
): Promise<QueryChatInputGuideResponse> {
const { appId, searchKey } = req.query;
await authApp({ req, appId, authToken: true, authApiKey: true, per: 'r' });
const params = {
appId,
...(searchKey && { text: { $regex: new RegExp(searchKey, 'i') } })
};
const result = await MongoChatInputGuide.find(params).sort({ _id: -1 }).limit(6);
return result
.map((item) => item.text)
.filter(Boolean)
.filter((item) => item !== searchKey);
}
export default NextAPI(handler);

View File

@@ -0,0 +1,36 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { authApp } from '@fastgpt/service/support/permission/auth/app';
import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schema';
export type updateChatInputGuideQuery = {};
export type updateInputGuideBody = {
appId: string;
dataId: string;
text: string;
};
export type updateInputGuideResponse = {};
async function handler(
req: ApiRequestProps<updateInputGuideBody, updateChatInputGuideQuery>,
res: ApiResponseType<any>
): Promise<updateInputGuideResponse> {
const { appId, dataId, text } = req.body;
await authApp({ req, appId, authToken: true, per: 'r' });
await MongoChatInputGuide.findOneAndUpdate(
{
_id: dataId,
appId
},
{
text
}
);
return {};
}
export default NextAPI(handler);

View File

@@ -2,7 +2,7 @@ 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, replaceAppChatConfig } from '@fastgpt/global/core/workflow/utils';
import { getGuideModule, getAppChatConfig } 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';
@@ -72,10 +72,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
variables: chat?.variables || {},
history,
app: {
userGuideModule: replaceAppChatConfig({
node: getGuideModule(nodes),
variableList: chat?.variableList,
welcomeText: chat?.welcomeText
chatConfig: getAppChatConfig({
chatConfig: app.chatConfig,
systemConfigNode: getGuideModule(nodes),
storeVariables: chat?.variableList,
storeWelcomeText: chat?.welcomeText,
isPublicFetch: false
}),
chatModels: getChatModelNameListByModules(nodes),
name: app.name,

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { getGuideModule, replaceAppChatConfig } from '@fastgpt/global/core/workflow/utils';
import { getGuideModule, getAppChatConfig } 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';
@@ -73,10 +73,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
variables: chat?.variables || {},
history,
app: {
userGuideModule: replaceAppChatConfig({
node: getGuideModule(nodes),
variableList: chat?.variableList,
welcomeText: chat?.welcomeText
chatConfig: getAppChatConfig({
chatConfig: app.chatConfig,
systemConfigNode: getGuideModule(nodes),
storeVariables: chat?.variableList,
storeWelcomeText: chat?.welcomeText,
isPublicFetch: false
}),
chatModels: getChatModelNameListByModules(nodes),
name: app.name,

View File

@@ -1,82 +0,0 @@
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/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/workflow/runtime/constants';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
let { appId, chatId, loadCustomFeedbacks } = req.query as InitChatProps;
if (!appId) {
return jsonRes(res, {
code: 501,
message: "You don't have an app yet"
});
}
// auth app permission
const [{ app, tmbId }, chat] = await Promise.all([
authApp({
req,
authToken: true,
appId,
per: 'r'
}),
chatId ? MongoChat.findOne({ appId, chatId }) : undefined
]);
// // auth chat permission
// if (chat && !app.canWrite && String(tmbId) !== String(chat?.tmbId)) {
// throw new Error(ChatErrEnum.unAuthChat);
// }
// get app and history
const { history } = await getChatItems({
appId,
chatId,
limit: 30,
field: `dataId obj value adminFeedback userBadFeedback userGoodFeedback ${
DispatchNodeResponseKeyEnum.nodeResponse
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`
});
jsonRes<InitChatResponse>(res, {
data: {
chatId,
appId,
title: chat?.title || '新对话',
userAvatar: undefined,
variables: chat?.variables || {},
history,
app: {
userGuideModule: getGuideModule(app.modules),
chatModels: getChatModelNameListByModules(app.modules),
name: app.name,
avatar: app.avatar,
intro: app.intro
}
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export const config = {
api: {
responseLimit: '10mb'
}
};

View File

@@ -26,7 +26,7 @@ async function handler(
});
const [rebuildingCount, trainingCount] = await Promise.all([
MongoDatasetData.countDocuments({ teamId, datasetId, rebuilding: true }),
MongoDatasetData.countDocuments({ rebuilding: true, teamId, datasetId }),
MongoDatasetTraining.countDocuments({ teamId, datasetId })
]);

View File

@@ -7,7 +7,6 @@ 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/workflow/utils';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { NextAPI } from '@/service/middleware/entry';
@@ -47,14 +46,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
// auth role
const { teamId, tmbId } = await authChatCert({ req, authToken: true });
// auth app
const app = await MongoApp.findById(appId, 'modules').lean();
if (!app) {
throw new Error('app not found');
}
const { whisperConfig } = splitGuideModule(getGuideModule(app?.modules));
if (!whisperConfig?.open) {
throw new Error('Whisper is not open in the app');
}
// const app = await MongoApp.findById(appId, 'modules').lean();
// if (!app) {
// throw new Error('app not found');
// }
// if (!whisperConfig?.open) {
// throw new Error('Whisper is not open in the app');
// }
const ai = getAIApi();

View File

@@ -170,7 +170,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// 1. get and concat history; 2. get app workflow
const limit = getMaxHistoryLimitFromNodes(app.modules);
const [{ history }, { nodes, edges }] = await Promise.all([
const [{ history }, { nodes, edges, chatConfig }] = await Promise.all([
getChatItems({
appId: app._id,
chatId,
@@ -249,6 +249,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
teamId,
tmbId: tmbId,
nodes,
appChatConfig: chatConfig,
variables: newVariables,
isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time
shareId,

View File

@@ -1,7 +1,6 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Box, Flex, IconButton, useTheme, useDisclosure, Button } from '@chakra-ui/react';
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';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
@@ -9,8 +8,7 @@ import dynamic from 'next/dynamic';
import MyIcon from '@fastgpt/web/components/common/Icon';
import ChatTest, { type ChatTestComponentRef } from '@/components/core/workflow/Flow/ChatTest';
import { flowNode2StoreNodes } from '@/components/core/workflow/utils';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { uiWorkflow2StoreWorkflow } from '@/components/core/workflow/utils';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { getErrText } from '@fastgpt/global/common/error/utils';
@@ -27,19 +25,16 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '@/components/core/workflow/context';
import { useInterval, useUpdateEffect } from 'ahooks';
import { useI18n } from '@/web/context/I18n';
import { getGuideModule, splitGuideModule } from '@fastgpt/global/core/workflow/utils';
import { importQuestionGuides } from '@/web/core/app/api';
import { getAppQGuideCustomURL, getNodesWithNoQGuide } from '@/web/core/app/utils';
import { AppContext } from '@/web/core/app/context/appContext';
const ImportSettings = dynamic(() => import('@/components/core/workflow/Flow/ImportSettings'));
const PublishHistories = dynamic(
() => import('@/components/core/workflow/components/PublishHistoriesSlider')
);
type Props = { app: AppSchema; onClose: () => void };
type Props = { onClose: () => void };
const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
app,
ChatTestRef,
setWorkflowTestData,
onClose
@@ -55,7 +50,9 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
>
>;
}) {
const isV2Workflow = app?.version === 'v2';
const { appDetail } = useContextSelector(AppContext, (v) => v);
const isV2Workflow = appDetail?.version === 'v2';
const theme = useTheme();
const { toast } = useToast();
@@ -66,7 +63,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const { openConfirm: openConfigPublish, ConfirmModal } = useConfirm({
content: t('core.app.Publish Confirm')
});
const { publishApp, updateAppDetail } = useAppStore();
const { publishApp, updateAppDetail } = useContextSelector(AppContext, (v) => v);
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const [isSaving, setIsSaving] = useState(false);
@@ -90,7 +87,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
if (!checkResults) {
const storeNodes = flowNode2StoreNodes({ nodes, edges });
const storeNodes = uiWorkflow2StoreWorkflow({ nodes, edges });
return storeNodes;
} else {
@@ -112,12 +109,13 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
if (nodes.length === 0) return null;
setIsSaving(true);
const storeWorkflow = flowNode2StoreNodes({ nodes, edges });
const storeWorkflow = uiWorkflow2StoreWorkflow({ nodes, edges });
try {
await updateAppDetail(app._id, {
await updateAppDetail({
...storeWorkflow,
type: AppTypeEnum.advanced,
chatConfig: appDetail.chatConfig,
//@ts-ignore
version: 'v2'
});
@@ -134,7 +132,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
return null;
},
[isV2Workflow, isShowVersionHistories, edges, updateAppDetail, app._id, t]
[isV2Workflow, isShowVersionHistories, edges, updateAppDetail, appDetail.chatConfig, t]
);
const onclickPublish = useCallback(async () => {
@@ -142,19 +140,10 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
const data = await flowData2StoreDataAndCheck();
if (data) {
try {
const { questionGuideText } = splitGuideModule(getGuideModule(data.nodes));
await importQuestionGuides({
appId: app._id,
textList: questionGuideText.textList,
customURL: getAppQGuideCustomURL(app)
});
const newNodes = getNodesWithNoQGuide(data.nodes, questionGuideText);
await publishApp(app._id, {
await publishApp({
...data,
nodes: newNodes,
type: AppTypeEnum.advanced,
chatConfig: appDetail.chatConfig,
//@ts-ignore
version: 'v2'
});
@@ -172,7 +161,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
}
setIsSaving(false);
}, [flowData2StoreDataAndCheck, publishApp, app._id, toast, t, ChatTestRef]);
}, [flowData2StoreDataAndCheck, publishApp, appDetail.chatConfig, toast, t, ChatTestRef]);
const saveAndBack = useCallback(async () => {
try {
@@ -188,7 +177,8 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
JSON.stringify(
{
nodes: filterSensitiveNodesData(data.nodes),
edges: data.edges
edges: data.edges,
chatConfig: appDetail.chatConfig
},
null,
2
@@ -196,7 +186,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
appT('Export Config Successful')
);
}
}, [appT, copyData, flowData2StoreDataAndCheck]);
}, [appDetail.chatConfig, appT, copyData, flowData2StoreDataAndCheck]);
// effect
useBeforeunload({
@@ -205,7 +195,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
});
useInterval(() => {
if (!app._id) return;
if (!appDetail._id) return;
onclickSave(!!workflowDebugData);
}, 20000);
@@ -235,7 +225,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
/>
<Box ml={[2, 4]}>
<Box fontSize={['md', 'lg']} fontWeight={'bold'}>
{app.name}
{appDetail.name}
</Box>
{!isShowVersionHistories && isV2Workflow && (
<MyTooltip label={t('core.app.Onclick to save')}>
@@ -327,7 +317,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
theme.borders.base,
isSaving,
saveAndBack,
app.name,
appDetail.name,
isShowVersionHistories,
isV2Workflow,
t,
@@ -354,7 +344,6 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
});
const Header = (props: Props) => {
const { app } = props;
const ChatTestRef = useRef<ChatTestComponentRef>(null);
const [workflowTestData, setWorkflowTestData] = useState<{
@@ -374,13 +363,7 @@ const Header = (props: Props) => {
ChatTestRef={ChatTestRef}
setWorkflowTestData={setWorkflowTestData}
/>
<ChatTest
ref={ChatTestRef}
isOpen={isOpenTest}
{...workflowTestData}
app={app}
onClose={onCloseTest}
/>
<ChatTest ref={ChatTestRef} isOpen={isOpenTest} {...workflowTestData} onClose={onCloseTest} />
</>
);
};

View File

@@ -7,11 +7,15 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
import WorkflowContextProvider, { WorkflowContext } from '@/components/core/workflow/context';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '@/web/core/app/context/appContext';
import { useMount } from 'ahooks';
type Props = { app: AppSchema; onClose: () => void };
type Props = { onClose: () => void };
const Render = ({ app, onClose }: Props) => {
const isV2Workflow = app?.version === 'v2';
const Render = ({ onClose }: Props) => {
const appDetail = useContextSelector(AppContext, (e) => e.appDetail);
const isV2Workflow = appDetail?.version === 'v2';
const { openConfirm, ConfirmModal } = useConfirm({
showCancel: false,
content:
@@ -21,26 +25,23 @@ const Render = ({ app, onClose }: Props) => {
const initData = useContextSelector(WorkflowContext, (v) => v.initData);
const workflowStringData = JSON.stringify({
nodes: app.modules || [],
edges: app.edges || []
nodes: appDetail.modules || [],
edges: appDetail.edges || []
});
useEffect(() => {
if (!isV2Workflow) return;
initData(JSON.parse(workflowStringData));
}, [isV2Workflow, initData, app._id, workflowStringData]);
useEffect(() => {
useMount(() => {
if (!isV2Workflow) {
openConfirm(() => {
initData(JSON.parse(JSON.stringify(v1Workflow2V2((app.modules || []) as any))));
initData(JSON.parse(JSON.stringify(v1Workflow2V2((appDetail.modules || []) as any))));
})();
} else {
initData(JSON.parse(workflowStringData));
}
}, [app.modules, initData, isV2Workflow, openConfirm]);
});
const memoRender = useMemo(() => {
return <Flow Header={<Header app={app} onClose={onClose} />} />;
}, [app, onClose]);
return <Flow Header={<Header onClose={onClose} />} />;
}, [onClose]);
return (
<>
@@ -51,12 +52,13 @@ const Render = ({ app, onClose }: Props) => {
};
export default React.memo(function FlowEdit(props: Props) {
const filterAppIds = useMemo(() => [props.app._id], [props.app._id]);
const appDetail = useContextSelector(AppContext, (e) => e.appDetail);
const filterAppIds = useMemo(() => [appDetail._id], [appDetail._id]);
return (
<WorkflowContextProvider
value={{
appId: props.app._id,
appId: appDetail._id,
mode: 'app',
filterAppIds,
basicNodeTemplates: appSystemModuleTemplates

View File

@@ -19,10 +19,11 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import Avatar from '@/components/Avatar';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import PermissionRadio from '@/components/support/permission/Radio';
import { useTranslation } from 'next-i18next';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import { AppContext } from '@/web/core/app/context/appContext';
import { useContextSelector } from 'use-context-selector';
const InfoModal = ({
defaultApp,
@@ -35,7 +36,7 @@ const InfoModal = ({
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const { updateAppDetail } = useAppStore();
const { updateAppDetail } = useContextSelector(AppContext, (v) => v);
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png',
@@ -55,7 +56,7 @@ const InfoModal = ({
// submit config
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
mutationFn: async (data: AppSchema) => {
await updateAppDetail(data._id, {
await updateAppDetail({
name: data.name,
avatar: data.avatar,
intro: data.intro,

View File

@@ -335,7 +335,7 @@ const DetailLogsModal = ({
feedbackType={'admin'}
showMarkIcon
showVoiceIcon={false}
userGuideModule={chat?.app?.userGuideModule}
chatConfig={chat?.app?.chatConfig}
appId={appId}
chatId={chatId}
/>

View File

@@ -8,7 +8,6 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import { delModelById } from '@/web/core/app/api';
import { useTranslation } from 'next-i18next';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import PermissionIconText from '@/components/support/permission/IconText';
import dynamic from 'next/dynamic';
import Avatar from '@/components/Avatar';
@@ -16,6 +15,8 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import TagsEditModal from './TagsEditModal';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useI18n } from '@/web/context/I18n';
import { AppContext } from '@/web/core/app/context/appContext';
import { useContextSelector } from 'use-context-selector';
const InfoModal = dynamic(() => import('../InfoModal'));
const AppCard = ({ appId }: { appId: string }) => {
@@ -24,7 +25,7 @@ const AppCard = ({ appId }: { appId: string }) => {
const { appT } = useI18n();
const { toast } = useToast();
const { appDetail } = useAppStore();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const { feConfigs } = useSystemStore();
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
const [TeamTagsSet, setTeamTagsSet] = useState<AppSchema>();

View File

@@ -16,12 +16,13 @@ import {
initWorkflowEdgeStatus,
storeNodes2RuntimeNodes
} from '@fastgpt/global/core/workflow/runtime/utils';
import { useCreation, useMemoizedFn, useSafeState } from 'ahooks';
import { useMemoizedFn, useSafeState } from 'ahooks';
import { UseFormReturn } from 'react-hook-form';
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { form2AppWorkflow } from '@/web/core/app/utils';
import { useI18n } from '@/web/context/I18n';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '@/web/core/app/context/appContext';
const ChatTest = ({
editForm,
@@ -35,18 +36,15 @@ const ChatTest = ({
const { userInfo } = useUserStore();
const ChatBoxRef = useRef<ComponentRef>(null);
const { appDetail } = useAppStore();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const { watch } = editForm;
const chatConfig = watch('chatConfig');
const [workflowData, setWorkflowData] = useSafeState({
nodes: appDetail.modules || [],
edges: appDetail.edges || []
});
const userGuideModule = useCreation(
() => getGuideModule(workflowData.nodes),
[workflowData.nodes]
);
const startChat = useMemoizedFn(
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
@@ -131,7 +129,7 @@ const ChatTest = ({
appAvatar={appDetail.avatar}
userAvatar={userInfo?.avatar}
showMarkIcon
userGuideModule={userGuideModule}
chatConfig={chatConfig}
showFileSelector={checkChatSupportSelectFileByModules(workflowData.nodes)}
onStartChat={startChat}
onDelMessage={() => {}}

View File

@@ -11,12 +11,7 @@ import { useRouter } from 'next/router';
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 {
form2AppWorkflow,
getAppQGuideCustomURL,
getNodesWithNoQGuide
} from '@/web/core/app/utils';
import { form2AppWorkflow } from '@/web/core/app/utils';
import dynamic from 'next/dynamic';
import MyTooltip from '@/components/MyTooltip';
@@ -34,15 +29,27 @@ import { TTSTypeEnum } from '@/web/core/app/constants';
import { getSystemVariables } from '@/web/core/app/utils';
import { useUpdate } from 'ahooks';
import { useI18n } from '@/web/context/I18n';
import { importQuestionGuides } from '@/web/core/app/api';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '@/web/core/app/context/appContext';
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'));
const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig'));
const QGuidesConfigModal = dynamic(() => import('@/components/core/app/QGuidesConfig'));
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'), {
ssr: false
});
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'), {
ssr: false
});
const ToolSelectModal = dynamic(() => import('./ToolSelectModal'), { ssr: false });
const TTSSelect = dynamic(() => import('@/components/core/app/TTSSelect'), { ssr: false });
const QGSwitch = dynamic(() => import('@/components/core/app/QGSwitch'), { ssr: false });
const WhisperConfig = dynamic(() => import('@/components/core/app/WhisperConfig'), { ssr: false });
const InputGuideConfig = dynamic(
() => import('@/components/core/chat/appConfig/InputGuideConfig'),
{ ssr: false }
);
const ScheduledTriggerConfig = dynamic(
() => import('@/components/core/app/ScheduledTriggerConfig'),
{ ssr: false }
);
const BoxStyles: BoxProps = {
px: 5,
@@ -70,7 +77,7 @@ const EditForm = ({
const { t } = useTranslation();
const { appT } = useI18n();
const { appDetail, publishApp } = useAppStore();
const { appDetail, publishApp } = useContextSelector(AppContext, (v) => v);
const { allDatasets } = useDatasetStore();
const { llmModelList } = useSystemStore();
@@ -107,18 +114,19 @@ const EditForm = ({
const aiSystemPrompt = watch('aiSettings.systemPrompt');
const selectLLMModel = watch('aiSettings.model');
const datasetSearchSetting = watch('dataset');
const variables = watch('userGuide.variables');
const variables = watch('chatConfig.variables');
const formatVariables: any = useMemo(
() => formatEditorVariablePickerIcon([...getSystemVariables(t), ...variables]),
() => formatEditorVariablePickerIcon([...getSystemVariables(t), ...(variables || [])]),
[t, variables]
);
const searchMode = watch('dataset.searchMode');
const tts = getValues('userGuide.tts');
const whisperConfig = getValues('userGuide.whisper');
const postQuestionGuide = getValues('userGuide.questionGuide');
const tts = getValues('chatConfig.ttsConfig');
const whisperConfig = getValues('chatConfig.whisperConfig');
const postQuestionGuide = getValues('chatConfig.questionGuide');
const selectedTools = watch('selectedTools');
const QGuidesConfig = watch('userGuide.questionGuideText');
const inputGuideConfig = watch('chatConfig.chatInputGuide');
const scheduledTriggerConfig = watch('chatConfig.scheduledTriggerConfig');
const searchMode = watch('dataset.searchMode');
const selectDatasets = useMemo(
() => allDatasets.filter((item) => datasets.find((dataset) => dataset.datasetId === item._id)),
@@ -132,20 +140,11 @@ const EditForm = ({
/* on save app */
const { mutate: onSubmitPublish, isLoading: isSaving } = useRequest({
mutationFn: async (data: AppSimpleEditFormType) => {
const questionGuideText = data.userGuide.questionGuideText;
await importQuestionGuides({
appId: appDetail._id,
textList: questionGuideText.textList,
customURL: getAppQGuideCustomURL(appDetail)
});
const { nodes, edges } = form2AppWorkflow(data);
const newNodes = getNodesWithNoQGuide(nodes, questionGuideText);
await publishApp(appDetail._id, {
nodes: newNodes,
await publishApp({
nodes,
edges,
chatConfig: data.chatConfig,
type: AppTypeEnum.simple
});
},
@@ -403,7 +402,7 @@ const EditForm = ({
<VariableEdit
variables={variables}
onChange={(e) => {
setValue('userGuide.variables', e);
setValue('chatConfig.variables', e);
}}
/>
</Box>
@@ -422,9 +421,9 @@ const EditForm = ({
bg={'myWhite.400'}
rows={5}
placeholder={t(welcomeTextTip)}
defaultValue={getValues('userGuide.welcomeText')}
defaultValue={getValues('chatConfig.welcomeText')}
onBlur={(e) => {
setValue('userGuide.welcomeText', e.target.value || '');
setValue('chatConfig.welcomeText', e.target.value || '');
}}
/>
</Box>
@@ -434,7 +433,7 @@ const EditForm = ({
<TTSSelect
value={tts}
onChange={(e) => {
setValue('userGuide.tts', e);
setValue('chatConfig.ttsConfig', e);
}}
/>
</Box>
@@ -442,10 +441,10 @@ const EditForm = ({
{/* whisper */}
<Box {...BoxStyles}>
<WhisperConfig
isOpenAudio={tts.type !== TTSTypeEnum.none}
isOpenAudio={tts?.type !== TTSTypeEnum.none}
value={whisperConfig}
onChange={(e) => {
setValue('userGuide.whisper', e);
setValue('chatConfig.whisperConfig', e);
}}
/>
</Box>
@@ -456,17 +455,28 @@ const EditForm = ({
isChecked={postQuestionGuide}
size={'lg'}
onChange={(e) => {
setValue('userGuide.questionGuide', e.target.checked);
setValue('chatConfig.questionGuide', e.target.checked);
}}
/>
</Box>
{/* question tips */}
<Box {...BoxStyles} borderBottom={'none'}>
<QGuidesConfigModal
value={QGuidesConfig}
<Box {...BoxStyles}>
<InputGuideConfig
appId={appDetail._id}
value={inputGuideConfig}
onChange={(e) => {
setValue('userGuide.questionGuideText', e);
setValue('chatConfig.chatInputGuide', e);
}}
/>
</Box>
{/* timer trigger */}
<Box {...BoxStyles} borderBottom={'none'}>
<ScheduledTriggerConfig
value={scheduledTriggerConfig}
onChange={(e) => {
setValue('chatConfig.scheduledTriggerConfig', e);
}}
/>
</Box>

View File

@@ -19,22 +19,22 @@ import {
TagLabel
} from '@chakra-ui/react';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { getTeamsTags } from '@/web/support/user/team/api';
import { useQuery } from '@tanstack/react-query';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from '@/web/core/app/context/appContext';
const TagsEditModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { appDetail } = useAppStore();
const { toast } = useToast();
const { updateAppDetail } = useAppStore();
const { appDetail, updateAppDetail } = useContextSelector(AppContext, (v) => v);
const [selectedTags, setSelectedTags] = useState<string[]>(appDetail?.teamTags || []);
// submit config
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
mutationFn: async () => {
await updateAppDetail(appDetail._id, {
await updateAppDetail({
teamTags: selectedTags
});
},

View File

@@ -4,36 +4,42 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useSticky } from '@/web/common/hooks/useSticky';
import { useMount } from 'ahooks';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { useForm } from 'react-hook-form';
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
import { appWorkflow2Form, getDefaultAppForm } from '@fastgpt/global/core/app/utils';
import ChatTest from './ChatTest';
import AppCard from './AppCard';
import EditForm from './EditForm';
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
import { AppContext } from '@/web/core/app/context/appContext';
import { useContextSelector } from 'use-context-selector';
const SimpleEdit = ({ appId }: { appId: string }) => {
const { isPc } = useSystemStore();
const { parentRef, divRef, isSticky } = useSticky();
const { loadAllDatasets } = useDatasetStore();
const { appDetail } = useAppStore();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const editForm = useForm<AppSimpleEditFormType>({
defaultValues: appWorkflow2Form({
nodes: appDetail.modules
})
defaultValues: getDefaultAppForm()
});
// show selected dataset
useMount(() => {
loadAllDatasets();
editForm.reset(
appWorkflow2Form({
nodes: appDetail.modules,
chatConfig: appDetail.chatConfig
})
);
if (appDetail.version !== 'v2') {
editForm.reset(
appWorkflow2Form({
nodes: v1Workflow2V2((appDetail.modules || []) as any)?.nodes
nodes: v1Workflow2V2((appDetail.modules || []) as any)?.nodes,
chatConfig: appDetail.chatConfig
})
);
}

View File

@@ -1,9 +1,7 @@
import React, { useEffect, useMemo, useCallback } from 'react';
import React, { useMemo, useCallback } from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, IconButton, useTheme } from '@chakra-ui/react';
import dynamic from 'next/dynamic';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import Tabs from '@/components/Tabs';
@@ -14,11 +12,11 @@ import PageContainer from '@/components/PageContainer';
import Loading from '@fastgpt/web/components/common/MyLoading';
import SimpleEdit from './components/SimpleEdit';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import Head from 'next/head';
import { useTranslation } from 'next-i18next';
import { useI18n } from '@/web/context/I18n';
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
import { AppContext, AppContextProvider } from '@/web/core/app/context/appContext';
import { useContextSelector } from 'use-context-selector';
const FlowEdit = dynamic(() => import('./components/FlowEdit'), {
loading: () => <Loading />
@@ -34,19 +32,16 @@ enum TabEnum {
'startChat' = 'startChat'
}
const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const AppDetail = ({ appId, currentTab }: { appId: string; currentTab: TabEnum }) => {
const { t } = useTranslation();
const { appT } = useI18n();
const router = useRouter();
const theme = useTheme();
const { feConfigs } = useSystemStore();
const { toast } = useToast();
const { appId } = router.query as { appId: string };
const { appDetail, loadAppDetail } = useAppStore();
const { appDetail, loadingApp } = useContextSelector(AppContext, (e) => e);
const setCurrentTab = useCallback(
(tab: `${TabEnum}`) => {
(tab: TabEnum) => {
router.push({
query: {
...router.query,
@@ -86,26 +81,13 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const onCloseFlowEdit = useCallback(() => setCurrentTab(TabEnum.simpleEdit), [setCurrentTab]);
const { isSuccess, isLoading } = useQuery([appId], () => loadAppDetail(appId, true), {
onError(err: any) {
toast({
title: err?.message || t('core.app.error.Get app failed'),
status: 'error'
});
router.replace('/app/list');
},
onSettled() {
router.prefetch(`/chat?appId=${appId}`);
}
});
return (
<>
<Head>
<title>{appDetail.name}</title>
</Head>
<PageContainer isLoading={isLoading}>
{isSuccess && (
<PageContainer isLoading={loadingApp}>
{!loadingApp && (
<Flex flexDirection={['column', 'row']} h={'100%'}>
{/* pc tab */}
<Box
@@ -180,9 +162,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
</Box>
<Box flex={'1 0 0'} h={[0, '100%']} overflow={['overlay', '']}>
{currentTab === TabEnum.simpleEdit && <SimpleEdit appId={appId} />}
{currentTab === TabEnum.adEdit && appDetail && (
<FlowEdit app={appDetail} onClose={onCloseFlowEdit} />
)}
{currentTab === TabEnum.adEdit && appDetail && <FlowEdit onClose={onCloseFlowEdit} />}
{currentTab === TabEnum.logs && <Logs appId={appId} />}
{currentTab === TabEnum.publish && <Publish appId={appId} />}
</Box>
@@ -193,15 +173,25 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
);
};
const Provider = ({ appId, currentTab }: { appId: string; currentTab: TabEnum }) => {
return (
<AppContextProvider appId={appId}>
<AppDetail appId={appId} currentTab={currentTab} />
</AppContextProvider>
);
};
export async function getServerSideProps(context: any) {
const currentTab = context?.query?.currentTab || TabEnum.simpleEdit;
const appId = context?.query?.appId || '';
return {
props: {
currentTab,
...(await serviceSideProps(context, ['app', 'file', 'publish', 'workflow']))
appId,
...(await serviceSideProps(context, ['app', 'chat', 'file', 'publish', 'workflow']))
}
};
}
export default AppDetail;
export default Provider;

View File

@@ -45,7 +45,7 @@ const MyApps = () => {
title: '删除成功',
status: 'success'
});
loadMyApps(true);
loadMyApps();
} catch (err: any) {
toast({
title: err?.message || t('common.Delete Failed'),
@@ -57,7 +57,7 @@ const MyApps = () => {
);
/* 加载模型 */
const { isFetching } = useQuery(['loadApps'], () => loadMyApps(true), {
const { isFetching } = useQuery(['loadApps'], () => loadMyApps(), {
refetchOnMount: true
});
@@ -182,7 +182,7 @@ const MyApps = () => {
)}
<ConfirmModal />
{isOpenCreateModal && (
<CreateModal onClose={onCloseCreateModal} onSuccess={() => loadMyApps(true)} />
<CreateModal onClose={onCloseCreateModal} onSuccess={() => loadMyApps()} />
)}
</PageContainer>
);

View File

@@ -33,15 +33,10 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
import { useUserStore } from '@/web/support/user/useUserStore';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import {
checkChatSupportSelectFileByChatModels,
getAppQuestionGuidesByUserGuideModule
} from '@/web/core/chat/utils';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
const router = useRouter();
@@ -133,7 +128,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
[appId, chatId, histories, pushHistory, router, setChatData, updateHistory]
);
useQuery(['loadModels'], () => loadMyApps(false));
useQuery(['loadModels'], () => loadMyApps());
// get chat app info
const loadChatInfo = useCallback(
@@ -354,7 +349,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
showEmptyIntro
appAvatar={chatData.app.avatar}
userAvatar={userInfo?.avatar}
userGuideModule={chatData.app?.userGuideModule}
chatConfig={chatData.app?.chatConfig}
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
feedbackType={'user'}
onStartChat={startChat}

View File

@@ -20,10 +20,7 @@ import PageContainer from '@/components/PageContainer';
import ChatHeader from './components/ChatHeader';
import ChatHistorySlider from './components/ChatHistorySlider';
import { serviceSideProps } from '@/web/common/utils/i18n';
import {
checkChatSupportSelectFileByChatModels,
getAppQuestionGuidesByUserGuideModule
} from '@/web/core/chat/utils';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useTranslation } from 'next-i18next';
import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
@@ -34,9 +31,6 @@ import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
import { addLog } from '@fastgpt/service/common/system/log';
import { connectToDatabase } from '@/service/mongo';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
const OutLink = ({
appName,
@@ -393,7 +387,7 @@ const OutLink = ({
ref={ChatBoxRef}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
userGuideModule={chatData.app?.userGuideModule}
chatConfig={chatData.app?.chatConfig}
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
feedbackType={'user'}
onUpdateVariable={(e) => {}}

View File

@@ -39,7 +39,6 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
import SliderApps from './components/SliderApps';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
import { useAppStore } from '@/web/core/app/store/useAppStore';
import { getAppQGuideCustomURL } from '@/web/core/app/utils';
const OutLink = () => {
@@ -364,7 +363,7 @@ const OutLink = () => {
ref={ChatBoxRef}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
userGuideModule={chatData.app?.userGuideModule}
chatConfig={chatData.app?.chatConfig}
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
feedbackType={'user'}
onUpdateVariable={(e) => {}}

View File

@@ -7,7 +7,7 @@ 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 { flowNode2StoreNodes } from '@/components/core/workflow/utils';
import { uiWorkflow2StoreWorkflow } from '@/components/core/workflow/utils';
import { putUpdatePlugin } from '@/web/core/plugin/api';
import { useToast } from '@fastgpt/web/hooks/useToast';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
@@ -39,7 +39,7 @@ const Header = ({ plugin, onClose }: Props) => {
const checkResults = checkWorkflowNodeAndConnection({ nodes, edges });
if (!checkResults) {
const storeNodes = flowNode2StoreNodes({ nodes, edges });
const storeNodes = uiWorkflow2StoreWorkflow({ nodes, edges });
return storeNodes;
} else {
@@ -68,7 +68,7 @@ const Header = ({ plugin, onClose }: Props) => {
}
});
const onCopy = useCallback(async () => {
const onExportWorkflow = useCallback(async () => {
const data = await flowData2StoreDataAndCheck();
if (data) {
copyData(
@@ -125,7 +125,7 @@ const Header = ({ plugin, onClose }: Props) => {
{
label: appT('Export Configs'),
icon: 'export',
onClick: onCopy
onClick: onExportWorkflow
}
]}
/>
@@ -147,7 +147,7 @@ const Header = ({ plugin, onClose }: Props) => {
isOpenImport,
onClose,
onCloseImport,
onCopy,
onExportWorkflow,
onOpenImport,
onclickSave,
plugin.name,

View File

@@ -15,6 +15,7 @@ import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
import WorkflowContextProvider, { WorkflowContext } from '@/components/core/workflow/context';
import { useContextSelector } from 'use-context-selector';
import { AppContextProvider } from '@/web/core/app/context/appContext';
type Props = { pluginId: string };
@@ -77,13 +78,15 @@ const Render = ({ pluginId }: Props) => {
);
};
export default function FlowEdit(props: any) {
function Provider(props: Props) {
return (
<WorkflowContextProvider
value={{ mode: 'plugin', basicNodeTemplates: pluginSystemModuleTemplates }}
>
<Render {...props} />
</WorkflowContextProvider>
<AppContextProvider appId={''}>
<WorkflowContextProvider
value={{ mode: 'plugin', basicNodeTemplates: pluginSystemModuleTemplates }}
>
<Render {...props} />
</WorkflowContextProvider>
</AppContextProvider>
);
}
@@ -95,3 +98,5 @@ export async function getServerSideProps(context: any) {
}
};
}
export default Provider;