V4.8.14 dev (#3234)

* feat: rewrite chat context (#3176)

* feat: add app auto execute (#3115)

* feat: add app auto execute

* auto exec configtion

* chatting animation

* change icon

* fix

* fix

* fix link

* feat: add chat context to all chatbox

* perf: loading ui

---------

Co-authored-by: heheer <heheer@sealos.io>

* app auto exec (#3179)

* add chat records loaded state (#3184)

* perf: chat store reset storage (#3186)

* perf: chat store reset storage

* perf: auto exec code

* chore: workflow ui (#3175)

* chore: workflow ui

* fix

* change icon color config

* change popover to mymenu

* 4.8.14 test (#3189)

* update doc

* fix: token check

* perf: icon button

* update doc

* feat: share page support configuration Whether to allow the original view (#3194)

* update doc

* perf: fix index (#3206)

* perf: i18n

* perf: Add service entry (#3226)

* 4.8.14 test (#3228)

* fix: ai log

* fix: text splitter

* fix: reference unselect & user form description & simple to advance (#3229)

* fix: reference unselect & user form description & simple to advance

* change abort position

* perf

* perf: code (#3232)

* perf: code

* update doc

* fix: create btn permission (#3233)

* update doc

* fix: refresh chatbox listener

* perf: check invalid reference

* perf: check invalid reference

* update doc

* fix: ui props

---------

Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
Archer
2024-11-26 12:02:58 +08:00
committed by GitHub
parent 7e1d31b5a9
commit 8aa6b53760
221 changed files with 3831 additions and 2737 deletions

View File

@@ -2,7 +2,7 @@
Read db file content and response 3000 words
*/
import type { NextApiResponse } from 'next';
import { authFile } from '@fastgpt/service/support/permission/auth/file';
import { authCollectionFile } from '@fastgpt/service/support/permission/auth/file';
import { NextAPI } from '@/service/middleware/entry';
import { DatasetSourceReadTypeEnum } from '@fastgpt/global/core/dataset/constants';
import { readDatasetSourceRawText } from '@fastgpt/service/core/dataset/read';
@@ -26,7 +26,7 @@ async function handler(req: ApiRequestProps<PreviewContextProps>, res: NextApiRe
const { teamId } = await (async () => {
if (type === DatasetSourceReadTypeEnum.fileLocal) {
return authFile({
return authCollectionFile({
req,
authToken: true,
authApiKey: true,

View File

@@ -9,7 +9,17 @@ import { ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
import { addLog } from '@fastgpt/service/common/system/log';
import { authFrequencyLimit } from '@/service/common/frequencyLimit/api';
import { addSeconds } from 'date-fns';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
export type UploadChatFileProps = {
appId: string;
} & OutLinkChatAuthProps;
export type UploadDatasetFileProps = {
datasetId: string;
};
const authUploadLimit = (tmbId: string) => {
if (!global.feConfigs.uploadFileMaxAmount) return;
@@ -28,15 +38,43 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const upload = getUploadModel({
maxSize: global.feConfigs?.uploadFileMaxSize
});
const { file, bucketName, metadata } = await upload.doUpload(req, res);
const { file, bucketName, metadata, data } = await upload.doUpload<
UploadChatFileProps | UploadDatasetFileProps
>(req, res);
filePaths.push(file.path);
const { teamId, tmbId, outLinkUid } = await authChatCert({
req,
authToken: true,
authApiKey: true
});
await authUploadLimit(outLinkUid || tmbId);
const { teamId, uid } = await (async () => {
if (bucketName === 'chat') {
const chatData = data as UploadChatFileProps;
const authData = await authChatCrud({
req,
authToken: true,
authApiKey: true,
...chatData
});
return {
teamId: authData.teamId,
uid: authData.uid
};
}
if (bucketName === 'dataset') {
const chatData = data as UploadDatasetFileProps;
const authData = await authDataset({
datasetId: chatData.datasetId,
per: WritePermissionVal,
req,
authToken: true,
authApiKey: true
});
return {
teamId: authData.teamId,
uid: authData.tmbId
};
}
return Promise.reject('bucketName is empty');
})();
await authUploadLimit(uid);
addLog.info(`Upload file success ${file.originalname}, cost ${Date.now() - start}ms`);
@@ -46,7 +84,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
const fileId = await uploadFile({
teamId,
tmbId,
uid,
bucketName,
path: file.path,
filename: file.originalname,
@@ -61,7 +99,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
previewUrl: `${ReadFileBaseUrl}/${file.originalname}?token=${await createFileToken({
bucketName,
teamId,
tmbId,
uid,
fileId
})}`
}

View File

@@ -1,16 +1,19 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { uploadMongoImg } from '@fastgpt/service/common/file/image/controller';
import { UploadImgProps } from '@fastgpt/global/common/file/api';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
/*
Upload avatar image
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const body = req.body as UploadImgProps;
const { teamId } = await authChatCert({ req, authToken: true });
const { teamId } = await authCert({ req, authToken: true });
const imgId = await uploadMongoImg({
teamId,

View File

@@ -1,15 +1,25 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push';
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
async function handler(
req: ApiRequestProps<
OutLinkChatAuthProps & {
messages: ChatCompletionMessageParam[];
}
>,
res: NextApiResponse<any>
) {
try {
await connectToDatabase();
const { messages } = req.body as CreateQuestionGuideParams;
const { messages } = req.body;
const { tmbId, teamId } = await authChatCert({
req,
@@ -40,3 +50,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
}
}
export default NextAPI(handler);

View File

@@ -0,0 +1,47 @@
import type { NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { CreateQuestionGuideParams } from '@/global/core/ai/api.d';
import { pushQuestionGuideUsage } from '@/service/support/wallet/usage/push';
import { createQuestionGuide } from '@fastgpt/service/core/ai/functions/createQuestionGuide';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
async function handler(req: ApiRequestProps<CreateQuestionGuideParams>, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { messages } = req.body;
const { tmbId, teamId } = await authChatCrud({
req,
authToken: true,
authApiKey: true,
...req.body
});
const qgModel = global.llmModels[0];
const { result, tokens } = await createQuestionGuide({
messages,
model: qgModel.model
});
jsonRes(res, {
data: result
});
pushQuestionGuideUsage({
tokens,
teamId,
tmbId
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}
export default NextAPI(handler);

View File

@@ -43,10 +43,10 @@ async function handler(
});
return { id: appId };
} else {
await MongoApp.findByIdAndUpdate(appId, { type: AppTypeEnum.workflow });
}
await MongoApp.findByIdAndUpdate(appId, { type: AppTypeEnum.workflow });
return {};
}

View File

@@ -8,17 +8,14 @@ import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
import { getNextTimeByCronStringAndTimezone } from '@fastgpt/global/common/string/time';
import { PostPublishAppProps } from '@/global/core/app/api';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { ApiRequestProps } from '@fastgpt/service/type/next';
async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<{}> {
async function handler(
req: ApiRequestProps<PostPublishAppProps>,
res: NextApiResponse<any>
): Promise<{}> {
const { appId } = req.query as { appId: string };
const {
nodes = [],
edges = [],
chatConfig,
type,
isPublish,
versionName
} = req.body as PostPublishAppProps;
const { nodes = [], edges = [], chatConfig, isPublish, versionName } = req.body;
const { tmbId } = await authApp({ appId, req, per: WritePermissionVal, authToken: true });
@@ -50,11 +47,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
chatConfig,
updateTime: new Date(),
version: 'v2',
type,
scheduledTriggerConfig: chatConfig?.scheduledTriggerConfig,
scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig?.cronString
? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig)
: null,
// 只有发布才会更新定时器
...(isPublish && {
scheduledTriggerConfig: chatConfig?.scheduledTriggerConfig,
scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig?.cronString
? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig)
: null
}),
'pluginData.nodeVersion': _id
},
{

View File

@@ -1,16 +1,22 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { sseErrRes } from '@fastgpt/service/common/response';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import {
DispatchNodeResponseKeyEnum,
SseResponseEventEnum
} from '@fastgpt/global/core/workflow/runtime/constants';
import { responseWrite } from '@fastgpt/service/common/response';
import { pushChatUsage } from '@/service/support/wallet/usage/push';
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
import type { UserChatItemType } from '@fastgpt/global/core/chat/type';
import type { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
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 { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { removeEmptyUserInput } from '@fastgpt/global/core/chat/utils';
import {
concatHistories,
getChatTitleFromChatMessage,
removeEmptyUserInput
} from '@fastgpt/global/core/chat/utils';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import {
@@ -18,27 +24,37 @@ import {
updatePluginInputByVariables
} from '@fastgpt/global/core/workflow/utils';
import { NextAPI } from '@/service/middleware/entry';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { chatValue2RuntimePrompt, GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
import {
getLastInteractiveValue,
getMaxHistoryLimitFromNodes,
getWorkflowEntryNodeIds,
initWorkflowEdgeStatus,
rewriteNodeOutputByHistories,
storeNodes2RuntimeNodes
storeNodes2RuntimeNodes,
textAdaptGptResponse
} from '@fastgpt/global/core/workflow/runtime/utils';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { getWorkflowResponseWrite } from '@fastgpt/service/core/workflow/dispatch/utils';
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils';
import { getChatItems } from '@fastgpt/service/core/chat/controller';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { saveChat, updateInteractiveChat } from '@fastgpt/service/core/chat/saveChat';
export type Props = {
messages: ChatCompletionMessageParam[];
responseChatItemId: string;
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
variables: Record<string, any>;
appId: string;
appName: string;
chatId: string;
chatConfig: AppChatConfigType;
};
@@ -55,10 +71,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
nodes = [],
edges = [],
messages = [],
responseChatItemId,
variables = {},
appName,
appId,
chatConfig
chatConfig,
chatId
} = req.body as Props;
try {
if (!Array.isArray(nodes)) {
@@ -71,15 +89,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// console.log(JSON.stringify(chatMessages, null, 2), '====', chatMessages.length);
/* user auth */
const [{ app }, { teamId, tmbId }] = await Promise.all([
authApp({ req, authToken: true, appId, per: ReadPermissionVal }),
authCert({
req,
authToken: true
})
]);
// auth balance
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
const { app, teamId, tmbId } = await authApp({
req,
authToken: true,
appId,
per: ReadPermissionVal
});
const isPlugin = app.type === AppTypeEnum.plugin;
@@ -99,48 +114,79 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
return latestHumanChat;
})();
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, chatMessages));
const limit = getMaxHistoryLimitFromNodes(nodes);
const [{ histories }, chatDetail, { user }] = await Promise.all([
getChatItems({
appId,
chatId,
offset: 0,
limit,
field: `dataId obj value nodeOutputs`
}),
MongoChat.findOne({ appId: app._id, chatId }, 'source variableList variables'),
// auth balance
getUserChatInfoAndAuthTeamPoints(tmbId)
]);
// Plugin need to replace inputs
if (chatDetail?.variables) {
variables = {
...chatDetail.variables,
...variables
};
}
const newHistories = concatHistories(histories, chatMessages);
// Get runtimeNodes
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, newHistories));
if (isPlugin) {
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
variables = {};
} else {
if (!userQuestion.value) {
throw new Error('Params Error');
}
}
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
runtimeNodes = rewriteNodeOutputByHistories(chatMessages, runtimeNodes);
const workflowResponseWrite = getWorkflowResponseWrite({
res,
detail: true,
streamResponse: true
streamResponse: true,
id: chatId,
showNodeStatus: true
});
/* start process */
const { flowResponses, flowUsages } = await dispatchWorkFlow({
const { flowResponses, assistantResponses, newVariables, flowUsages } = await dispatchWorkFlow({
res,
requestOrigin: req.headers.origin,
mode: 'test',
user,
uid: tmbId,
runningAppInfo: {
id: appId,
teamId,
tmbId
},
uid: tmbId,
user,
chatId,
responseChatItemId,
runtimeNodes,
runtimeEdges: initWorkflowEdgeStatus(edges, chatMessages),
runtimeEdges: initWorkflowEdgeStatus(edges, newHistories),
variables,
query: removeEmptyUserInput(userQuestion.value),
chatConfig,
histories: chatMessages,
histories: newHistories,
stream: true,
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
workflowStreamResponse: workflowResponseWrite
});
workflowResponseWrite({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: null,
finish_reason: 'stop'
})
});
responseWrite({
res,
event: SseResponseEventEnum.answer,
@@ -152,7 +198,44 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
data: JSON.stringify(flowResponses)
});
res.end();
// save chat
const isInteractiveRequest = !!getLastInteractiveValue(histories);
const { text: userInteractiveVal } = chatValue2RuntimePrompt(userQuestion.value);
const newTitle = isPlugin
? variables.cTime ?? getSystemTime(user.timezone)
: getChatTitleFromChatMessage(userQuestion);
const aiResponse: AIChatItemType & { dataId?: string } = {
dataId: responseChatItemId,
obj: ChatRoleEnum.AI,
value: assistantResponses,
[DispatchNodeResponseKeyEnum.nodeResponse]: flowResponses
};
if (isInteractiveRequest) {
await updateInteractiveChat({
chatId,
appId: app._id,
userInteractiveVal,
aiResponse,
newVariables
});
} else {
await saveChat({
chatId,
appId: app._id,
teamId,
tmbId: tmbId,
nodes,
appChatConfig: chatConfig,
variables: newVariables,
isUpdateUseTime: false, // owner update use time
newTitle,
source: ChatSourceEnum.test,
content: [userQuestion, aiResponse]
});
}
pushChatUsage({
appName,
@@ -165,8 +248,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
} catch (err: any) {
res.status(500);
sseErrRes(res, err);
res.end();
}
res.end();
}
export default NextAPI(handler);

View File

@@ -1,44 +1,48 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import type { NextApiResponse } from 'next';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { ClearHistoriesProps } from '@/global/core/chat/api';
import { authOutLink } from '@/service/support/permission/auth/outLink';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
import { NextAPI } from '@/service/middleware/entry';
import { deleteChatFiles } from '@fastgpt/service/core/chat/controller';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { authChatCrud } from '@/service/support/permission/auth/chat';
/* clear chat history */
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { appId, shareId, outLinkUid, teamId, teamToken } = req.query as ClearHistoriesProps;
async function handler(req: ApiRequestProps<{}, ClearHistoriesProps>, res: NextApiResponse) {
const { appId, shareId, outLinkUid, teamId, teamToken } = req.query;
let chatAppId = appId!;
const {
teamId: chatTeamId,
tmbId,
uid,
authType
} = await authChatCrud({
req,
authToken: true,
authApiKey: true,
...req.query
});
const match = await (async () => {
if (shareId && outLinkUid) {
const { appId, uid } = await authOutLink({ shareId, outLinkUid });
chatAppId = appId;
if (shareId && outLinkUid && authType === 'outLink') {
return {
shareId,
outLinkUid: uid
};
}
if (teamId && teamToken) {
const { uid } = await authTeamSpaceToken({ teamId, teamToken });
return {
teamId,
teamId: chatTeamId,
appId,
outLinkUid: uid
};
}
if (appId) {
const { tmbId } = await authCert({ req, authToken: true, authApiKey: true });
if (teamId && teamToken && authType === 'teamDomain') {
return {
teamId: chatTeamId,
appId,
outLinkUid: uid
};
}
if (authType === 'token') {
return {
teamId: chatTeamId,
tmbId,
appId,
source: ChatSourceEnum.online
@@ -54,24 +58,22 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
await deleteChatFiles({ chatIdList: idList });
await mongoSessionRun(async (session) => {
return mongoSessionRun(async (session) => {
await MongoChatItem.deleteMany(
{
appId: chatAppId,
appId,
chatId: { $in: idList }
},
{ session }
);
await MongoChat.deleteMany(
{
appId: chatAppId,
appId,
chatId: { $in: idList }
},
{ session }
);
});
jsonRes(res);
}
export default NextAPI(handler);

View File

@@ -7,7 +7,6 @@ import { authChatCrud } from '@/service/support/permission/auth/chat';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { deleteChatFiles } from '@fastgpt/service/core/chat/controller';
/* clear chat history */
@@ -18,8 +17,7 @@ async function handler(req: ApiRequestProps<{}, DelHistoryProps>, res: NextApiRe
req,
authToken: true,
authApiKey: true,
...req.query,
per: WritePermissionVal
...req.query
});
await deleteChatFiles({ chatIdList: [chatId] });

View File

@@ -4,7 +4,6 @@ import { connectToDatabase } from '@/service/mongo';
import type { AdminUpdateFeedbackParams } from '@/global/core/chat/api.d';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -20,9 +19,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await authChatCrud({
req,
authToken: true,
authApiKey: true,
appId,
chatId,
per: ReadPermissionVal
chatId
});
await MongoChatItem.findOneAndUpdate(

View File

@@ -5,7 +5,6 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
import type { CloseCustomFeedbackParams } from '@/global/core/chat/api.d';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
/* remove custom feedback */
@@ -21,9 +20,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await authChatCrud({
req,
authToken: true,
authApiKey: true,
appId,
chatId,
per: ReadPermissionVal
chatId
});
await authCert({ req, authToken: true });

View File

@@ -1,71 +1,42 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import type { NextApiResponse } from 'next';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { UpdateChatFeedbackProps } from '@fastgpt/global/core/chat/api';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps } from '@fastgpt/service/type/next';
/* 初始化我的聊天框,需要身份验证 */
async function handler(req: NextApiRequest, res: NextApiResponse) {
const {
appId,
chatId,
dataId,
shareId,
teamId,
teamToken,
outLinkUid,
userBadFeedback,
userGoodFeedback
} = req.body as UpdateChatFeedbackProps;
async function handler(req: ApiRequestProps<UpdateChatFeedbackProps>, res: NextApiResponse) {
const { appId, chatId, dataId, userBadFeedback, userGoodFeedback } = req.body;
try {
await connectToDatabase();
await authChatCrud({
req,
authToken: true,
authApiKey: true,
appId,
teamId,
teamToken,
chatId,
shareId,
outLinkUid,
per: ReadPermissionVal
});
if (!dataId) {
throw new Error('dataId is required');
}
await MongoChatItem.findOneAndUpdate(
{
appId,
chatId,
dataId
},
{
$unset: {
...(userBadFeedback === undefined && { userBadFeedback: '' }),
...(userGoodFeedback === undefined && { userGoodFeedback: '' })
},
$set: {
...(userBadFeedback !== undefined && { userBadFeedback }),
...(userGoodFeedback !== undefined && { userGoodFeedback })
}
}
);
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
if (!chatId || !dataId) {
return Promise.reject('chatId or dataId is empty');
}
await authChatCrud({
req,
authToken: true,
authApiKey: true,
...req.body
});
await MongoChatItem.findOneAndUpdate(
{
appId,
chatId,
dataId
},
{
$unset: {
...(userBadFeedback === undefined && { userBadFeedback: '' }),
...(userGoodFeedback === undefined && { userGoodFeedback: '' })
},
$set: {
...(userBadFeedback !== undefined && { userBadFeedback }),
...(userGoodFeedback !== undefined && { userGoodFeedback })
}
}
);
}
export default NextAPI(handler);

View File

@@ -7,6 +7,8 @@ import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { GetHistoriesProps } from '@/global/core/chat/api';
import { addMonths } from 'date-fns';
export type getHistoriesQuery = {};
export type getHistoriesBody = PaginationProps<GetHistoriesProps>;
@@ -17,8 +19,7 @@ async function handler(
req: ApiRequestProps<getHistoriesBody, getHistoriesQuery>,
res: ApiResponseType<any>
): Promise<PaginationResponse<getHistoriesResponse>> {
const { appId, shareId, outLinkUid, teamId, teamToken, offset, pageSize, source } =
req.body as getHistoriesBody;
const { appId, shareId, outLinkUid, teamId, teamToken, offset, pageSize, source } = req.body;
const match = await (async () => {
if (shareId && outLinkUid) {
@@ -28,7 +29,7 @@ async function handler(
shareId,
outLinkUid: uid,
updateTime: {
$gte: new Date(new Date().setDate(new Date().getDate() - 30))
$gte: addMonths(new Date(), -1)
}
};
}
@@ -62,7 +63,8 @@ async function handler(
await MongoChat.find(match, 'chatId title top customTitle appId updateTime')
.sort({ top: -1, updateTime: -1 })
.skip(offset)
.limit(pageSize),
.limit(pageSize)
.lean(),
MongoChat.countDocuments(match)
]);

View File

@@ -1,7 +1,6 @@
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { GetChatRecordsProps } from '@/global/core/chat/api';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { transformPreviewHistories } from '@/global/core/chat/utils';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
@@ -11,7 +10,6 @@ import { MongoApp } from '@fastgpt/service/core/app/schema';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
import { authOutLink } from '@/service/support/permission/auth/outLink';
import { GetChatTypeEnum } from '@/global/core/chat/constants';
import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type';
import { ChatItemType } from '@fastgpt/global/core/chat/type';
@@ -42,14 +40,13 @@ async function handler(
};
}
const [app] = await Promise.all([
const [app, { responseDetail, showNodeStatus, authType }] = await Promise.all([
MongoApp.findById(appId, 'type').lean(),
authChatCrud({
req,
authToken: true,
authApiKey: true,
...req.body,
per: ReadPermissionVal
...req.body
})
]);
@@ -57,21 +54,14 @@ async function handler(
return Promise.reject(AppErrEnum.unExist);
}
const isPlugin = app.type === AppTypeEnum.plugin;
const shareChat = await (async () => {
if (type === GetChatTypeEnum.outLink)
return await authOutLink({
shareId: req.body.shareId,
outLinkUid: req.body.outLinkUid
}).then((result) => result.shareChat);
})();
const isOutLink = authType === GetChatTypeEnum.outLink;
const fieldMap = {
[GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback time ${
[GetChatTypeEnum.normal]: `dataId obj value adminFeedback userBadFeedback userGoodFeedback time hideInUI ${
DispatchNodeResponseKeyEnum.nodeResponse
} ${loadCustomFeedbacks ? 'customFeedbacks' : ''}`,
[GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time ${DispatchNodeResponseKeyEnum.nodeResponse}`,
[GetChatTypeEnum.team]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time ${DispatchNodeResponseKeyEnum.nodeResponse}`
[GetChatTypeEnum.outLink]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time hideInUI ${DispatchNodeResponseKeyEnum.nodeResponse}`,
[GetChatTypeEnum.team]: `dataId obj value userGoodFeedback userBadFeedback adminFeedback time hideInUI ${DispatchNodeResponseKeyEnum.nodeResponse}`
};
const { total, histories } = await getChatItems({
@@ -82,10 +72,8 @@ async function handler(
limit: pageSize
});
const responseDetail = !shareChat || shareChat.responseDetail;
// Remove important information
if (shareChat && app.type !== AppTypeEnum.plugin) {
if (isOutLink && app.type !== AppTypeEnum.plugin) {
histories.forEach((item) => {
if (item.obj === ChatRoleEnum.AI) {
item.responseData = filterPublicNodeResponseData({
@@ -93,7 +81,7 @@ async function handler(
responseDetail
});
if (shareChat.showNodeStatus === false) {
if (showNodeStatus === false) {
item.value = item.value.filter((v) => v.type !== ChatItemValueTypeEnum.tool);
}
}

View File

@@ -1,17 +1,11 @@
import { authChatCrud } from '@/service/support/permission/auth/chat';
import {
ManagePermissionVal,
ReadPermissionVal
} from '@fastgpt/global/support/permission/constant';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
export type getResDataQuery = OutLinkChatAuthProps & {
chatId?: string;
@@ -32,29 +26,13 @@ async function handler(
return {};
}
// 1. Un login api: share chat, team chat
// 2. Login api: account chat, chat log
const authData = await (() => {
try {
return authChatCrud({
req,
authToken: true,
authApiKey: true,
...req.query,
per: ReadPermissionVal
});
} catch (error) {
return authApp({
req,
authToken: true,
authApiKey: true,
appId,
per: ManagePermissionVal
});
}
})();
const [chatData] = await Promise.all([
const [{ responseDetail }, chatData] = await Promise.all([
authChatCrud({
req,
authToken: true,
authApiKey: true,
...req.query
}),
MongoChatItem.findOne(
{
appId,
@@ -62,8 +40,7 @@ async function handler(
dataId
},
'obj responseData'
).lean(),
shareId ? MongoOutLink.findOne({ shareId }).lean() : Promise.resolve(null)
).lean()
]);
if (chatData?.obj !== ChatRoleEnum.AI) {
@@ -73,8 +50,7 @@ async function handler(
const flowResponses = chatData.responseData ?? {};
return req.query.shareId
? filterPublicNodeResponseData({
// @ts-ignore
responseDetail: authData.responseDetail,
responseDetail,
flowResponses: chatData.responseData
})
: flowResponses;

View File

@@ -3,7 +3,7 @@ import { MongoChatInputGuide } from '@fastgpt/service/core/chat/inputGuide/schem
import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
@@ -21,7 +21,7 @@ async function handler(
const { appId, searchKey } = req.body;
// tmp auth
const { teamId } = await authChatCert({ req, authToken: true });
const { teamId } = await authChatCrud({ req, authToken: true, ...req.body });
const app = await MongoApp.findOne({ _id: appId, teamId });
if (!app) {
return Promise.reject(AppErrEnum.unAuthApp);

View File

@@ -5,21 +5,19 @@ import { authChatCrud } from '@/service/support/permission/auth/chat';
import type { DeleteChatItemProps } from '@/global/core/chat/api.d';
import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextApiResponse) {
const { appId, chatId, contentId } = req.query;
if (!contentId || !chatId) {
return jsonRes(res);
return Promise.reject('contentId or chatId is empty');
}
await authChatCrud({
req,
authToken: true,
authApiKey: true,
...req.query,
per: WritePermissionVal
...req.query
});
await MongoChatItem.deleteOne({
@@ -28,7 +26,7 @@ async function handler(req: ApiRequestProps<{}, DeleteChatItemProps>, res: NextA
dataId: contentId
});
jsonRes(res);
return;
}
export default NextAPI(handler);

View File

@@ -1,33 +1,35 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { GetChatSpeechProps } from '@/global/core/chat/api.d';
import { text2Speech } from '@fastgpt/service/core/ai/audio/speech';
import { pushAudioSpeechUsage } from '@/service/support/wallet/usage/push';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import { authType2UsageSource } from '@/service/support/wallet/usage/utils';
import { getAudioSpeechModel } from '@fastgpt/service/core/ai/model';
import { MongoTTSBuffer } from '@fastgpt/service/common/buffer/tts/schema';
import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps } from '@fastgpt/service/type/next';
/*
1. get tts from chatItem store
2. get tts from ai
4. push bill
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
async function handler(req: ApiRequestProps<GetChatSpeechProps>, res: NextApiResponse) {
try {
await connectToDatabase();
const { ttsConfig, input } = req.body as GetChatSpeechProps;
const { ttsConfig, input } = req.body;
if (!ttsConfig.model || !ttsConfig.voice) {
throw new Error('model or voice not found');
}
const { teamId, tmbId, authType } = await authChatCert({
const { teamId, tmbId, authType } = await authChatCrud({
req,
authToken: true,
authApiKey: true
authApiKey: true,
...req.body
});
const ttsModel = getAudioSpeechModel(ttsConfig.model);
@@ -90,3 +92,5 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
}
export default NextAPI(handler);

View File

@@ -16,11 +16,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
let { chatId, shareId, outLinkUid } = req.query as InitOutLinkChatProps;
// auth link permission
const { shareChat, uid, appId } = await authOutLink({ shareId, outLinkUid });
const { outLinkConfig, uid, appId } = await authOutLink({ shareId, outLinkUid });
// auth app permission
const [tmb, chat, app] = await Promise.all([
MongoTeamMember.findById(shareChat.tmbId, '_id userId').populate('userId', 'avatar').lean(),
MongoTeamMember.findById(outLinkConfig.tmbId, '_id userId').populate('userId', 'avatar').lean(),
MongoChat.findOne({ appId, chatId, shareId }).lean(),
MongoApp.findById(appId).lean()
]);
@@ -35,7 +35,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
const { nodes, chatConfig } = await getAppLatestVersion(app._id, app);
// pick share response field
jsonRes<InitChatResponse>(res, {
data: {
@@ -66,9 +65,3 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
export default NextAPI(handler);
export const config = {
api: {
responseLimit: '10mb'
}
};

View File

@@ -1,4 +1,4 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { getGuideModule, getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
import { getChatModelNameListByModules } from '@/service/core/app/workflow';
@@ -12,12 +12,13 @@ import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps } from '@fastgpt/service/type/next';
async function handler(req: NextApiRequest, res: NextApiResponse) {
let { teamId, appId, chatId, teamToken } = req.query as InitTeamChatProps;
async function handler(req: ApiRequestProps<InitTeamChatProps>, res: NextApiResponse) {
let { teamId, appId, chatId, teamToken } = req.query;
if (!teamId || !appId || !teamToken) {
throw new Error('teamId, appId, teamToken are required');
return Promise.reject('teamId, appId, teamToken are required');
}
const { uid } = await authTeamSpaceToken({
@@ -32,7 +33,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
]);
if (!app) {
throw new Error(AppErrEnum.unExist);
return Promise.reject(AppErrEnum.unExist);
}
// auth chat permission
@@ -43,8 +44,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// get app and history
const { nodes, chatConfig } = await getAppLatestVersion(app._id, app);
// pick share response field
jsonRes<InitChatResponse>(res, {
data: {
chatId,
@@ -74,9 +73,3 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
export default NextAPI(handler);
export const config = {
api: {
responseLimit: '10mb'
}
};

View File

@@ -77,7 +77,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): CreateCo
// 2. upload file
const fileId = await uploadFile({
teamId,
tmbId,
uid: tmbId,
bucketName,
path: file.path,
filename: file.originalname,

View File

@@ -5,40 +5,142 @@ import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant
import { createFileToken } from '@fastgpt/service/support/permission/controller';
import { BucketNameEnum, ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { ShareChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
import { MongoChatItem } from '@fastgpt/service/core/chat/chatItemSchema';
import { AIChatItemType, ChatHistoryItemResType } from '@fastgpt/global/core/chat/type';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller';
export type readCollectionSourceQuery = {};
export type readCollectionSourceBody = {
collectionId: string;
} & ShareChatAuthProps;
appId?: string;
chatId?: string;
chatItemId?: string;
} & OutLinkChatAuthProps;
export type readCollectionSourceResponse = {
type: 'url';
value: string;
};
const authCollectionInChat = async ({
collectionId,
appId,
chatId,
chatItemId
}: {
collectionId: string;
appId: string;
chatId: string;
chatItemId: string;
}) => {
try {
const chatItem = (await MongoChatItem.findOne(
{
appId,
chatId,
dataId: chatItemId
},
'responseData'
).lean()) as AIChatItemType;
if (!chatItem) return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
// 找 responseData 里,是否有该文档 id
const responseData = chatItem.responseData || [];
const flatResData: ChatHistoryItemResType[] =
responseData
?.map((item) => {
return [
item,
...(item.pluginDetail || []),
...(item.toolDetail || []),
...(item.loopDetail || [])
];
})
.flat() || [];
if (
flatResData.some((item) => {
if (item.quoteList) {
return item.quoteList.some((quote) => quote.collectionId === collectionId);
}
return false;
})
) {
return true;
}
} catch (error) {}
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
};
async function handler(
req: ApiRequestProps<readCollectionSourceBody, readCollectionSourceQuery>
): Promise<readCollectionSourceResponse> {
const { collection, teamId, tmbId } = await authDatasetCollection({
req,
authToken: true,
authApiKey: true,
collectionId: req.body.collectionId,
per: ReadPermissionVal
});
const { collectionId, appId, chatId, chatItemId, shareId, outLinkUid, teamId, teamToken } =
req.body;
const {
collection,
teamId: userTeamId,
tmbId: uid,
authType
} = await (async () => {
if (!appId || !chatId || !chatItemId) {
return authDatasetCollection({
req,
authToken: true,
authApiKey: true,
collectionId: req.body.collectionId,
per: ReadPermissionVal
});
}
/*
1. auth chat read permission
2. auth collection quote in chat
3. auth outlink open show quote
*/
const [authRes, collection] = await Promise.all([
authChatCrud({
req,
authToken: true,
appId,
chatId,
shareId,
outLinkUid,
teamId,
teamToken
}),
getCollectionWithDataset(collectionId),
authCollectionInChat({ appId, chatId, chatItemId, collectionId })
]);
if (!authRes.showRawSource) {
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
}
return {
...authRes,
collection
};
})();
const sourceUrl = await (async () => {
if (collection.type === DatasetCollectionTypeEnum.file && collection.fileId) {
const token = await createFileToken({
bucketName: BucketNameEnum.dataset,
teamId,
tmbId,
fileId: collection.fileId
teamId: userTeamId,
uid,
fileId: collection.fileId,
customExpireMinutes: authType === 'outLink' ? 5 : undefined
});
return `${ReadFileBaseUrl}?token=${token}`;
return `${ReadFileBaseUrl}/${collection.name}?token=${token}`;
}
if (collection.type === DatasetCollectionTypeEnum.link && collection.rawLink) {
return collection.rawLink;

View File

@@ -4,7 +4,7 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { NextAPI } from '@/service/middleware/entry';
import { ApiRequestProps } from '@fastgpt/service/type/next';
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
import { authFile } from '@fastgpt/service/support/permission/auth/file';
import { authCollectionFile } from '@fastgpt/service/support/permission/auth/file';
export type PostPreviewFilesChunksProps = {
type: DatasetSourceReadTypeEnum;
@@ -35,7 +35,7 @@ async function handler(
const { teamId } = await (async () => {
if (type === DatasetSourceReadTypeEnum.fileLocal) {
return authFile({
return authCollectionFile({
req,
authToken: true,
authApiKey: true,

View File

@@ -1,7 +1,7 @@
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import type { OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import { authOutLinkCrud } from '@fastgpt/service/support/permission/publish/authLink';
import { OwnerPermissionVal } from '@fastgpt/global/support/permission/constant';
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
@@ -30,7 +30,7 @@ async function handler(
return Promise.reject(CommonErrEnum.missingParams);
}
await authOutLinkCrud({ req, outLinkId: _id, authToken: true, per: OwnerPermissionVal });
await authOutLinkCrud({ req, outLinkId: _id, authToken: true, per: ManagePermissionVal });
await MongoOutLink.findByIdAndUpdate(_id, {
name,

View File

@@ -4,7 +4,7 @@ import { getUploadModel } from '@fastgpt/service/common/file/multer';
import { removeFilesByPaths } from '@fastgpt/service/common/file/utils';
import fs from 'fs';
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { NextAPI } from '@/service/middleware/entry';
import { aiTranscriptions } from '@fastgpt/service/core/ai/audio/transcriptions';
@@ -27,6 +27,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
}
>(req, res);
req.body.appId = appId;
req.body.shareId = shareId;
req.body.outLinkUid = outLinkUid;
req.body.teamId = spaceTeamId;
@@ -43,7 +44,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
}
// auth role
const { teamId, tmbId } = await authChatCert({ req, authToken: true });
const { teamId, tmbId } = await authChatCrud({ req, authToken: true, ...req.body });
// auth app
// const app = await MongoApp.findById(appId, 'modules').lean();

View File

@@ -86,7 +86,7 @@ type AuthResponseType = {
showNodeStatus?: boolean;
authType: `${AuthUserTypeEnum}`;
apikey?: string;
canWrite: boolean;
responseAllData: boolean;
outLinkUserId?: string;
sourceName?: string;
};
@@ -160,7 +160,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
authType,
sourceName,
apikey,
canWrite,
responseAllData,
outLinkUserId = customUid,
showNodeStatus
} = await (async () => {
@@ -328,12 +328,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
await updateInteractiveChat({
chatId,
appId: app._id,
teamId,
tmbId: tmbId,
userInteractiveVal,
aiResponse,
newVariables,
newTitle
newVariables
});
} else {
await saveChat({
@@ -361,7 +358,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`);
/* select fe response field */
const feResponseData = canWrite
const feResponseData = responseAllData
? flowResponses
: filterPublicNodeResponseData({ flowResponses, responseDetail });
@@ -482,10 +479,10 @@ const authShareChat = async ({
tmbId,
user,
app,
responseDetail,
apikey: '',
authType,
canWrite: false,
responseAllData: false,
responseDetail,
outLinkUserId: uid,
showNodeStatus
};
@@ -525,10 +522,10 @@ const authTeamSpaceChat = async ({
tmbId: app.tmbId,
user,
app,
responseDetail: true,
authType: AuthUserTypeEnum.outLink,
apikey: '',
canWrite: false,
responseAllData: false,
responseDetail: true,
outLinkUserId: uid
};
};
@@ -610,11 +607,11 @@ const authHeaderRequest = async ({
tmbId,
user,
app,
responseDetail: true,
apikey,
authType,
sourceName,
canWrite: true
responseAllData: true,
responseDetail: true
};
};

View File

@@ -1,12 +1,10 @@
import React, { useMemo } from 'react';
import { Flex, Box, useTheme } from '@chakra-ui/react';
import { Flex, Box } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
import { getInitChatInfo } from '@/web/core/chat/api';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
import dynamic from 'next/dynamic';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
@@ -14,53 +12,52 @@ import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/Plugin
import CloseIcon from '@fastgpt/web/components/common/Icon/close';
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useQuery } from '@tanstack/react-query';
import { PcHeader } from '@/pages/chat/components/ChatHeader';
import { GetChatTypeEnum } from '@/global/core/chat/constants';
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider, {
ChatRecordContext
} from '@/web/core/chat/context/chatRecordContext';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useContextSelector } from 'use-context-selector';
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
const DetailLogsModal = ({
appId,
chatId,
onClose
}: {
type Props = {
appId: string;
chatId: string;
onClose: () => void;
}) => {
};
const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
const { t } = useTranslation();
const { isPc } = useSystem();
const theme = useTheme();
const params = useMemo(() => {
return {
chatId,
appId,
loadCustomFeedbacks: true,
type: GetChatTypeEnum.normal
};
}, [appId, chatId]);
const {
ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
chatRecords,
ScrollData,
setChatRecords,
totalRecordsCount
} = useChat(params);
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
const { data: chat, isFetching } = useQuery(
['getChatDetail', chatId],
() => getInitChatInfo({ appId, chatId, loadCustomFeedbacks: true }),
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
const { data: chat, loading: isFetching } = useRequest2(
async () => {
const res = await getInitChatInfo({ appId, chatId, loadCustomFeedbacks: true });
res.userAvatar = HUMAN_ICON;
setChatBoxData(res);
resetVariables({
variables: res.variables
});
return res;
},
{
onSuccess(res) {
resetVariables({
variables: res.variables
});
manual: false,
refreshDeps: [chatId],
onError(e) {
onClose();
}
}
);
@@ -68,12 +65,11 @@ const DetailLogsModal = ({
const title = chat?.title;
const chatModels = chat?.app?.chatModels;
const isPlugin = chat?.app.type === AppTypeEnum.plugin;
const loading = isFetching;
return (
<>
<MyBox
isLoading={loading}
isLoading={isFetching}
display={'flex'}
flexDirection={'column'}
zIndex={3}
@@ -124,7 +120,7 @@ const DetailLogsModal = ({
alignItems={'center'}
px={[3, 5]}
h={['46px', '60px']}
borderBottom={theme.borders.base}
borderBottom={'base'}
borderBottomColor={'gray.200'}
color={'myGray.900'}
>
@@ -154,32 +150,15 @@ const DetailLogsModal = ({
<Box pt={2} flex={'1 0 0'}>
{isPlugin ? (
<Box px={5} pt={2} h={'100%'}>
<PluginRunBox
chatConfig={chat?.app?.chatConfig}
pluginInputs={chat?.app.pluginInputs}
variablesForm={variablesForm}
histories={chatRecords}
setHistories={setChatRecords}
appId={chat.appId}
tab={pluginRunTab}
setTab={setPluginRunTab}
/>
<PluginRunBox appId={appId} chatId={chatId} />
</Box>
) : (
<ChatBox
ScrollData={ScrollData}
ref={ChatBoxRef}
chatHistories={chatRecords}
setChatHistories={setChatRecords}
variablesForm={variablesForm}
appAvatar={chat?.app.avatar}
userAvatar={HUMAN_ICON}
appId={appId}
chatId={chatId}
feedbackType={'admin'}
showMarkIcon
showVoiceIcon={false}
chatConfig={chat?.app?.chatConfig}
appId={appId}
chatId={chatId}
chatType="log"
showRawSource
showNodeStatus
@@ -191,4 +170,24 @@ const DetailLogsModal = ({
</>
);
};
export default DetailLogsModal;
const Render = (props: Props) => {
const { appId, chatId } = props;
const params = useMemo(() => {
return {
chatId,
appId,
loadCustomFeedbacks: true,
type: GetChatTypeEnum.normal
};
}, [appId, chatId]);
return (
<ChatItemContextProvider>
<ChatRecordContextProvider params={params}>
<DetailLogsModal {...props} />
</ChatRecordContextProvider>
</ChatItemContextProvider>
);
};
export default Render;

View File

@@ -11,6 +11,7 @@ import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { fileToBase64 } from '@/web/common/file/utils';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
import { subRoute } from '@fastgpt/web/common/system/utils';
enum UsingWayEnum {
link = 'link',
@@ -74,7 +75,7 @@ const SelectUsingWayModal = ({ share, onClose }: { share: OutLinkSchema; onClose
});
const baseUrl = feConfigs?.customSharePageDomain || location?.origin;
const linkUrl = `${baseUrl}/chat/share?shareId=${share?.shareId}${
const linkUrl = `${baseUrl}${subRoute ? `${subRoute}/` : '/'}chat/share?shareId=${share?.shareId}${
getValues('showHistory') ? '' : '&showHistory=0'
}`;

View File

@@ -423,7 +423,7 @@ function EditLinkModal({
</Flex>
<Switch {...register('responseDetail')} isChecked={responseDetail} />
</Flex>
{/* <Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
<Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
<Flex alignItems={'center'}>
<FormLabel>{t('common:support.outlink.share.show_complete_quote')}</FormLabel>
<QuestionTip
@@ -431,8 +431,17 @@ function EditLinkModal({
label={t('common:support.outlink.share.show_complete_quote_tips' || '')}
></QuestionTip>
</Flex>
<Switch {...register('showRawSource')} isChecked={showRawSource} />
</Flex> */}
<Switch
{...register('showRawSource', {
onChange(e) {
if (e.target.checked) {
setValue('responseDetail', true);
}
}
})}
isChecked={showRawSource}
/>
</Flex>
</Box>
</ModalBody>

View File

@@ -10,7 +10,7 @@ import {
ModalFooter
} from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { AppSchema } from '@fastgpt/global/core/app/type.d';
import { AppSchema, AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
import { useTranslation } from 'next-i18next';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -19,21 +19,25 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import { AppContext } from '@/pages/app/detail/components/context';
import { useContextSelector } from 'use-context-selector';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { useI18n } from '@/web/context/I18n';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { postTransition2Workflow } from '@/web/core/app/api/app';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { form2AppWorkflow } from '@/web/core/app/utils';
import { SimpleAppSnapshotType } from './useSnapshots';
const AppCard = () => {
const AppCard = ({
appForm,
setPast
}: {
appForm: AppSimpleEditFormType;
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
}) => {
const router = useRouter();
const { t } = useTranslation();
const { appT } = useI18n();
const { appDetail, setAppDetail, onOpenInfoEdit, onDelApp } = useContextSelector(
AppContext,
(v) => v
);
const onSaveApp = useContextSelector(AppContext, (v) => v.onSaveApp);
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
const onOpenInfoEdit = useContextSelector(AppContext, (v) => v.onOpenInfoEdit);
const onDelApp = useContextSelector(AppContext, (v) => v.onDelApp);
const appId = appDetail._id;
const { feConfigs } = useSystemStore();
@@ -42,7 +46,18 @@ const AppCard = () => {
// transition to workflow
const [transitionCreateNew, setTransitionCreateNew] = useState<boolean>();
const { runAsync: onTransition, loading: transiting } = useRequest2(
() => postTransition2Workflow({ appId, createNew: transitionCreateNew }),
async () => {
const { nodes, edges } = form2AppWorkflow(appForm, t);
await onSaveApp({
nodes,
edges,
chatConfig: appForm.chatConfig,
isPublish: false,
versionName: t('app:transition_to_workflow')
});
return postTransition2Workflow({ appId, createNew: transitionCreateNew });
},
{
onSuccess: ({ id }) => {
if (id) {
@@ -52,10 +67,8 @@ const AppCard = () => {
}
});
} else {
setAppDetail((state) => ({
...state,
type: AppTypeEnum.workflow
}));
setPast([]);
router.reload();
}
},
successToast: t('common:common.Success')
@@ -118,7 +131,7 @@ const AppCard = () => {
children: [
{
icon: 'core/app/type/workflow',
label: appT('transition_to_workflow'),
label: t('app:transition_to_workflow'),
onClick: () => setTransitionCreateNew(true)
},
...(appDetail.permission.hasWritePer && feConfigs?.show_team_chat
@@ -159,15 +172,15 @@ const AppCard = () => {
</Box>
{TeamTagsSet && <TagsEditModal onClose={() => setTeamTagsSet(undefined)} />}
{transitionCreateNew !== undefined && (
<MyModal isOpen title={appT('transition_to_workflow')} iconSrc="core/app/type/workflow">
<MyModal isOpen title={t('app:transition_to_workflow')} iconSrc="core/app/type/workflow">
<ModalBody>
<Box mb={3}>{appT('transition_to_workflow_create_new_tip')}</Box>
<Box mb={3}>{t('app:transition_to_workflow_create_new_tip')}</Box>
<HStack cursor={'pointer'} onClick={() => setTransitionCreateNew((state) => !state)}>
<Checkbox
isChecked={transitionCreateNew}
icon={<MyIcon name={'common/check'} w={'12px'} />}
/>
<Box>{appT('transition_to_workflow_create_new_placeholder')}</Box>
<Box>{t('app:transition_to_workflow_create_new_placeholder')}</Box>
</HStack>
</ModalBody>
<ModalFooter>

View File

@@ -1,6 +1,6 @@
import { Box, Flex, IconButton } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import React, { useEffect } from 'react';
import React, { useEffect, useMemo } from 'react';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -12,8 +12,13 @@ import { useContextSelector } from 'use-context-selector';
import { AppContext } from '../context';
import { useChatTest } from '../useChatTest';
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
import ChatItemContextProvider from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider from '@/web/core/chat/context/chatRecordContext';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import MyBox from '@fastgpt/web/components/common/MyBox';
const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
type Props = { appForm: AppSimpleEditFormType };
const ChatTest = ({ appForm }: Props) => {
const { t } = useTranslation();
const { appT } = useI18n();
@@ -32,13 +37,21 @@ const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
setWorkflowData({ nodes, edges });
}, [appForm, setWorkflowData, allDatasets, t]);
const { restartChat, ChatContainer } = useChatTest({
const { ChatContainer, restartChat, loading } = useChatTest({
...workflowData,
chatConfig: appForm.chatConfig
chatConfig: appForm.chatConfig,
isReady: true
});
return (
<Flex position={'relative'} flexDirection={'column'} h={'100%'} py={4}>
<MyBox
isLoading={loading}
display={'flex'}
position={'relative'}
flexDirection={'column'}
h={'100%'}
py={4}
>
<Flex px={[2, 5]}>
<Box fontSize={['md', 'lg']} fontWeight={'bold'} flex={1} color={'myGray.900'}>
{appT('chat_debug')}
@@ -61,8 +74,29 @@ const ChatTest = ({ appForm }: { appForm: AppSimpleEditFormType }) => {
<Box flex={1}>
<ChatContainer />
</Box>
</Flex>
</MyBox>
);
};
export default React.memo(ChatTest);
const Render = ({ appForm }: Props) => {
const { chatId } = useChatStore();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const chatRecordProviderParams = useMemo(
() => ({
chatId: chatId,
appId: appDetail._id
}),
[appDetail._id, chatId]
);
return (
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<ChatTest appForm={appForm} />
</ChatRecordContextProvider>
</ChatItemContextProvider>
);
};
export default React.memo(Render);

View File

@@ -132,7 +132,7 @@ const Edit = ({
flex={'1'}
>
<Box {...cardStyles} boxShadow={'2'}>
<AppCard />
<AppCard appForm={appForm} setPast={setPast} />
</Box>
<Box mt={4} {...cardStyles} boxShadow={'3.5'}>

View File

@@ -87,7 +87,6 @@ const Header = ({
nodes,
edges,
chatConfig: appForm.chatConfig,
type: AppTypeEnum.simple,
isPublish,
versionName
});

View File

@@ -37,7 +37,8 @@ export const compareSimpleAppSnapshot = (
scheduledTriggerConfig: appForm1.chatConfig?.scheduledTriggerConfig || undefined,
chatInputGuide: appForm1.chatConfig?.chatInputGuide || undefined,
fileSelectConfig: appForm1.chatConfig?.fileSelectConfig || undefined,
instruction: appForm1.chatConfig?.instruction || ''
instruction: appForm1.chatConfig?.instruction || '',
autoExecute: appForm1.chatConfig?.autoExecute || undefined
},
{
welcomeText: appForm2.chatConfig?.welcomeText || '',
@@ -48,7 +49,8 @@ export const compareSimpleAppSnapshot = (
scheduledTriggerConfig: appForm2.chatConfig?.scheduledTriggerConfig || undefined,
chatInputGuide: appForm2.chatConfig?.chatInputGuide || undefined,
fileSelectConfig: appForm2.chatConfig?.fileSelectConfig || undefined,
instruction: appForm2.chatConfig?.instruction || ''
instruction: appForm2.chatConfig?.instruction || '',
autoExecute: appForm2.chatConfig?.autoExecute || undefined
}
)
) {

View File

@@ -147,7 +147,6 @@ const AppCard = ({
appDetail.name,
appDetail.permission.hasWritePer,
appDetail.permission.isOwner,
currentTab,
feConfigs?.show_team_chat,
onDelApp,
onOpenImport,
@@ -252,10 +251,6 @@ function ExportPopover({
trigger={'hover'}
w={'8.6rem'}
Trigger={
// <Flex align={'center'} w={'100%'} py={2} px={3}>
// <Avatar src={'export'} borderRadius={'sm'} w={'1rem'} mr={3} />
// {t('app:export_configs')}
// </Flex>
<MyBox display={'flex'} size={'md'} rounded={'4px'} cursor={'pointer'}>
<MyIcon name={'export'} w={'16px'} mr={2} />
<Box fontSize={'sm'}>{t('app:export_configs')}</Box>

View File

@@ -1,5 +1,5 @@
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
import React from 'react';
import React, { useMemo } from 'react';
import { SmallCloseIcon } from '@chakra-ui/icons';
import { Box, Flex, IconButton } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
@@ -14,27 +14,34 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants';
import CloseIcon from '@fastgpt/web/components/common/Icon/close';
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider, {
ChatRecordContext
} from '@/web/core/chat/context/chatRecordContext';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import MyBox from '@fastgpt/web/components/common/MyBox';
const ChatTest = ({
isOpen,
nodes = [],
edges = [],
onClose
}: {
type Props = {
isOpen: boolean;
nodes?: StoreNodeItemType[];
edges?: StoreEdgeItemType[];
onClose: () => void;
}) => {
};
const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
const { t } = useTranslation();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const isPlugin = appDetail.type === AppTypeEnum.plugin;
const { restartChat, ChatContainer, pluginRunTab, setPluginRunTab, chatRecords } = useChatTest({
const { restartChat, ChatContainer, loading } = useChatTest({
nodes,
edges,
chatConfig: appDetail.chatConfig
chatConfig: appDetail.chatConfig,
isReady: isOpen
});
const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
return (
<>
@@ -48,8 +55,10 @@ const ChatTest = ({
right={0}
onClick={onClose}
/>
<Flex
<MyBox
isLoading={loading}
zIndex={300}
display={'flex'}
flexDirection={'column'}
position={'absolute'}
top={5}
@@ -131,9 +140,30 @@ const ChatTest = ({
<Box flex={'1 0 0'} overflow={'auto'}>
<ChatContainer />
</Box>
</Flex>
</MyBox>
</>
);
};
export default React.memo(ChatTest);
const Render = (Props: Props) => {
const { chatId } = useChatStore();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const chatRecordProviderParams = useMemo(
() => ({
chatId: chatId,
appId: appDetail._id
}),
[appDetail._id, chatId]
);
return (
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<ChatTest {...Props} />
</ChatRecordContextProvider>
</ChatItemContextProvider>
);
};
export default React.memo(Render);

View File

@@ -240,7 +240,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
position={'absolute'}
top={'10px'}
left={0}
pt={'20px'}
pt={5}
pb={4}
h={isOpen ? 'calc(100% - 20px)' : '0'}
w={isOpen ? ['100%', `${sliderWidth}px`] : '0'}
@@ -254,7 +254,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
{/* Header */}
<Box px={'5'} mb={3} whiteSpace={'nowrap'} overflow={'hidden'}>
{/* Tabs */}
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
<Flex flex={'1 0 0'} alignItems={'center'} gap={2}>
<Box flex={'1 0 0'}>
<FillRowTabs
list={[
@@ -288,8 +288,14 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
{/* close icon */}
<IconButton
size={'sm'}
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.700'} />}
borderColor={'myGray.300'}
icon={<MyIcon name={'common/backFill'} w={'14px'} color={'myGray.600'} />}
bg={'myGray.100'}
_hover={{
bg: 'myGray.200',
'& svg': {
color: 'primary.600'
}
}}
variant={'grayBase'}
aria-label={''}
onClick={onClose}

View File

@@ -177,20 +177,15 @@ const ButtonEdge = (props: EdgeProps) => {
position={'absolute'}
transform={arrowTransform}
pointerEvents={'all'}
w={highlightEdge ? '14px' : '10px'}
h={highlightEdge ? '14px' : '10px'}
w={highlightEdge ? '12px' : '10px'}
h={highlightEdge ? '12px' : '10px'}
zIndex={highlightEdge ? defaultZIndex + 1000 : defaultZIndex}
>
<MyIcon
name={'core/workflow/edgeArrow'}
name={highlightEdge ? 'core/workflow/edgeArrowBold' : 'core/workflow/edgeArrow'}
w={'100%'}
color={edgeColor}
{...(highlightEdge
? {
fontWeight: 'bold'
}
: {})}
></MyIcon>
/>
</Flex>
)}
</Box>
@@ -223,7 +218,7 @@ const ButtonEdge = (props: EdgeProps) => {
...style,
...(highlightEdge
? {
strokeWidth: 5
strokeWidth: 4
}
: { strokeWidth: 3, zIndex: 2 })
};

View File

@@ -1,7 +1,7 @@
import { Box, Flex } from '@chakra-ui/react';
import React from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'react-i18next';
import { useTranslation } from 'next-i18next';
import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils';
import { CommentNode } from '@fastgpt/global/core/workflow/template/system/comment';
import { useContextSelector } from 'use-context-selector';

View File

@@ -333,7 +333,7 @@ export const useDebug = () => {
<Flex alignItems={'center'} mb={1}>
<Box position={'relative'}>
{required && (
<Box position={'absolute'} right={-2} top={'-1px'} color={'red.600'}>
<Box position={'absolute'} left={'-8px'} top={'-2px'} color={'red.600'}>
*
</Box>
)}

View File

@@ -36,8 +36,8 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
return {
[NodeInputKeyEnum.code]: (item: FlowNodeInputItemType) => {
return (
<Box>
<Flex mb={1} alignItems={'flex-end'}>
<Box mt={-3}>
<Flex mb={2} alignItems={'flex-end'}>
<Box flex={'1'}>{'Javascript ' + workflowT('Code')}</Box>
<Box
cursor={'pointer'}
@@ -88,7 +88,7 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
</>
)}
<Container>
<IOTitle text={t('common:common.Input')} />
<IOTitle text={t('common:common.Input')} mb={-1} />
<RenderInput
nodeId={nodeId}
flowInputList={commonInputs}

View File

@@ -7,7 +7,7 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'react-i18next';
import { useTranslation } from 'next-i18next';
const NodeComment = ({ data }: NodeProps<FlowNodeItemType>) => {
const { nodeId, inputs } = data;

View File

@@ -34,6 +34,7 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
import IOTitle from '../../components/IOTitle';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
const { inputs, outputs, nodeId } = data;
@@ -53,14 +54,15 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
}: Omit<FlowNodeInputItemType, 'value'> & {
value?: ContextExtractAgentItemType[];
}) => (
<Box>
<Box mt={-2}>
<Flex alignItems={'center'}>
<Box flex={'1 0 0'} fontWeight={'medium'} color={'myGray.600'}>
<Box flex={'1 0 0'} fontSize={'sm'} fontWeight={'medium'} color={'myGray.600'}>
{t('common:core.module.extract.Target field')}
</Box>
<Button
size={'sm'}
variant={'ghost'}
variant={'grayGhost'}
px={2}
color={'myGray.600'}
leftIcon={<AddIcon fontSize={'10px'} />}
onClick={() => setEditExtractField(defaultField)}
@@ -68,48 +70,47 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
{t('common:core.module.extract.Add field')}
</Button>
</Flex>
<Box
mt={2}
borderRadius={'md'}
overflow={'hidden'}
borderWidth={'1px'}
borderBottom="none"
>
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th borderRadius={'none !important'}>{t('common:item_name')}</Th>
<Th>{t('common:item_description')}</Th>
<Th>{t('common:required')}</Th>
<Th borderRadius={'none !important'}></Th>
</Tr>
</Thead>
<Tbody>
{extractKeys.map((item, index) => (
<Tr
key={index}
position={'relative'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
>
<Td>{item.key}</Td>
<Td>{item.desc}</Td>
<Td>{item.required ? '✔' : ''}</Td>
<Td whiteSpace={'nowrap'}>
<MyIcon
mr={3}
name={'common/settingLight'}
w={'16px'}
cursor={'pointer'}
<TableContainer borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} mt={2}>
<Table variant={'workflow'}>
<Thead>
<Tr>
<Th>{t('common:item_name')}</Th>
<Th>{t('common:item_description')}</Th>
<Th>{t('common:required')}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{extractKeys.map((item, index) => (
<Tr key={index}>
<Td>
<Flex alignItems={'center'}>
<MyIcon name={'checkCircle'} w={'14px'} mr={1} color={'myGray.600'} />
{item.key}
</Flex>
</Td>
<Td>{item.desc}</Td>
<Td>
{item.required ? (
<Flex alignItems={'center'}>
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
</Flex>
) : (
''
)}
</Td>
<Td>
<Flex>
<MyIconButton
icon={'common/settingLight'}
onClick={() => {
setEditExtractField(item);
}}
/>
<MyIcon
name={'delete'}
w={'16px'}
cursor={'pointer'}
<MyIconButton
icon={'delete'}
hoverColor={'red.500'}
onClick={() => {
onChangeNode({
nodeId,
@@ -128,13 +129,13 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
});
}}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</Flex>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
)
}),

View File

@@ -3,7 +3,7 @@ import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import MyModal from '@fastgpt/web/components/common/MyModal';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { UserInputFormItemType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { useForm } from 'react-hook-form';

View File

@@ -13,7 +13,6 @@ import {
Box,
Button,
Flex,
FormLabel,
HStack,
Table,
TableContainer,
@@ -24,7 +23,7 @@ import {
Tr
} from '@chakra-ui/react';
import { UserInputFormItemType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { useTranslation } from 'react-i18next';
import { useTranslation } from 'next-i18next';
import {
FlowNodeInputMap,
FlowNodeInputTypeEnum,
@@ -37,6 +36,8 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
import InputFormEditModal, { defaultFormInput } from './InputFormEditModal';
import RenderOutput from '../render/RenderOutput';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { nodeId, inputs, outputs } = data;
@@ -120,10 +121,14 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
return (
<Box>
<HStack className="nodrag" cursor={'default'} mb={3}>
<FormLabel>{t('workflow:user_form_input_config')}</FormLabel>
<FormLabel fontSize={'sm'} color={'myGray.600'}>
{t('workflow:user_form_input_config')}
</FormLabel>
<Box flex={'1 0 0'} />
<Button
variant={'ghost'}
variant={'grayGhost'}
px={2}
color={'myGray.600'}
leftIcon={<SmallAddIcon />}
iconSpacing={1}
size={'sm'}
@@ -144,17 +149,14 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
/>
)}
</HStack>
<TableContainer borderWidth={'1px'} borderRadius={'md'} borderBottom="none">
<Table bg={'white'}>
<TableContainer borderWidth={'1px'} borderRadius={'md'}>
<Table variant={'workflow'}>
<Thead>
<Tr>
<Th borderBottomLeftRadius={'none !important'}>
{t('workflow:user_form_input_name')}
</Th>
<Th>{t('workflow:user_form_input_name')}</Th>
<Th>{t('workflow:user_form_input_description')}</Th>
<Th>{t('common:common.Require Input')}</Th>
<Th borderBottomRightRadius={'none !important'}>{t('user:operations')}</Th>
<Th>{t('user:operations')}</Th>
</Tr>
</Thead>
<Tbody>
@@ -163,35 +165,35 @@ const NodeFormInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
return (
<Tr key={index}>
<Td>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} fontSize={'mini'} fontWeight={'medium'}>
{!!icon && (
<MyIcon name={icon as any} w={'14px'} mr={1} color={'primary.600'} />
<MyIcon name={icon as any} w={'14px'} mr={1} color={'myGray.400'} />
)}
{item.label}
</Flex>
</Td>
<Td>{item.description}</Td>
<Td>{item.required ? '✅' : ''}</Td>
<Td>{item.description || '-'}</Td>
<Td>
<MyIcon
mr={3}
name={'common/settingLight'}
w={'16px'}
cursor={'pointer'}
_hover={{ color: 'primary.600' }}
onClick={() => setEditField(item)}
/>
<MyIcon
className="delete"
name={'delete'}
w={'16px'}
color={'myGray.600'}
cursor={'pointer'}
_hover={{ color: 'red.500' }}
onClick={() => {
onDelete(item.key);
}}
/>
{item.required ? (
<Flex alignItems={'center'}>
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
</Flex>
) : (
'-'
)}
</Td>
<Td>
<Flex>
<MyIconButton
icon={'common/settingLight'}
onClick={() => setEditField(item)}
/>
<MyIconButton
icon={'delete'}
hoverColor={'red.500'}
onClick={() => onDelete(item.key)}
/>
</Flex>
</Td>
</Tr>
);

View File

@@ -5,11 +5,6 @@ import {
FormControl,
HStack,
Input,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Stack,
Switch,
Textarea
@@ -28,7 +23,7 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
import React, { useMemo } from 'react';
import { useFieldArray, UseFormReturn } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
import MyTextarea from '@/components/common/Textarea/MyTextarea';

View File

@@ -100,6 +100,7 @@ const PluginOutputEditModal = ({
data.key = data?.key?.trim();
data.label = data.key;
data.required = true;
onSubmit({
data,

View File

@@ -1,7 +1,8 @@
import React from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { Box, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react';
import { Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
const VariableTable = ({
variables = [],
@@ -16,58 +17,61 @@ const VariableTable = ({
const showToolColumn = variables.some((item) => item.isTool);
return (
<Box bg={'white'} borderRadius={'md'} overflow={'hidden'} border={'base'}>
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th borderBottomLeftRadius={'none !important'}>{t('workflow:Variable_name')}</Th>
<Th>{t('common:core.workflow.Value type')}</Th>
{showToolColumn && <Th>{t('workflow:tool_input')}</Th>}
<Th borderBottomRightRadius={'none !important'}></Th>
<TableContainer
borderRadius={'md'}
overflow={'hidden'}
border={'1px solid'}
borderColor={'myGray.200'}
>
<Table variant={'workflow'}>
<Thead>
<Tr>
<Th>{t('workflow:Variable_name')}</Th>
<Th>{t('common:core.workflow.Value type')}</Th>
{showToolColumn && <Th>{t('workflow:tool_input')}</Th>}
<Th>{t('user:operations')}</Th>
</Tr>
</Thead>
<Tbody>
{variables.map((item, index) => (
<Tr key={item.key}>
<Td>
<Flex alignItems={'center'} fontSize={'xs'}>
{!!item.icon ? (
<MyIcon name={item.icon as any} w={'14px'} mr={1} color={'myGray.600'} />
) : (
<MyIcon name={'checkCircle'} w={'14px'} mr={1} color={'myGray.600'} />
)}
{item.label || item.key}
</Flex>
</Td>
<Td>{item.type}</Td>
{showToolColumn && (
<Td>
{item.isTool ? (
<Flex alignItems={'center'}>
<MyIcon name={'check'} w={'16px'} color={'myGray.900'} mr={2} />
</Flex>
) : (
''
)}
</Td>
)}
<Td>
<Flex>
<MyIconButton icon={'common/settingLight'} onClick={() => onEdit(item.key)} />
<MyIconButton
icon={'delete'}
hoverColor={'red.500'}
onClick={() => onDelete(item.key)}
/>
</Flex>
</Td>
</Tr>
</Thead>
<Tbody>
{variables.map((item) => (
<Tr key={item.key}>
<Td>
<Flex alignItems={'center'}>
{!!item.icon && (
<MyIcon name={item.icon as any} w={'14px'} mr={1} color={'primary.600'} />
)}
{item.label || item.key}
</Flex>
</Td>
<Td>{item.type}</Td>
{showToolColumn && <Th>{item.isTool ? '✅' : '-'}</Th>}
<Td>
<MyIcon
mr={3}
name={'common/settingLight'}
w={'16px'}
cursor={'pointer'}
_hover={{ color: 'primary.600' }}
onClick={() => onEdit(item.key)}
/>
<MyIcon
className="delete"
name={'delete'}
w={'16px'}
color={'myGray.600'}
cursor={'pointer'}
ml={2}
_hover={{ color: 'red.500' }}
onClick={() => {
onDelete(item.key);
}}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
))}
</Tbody>
</Table>
</TableContainer>
);
};

View File

@@ -22,6 +22,7 @@ import FileSelect from '@/components/core/app/FileSelect';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { userFilesInput } from '@fastgpt/global/core/workflow/template/system/workflowStart';
import Container from '../components/Container';
import AutoExecConfig from '@/components/core/app/AutoExecConfig';
type ComponentProps = {
chatConfig: AppChatConfigType;
@@ -80,6 +81,9 @@ const NodeUserGuide = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
<Box mt={4} pt={3} borderTop={'base'} borderColor={'myGray.200'}>
<ScheduledTrigger {...componentsProps} />
</Box>
<Box mt={3} pt={3} borderTop={'base'} borderColor={'myGray.200'}>
<AutoExecute {...componentsProps} />
</Box>
<Box mt={3} pt={3} borderTop={'base'} borderColor={'myGray.200'}>
<QuestionInputGuide {...componentsProps} />
</Box>
@@ -128,6 +132,23 @@ function ChatStartVariable({ chatConfig: { variables = [] }, setAppDetail }: Com
return <VariableEdit variables={variables} onChange={(e) => updateVariables(e)} />;
}
function AutoExecute({ chatConfig: { autoExecute }, setAppDetail }: ComponentProps) {
return (
<AutoExecConfig
value={autoExecute}
onChange={(e) =>
setAppDetail((state) => ({
...state,
chatConfig: {
...state.chatConfig,
autoExecute: e
}
}))
}
/>
);
}
function QuestionGuide({ chatConfig: { questionGuide = false }, setAppDetail }: ComponentProps) {
return (
<QGSwitch

View File

@@ -5,7 +5,7 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
import MySelect from '@fastgpt/web/components/common/MySelect';
import React, { useCallback, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { useTranslation } from 'next-i18next';
import { defaultEditFormData } from '../render/RenderToolInput/EditFieldModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useContextSelector } from 'use-context-selector';

View File

@@ -16,7 +16,7 @@ import {
Thead,
Tr
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { useTranslation } from 'next-i18next';
import { SmallAddIcon } from '@chakra-ui/icons';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import { defaultEditFormData } from '../render/RenderToolInput/EditFieldModal';
@@ -36,7 +36,7 @@ const NodeToolParams = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
<NodeCard selected={selected} {...data}>
<Container>
<Flex alignItems={'center'} justifyContent={'space-between'} mb={1.5}>
<FormLabel>{t('workflow:tool_custom_field')}</FormLabel>
<FormLabel fontSize={'sm'}>{t('workflow:tool_custom_field')}</FormLabel>
<Button
variant={'whiteBase'}
leftIcon={<SmallAddIcon />}
@@ -54,38 +54,89 @@ const NodeToolParams = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
/>
)}
</Flex>
<Box borderRadius={'md'} overflow={'hidden'} border={'base'}>
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th>{t('workflow:tool_params.params_name')}</Th>
<Th>{t('workflow:tool_params.params_description')}</Th>
<Th>{t('common:common.Operation')}</Th>
</Tr>
</Thead>
<Tbody>
{inputs.map((item, index) => (
<Tr
key={index}
position={'relative'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
<TableContainer
borderRadius={'md'}
overflow={'hidden'}
border={'1px solid'}
borderColor={'myGray.200'}
>
<Table bg={'white'}>
<Thead>
<Tr h={8}>
<Th p={0} px={4} bg={'myGray.50'} borderBottomLeftRadius={'none !important'}>
{t('workflow:tool_params.params_name')}
</Th>
<Th p={0} px={4} bg={'myGray.50'}>
{t('workflow:tool_params.params_description')}
</Th>
<Th p={0} px={4} bg={'myGray.50'} borderBottomRightRadius={'none !important'}>
{t('common:common.Operation')}
</Th>
</Tr>
</Thead>
<Tbody>
{inputs.map((item, index) => (
<Tr
key={index}
position={'relative'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
h={10}
>
<Td
p={0}
px={4}
borderBottom={index === inputs.length - 1 ? 'none' : undefined}
>
<Td>{item.key}</Td>
<Td>{item.toolDescription}</Td>
<Td whiteSpace={'nowrap'}>
<MyIcon
<Flex alignItems={'center'} fontSize={'xs'}>
<MyIcon name={'checkCircle'} w={'14px'} mr={1} color={'myGray.600'} />
{item.key}
</Flex>
</Td>
<Td
p={0}
px={4}
borderBottom={index === inputs.length - 1 ? 'none' : undefined}
fontSize={'xs'}
>
{item.toolDescription}
</Td>
<Td
p={0}
px={4}
borderBottom={index === inputs.length - 1 ? 'none' : undefined}
whiteSpace={'nowrap'}
>
<Flex alignItems={'center'}>
<Flex
mr={3}
name={'common/settingLight'}
w={'16px'}
p={1}
color={'myGray.500'}
rounded={'sm'}
alignItems={'center'}
bg={'transparent'}
transition={'background 0.1s'}
cursor={'pointer'}
_hover={{
bg: 'myGray.05',
color: 'primary.600'
}}
onClick={() => setEditField(item)}
/>
<MyIcon
name={'delete'}
w={'16px'}
>
<MyIcon name={'common/settingLight'} w={'16px'} />
</Flex>
<Flex
p={1}
color={'myGray.500'}
rounded={'sm'}
alignItems={'center'}
bg={'transparent'}
transition={'background 0.1s'}
cursor={'pointer'}
_hover={{
bg: 'myGray.05',
color: 'red.500'
}}
onClick={() => {
onChangeNode({
nodeId,
@@ -98,14 +149,16 @@ const NodeToolParams = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
key: item.key
});
}}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
>
<MyIcon name={'delete'} w={'16px'} />
</Flex>
</Flex>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Container>
</NodeCard>
);

View File

@@ -26,7 +26,7 @@ const NodeTools = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
</Container>
<Box position={'relative'}>
<Box mb={-4} borderBottomLeftRadius={'md'} borderBottomRadius={'md'} overflow={'hidden'}>
<Box mb={-3} borderBottomRadius={'lg'} overflow={'hidden'}>
<Divider
showBorderBottom={false}
icon={<MyIcon name="phoneTabbar/tool" w={'16px'} h={'16px'} />}

View File

@@ -123,7 +123,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
return (
<Container key={index} w={'full'} mx={0}>
<Flex alignItems={'center'}>
<Flex w={'60px'}>{t('common:core.workflow.variable')}</Flex>
<Flex w={'80px'}>{t('common:core.workflow.variable')}</Flex>
<VariableSelector
nodeId={nodeId}
variable={updateItem.variable}
@@ -162,7 +162,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
)}
</Flex>
<Flex mt={2} w={'full'} alignItems={'center'} className="nodrag">
<Flex w={'60px'}>
<Flex w={'80px'}>
<Box>{t('common:core.workflow.value')}</Box>
<MyTooltip
label={

View File

@@ -53,6 +53,8 @@ export const ToolTargetHandle = ({ show, nodeId }: ToolHandleProps) => {
w={handleSize}
h={handleSize}
border={'4px solid #8774EE'}
rounded={'xs'}
bg={'white'}
transform={'translate(0,0) rotate(45deg)'}
pointerEvents={'none'}
/>
@@ -105,6 +107,8 @@ export const ToolSourceHandle = () => {
w={handleSize}
h={handleSize}
border={'4px solid #8774EE'}
rounded={'xs'}
bg={'white'}
transform={'translate(0,0) rotate(45deg)'}
pointerEvents={'none'}
/>

View File

@@ -20,7 +20,6 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useWorkflowUtils } from '../../hooks/useUtils';
@@ -29,6 +28,7 @@ import { getDocPath } from '@/web/common/system/doc';
import { WorkflowNodeEdgeContext } from '../../../context/workflowInitContext';
import { WorkflowEventContext } from '../../../context/workflowEventContext';
import MyImage from '@fastgpt/web/components/common/Image/MyImage';
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
type Props = FlowNodeItemType & {
children?: React.ReactNode | React.ReactNode[] | string;
@@ -166,7 +166,7 @@ const NodeCard = (props: Props) => {
<ToolTargetHandle show={showToolHandle} nodeId={nodeId} />
{/* avatar and name */}
<Flex alignItems={'center'}>
<Flex alignItems={'center'} mb={intro ? 1 : 0}>
{node?.flowNodeType !== FlowNodeTypeEnum.stopTool && (
<Flex
alignItems={'center'}
@@ -202,15 +202,13 @@ const NodeCard = (props: Props) => {
<Box ml={2} fontSize={'18px'} fontWeight={'medium'} color={'myGray.900'}>
{t(name as any)}
</Box>
<MyIcon
className="controller-rename"
<Button
display={'none'}
name={'edit'}
w={'14px'}
variant={'grayGhost'}
size={'xs'}
ml={0.5}
className="controller-rename"
cursor={'pointer'}
ml={1}
color={'myGray.500'}
_hover={{ color: 'primary.600' }}
onClick={() => {
onOpenCustomTitleModal({
defaultVal: name,
@@ -230,7 +228,9 @@ const NodeCard = (props: Props) => {
}
});
}}
/>
>
<MyIcon name={'edit'} w={'14px'} />
</Button>
<Box flex={1} />
{hasNewVersion && (
<MyTooltip label={t('app:app.modules.click to update')}>
@@ -248,7 +248,7 @@ const NodeCard = (props: Props) => {
onClick={onOpenConfirmSync(onClickSyncVersion)}
>
<Box>{t('app:app.modules.has new version')}</Box>
<QuestionOutlineIcon ml={1} />
<MyIcon name={'help'} w={'14px'} ml={1} />
</Button>
</MyTooltip>
)}
@@ -263,32 +263,20 @@ const NodeCard = (props: Props) => {
/>
}
>
<Box
fontSize={'sm'}
color={'primary.700'}
p={1}
rounded={'sm'}
cursor={'default'}
_hover={{ bg: 'rgba(17, 24, 36, 0.05)' }}
>
<Button variant={'grayGhost'} size={'xs'} color={'primary.600'} px={1}>
{t('common:core.module.Diagram')}
</Box>
</Button>
</MyTooltip>
)}
{!!nodeTemplate?.diagram && node?.courseUrl && (
<Box bg={'myGray.300'} w={'1px'} h={'12px'} mx={1} />
<Box bg={'myGray.300'} w={'1px'} h={'12px'} ml={1} mr={0.5} />
)}
{node?.courseUrl && !hasNewVersion && (
<MyTooltip label={t('workflow:Node.Open_Node_Course')}>
<MyIcon
cursor={'pointer'}
name="book"
color={'primary.600'}
w={'18px'}
<MyIconButton
ml={1}
_hover={{
color: 'primary.800'
}}
icon="book"
color={'primary.600'}
onClick={() => window.open(getDocPath(node.courseUrl || ''), '_blank')}
/>
</MyTooltip>
@@ -378,7 +366,7 @@ const NodeCard = (props: Props) => {
>
<NodeDebugResponse nodeId={nodeId} debugResult={debugResult} />
{Header}
<Flex flexDirection={'column'} flex={1} my={!isFolded ? 4 : 0} gap={2}>
<Flex flexDirection={'column'} flex={1} my={!isFolded ? 3 : 0} gap={2}>
{!isFolded ? children : <Box h={4} />}
</Flex>
{RenderHandle}
@@ -521,7 +509,7 @@ const MenuRender = React.memo(function MenuRender({
className="nodrag controller-menu"
display={'none'}
flexDirection={'column'}
gap={3}
gap={2}
position={'absolute'}
top={'-20px'}
right={0}
@@ -532,16 +520,18 @@ const MenuRender = React.memo(function MenuRender({
pt={'20px'}
>
{menuList.map((item) => (
<Box key={item.icon}>
<Button
size={'xs'}
variant={item.variant}
leftIcon={<MyIcon name={item.icon as any} w={'13px'} />}
onClick={item.onClick}
>
{t(item.label as any)}
</Button>
</Box>
<Button
key={item.icon}
h={8}
fontSize={'sm'}
pl={2}
pr={6}
variant={item.variant}
leftIcon={<MyIcon name={item.icon as any} w={'16px'} mr={-1} />}
onClick={item.onClick}
>
{t(item.label as any)}
</Button>
))}
</Box>
<DebugInputModal />
@@ -592,31 +582,32 @@ const NodeIntro = React.memo(function NodeIntro({
<Box fontSize={'sm'} color={'myGray.500'} flex={'1 0 0'}>
{t(intro as any)}
</Box>
<Flex
p={'7px'}
rounded={'sm'}
alignItems={'center'}
_hover={{
bg: NodeIsTool ? 'myGray.100' : 'transparent'
}}
cursor={NodeIsTool ? 'pointer' : 'default'}
onClick={() => {
if (!NodeIsTool) return;
onOpenIntroModal({
defaultVal: intro,
onSuccess(e) {
onChangeNode({
nodeId,
type: 'attr',
key: 'intro',
value: e
});
}
});
}}
>
<MyIcon name={'edit'} w={'18px'} opacity={NodeIsTool ? 1 : 0} />
</Flex>
{NodeIsTool && (
<Flex
p={'7px'}
rounded={'sm'}
alignItems={'center'}
_hover={{
bg: 'myGray.100'
}}
cursor={'pointer'}
onClick={() => {
onOpenIntroModal({
defaultVal: intro,
onSuccess(e) {
onChangeNode({
nodeId,
type: 'attr',
key: 'intro',
value: e
});
}
});
}}
>
<MyIcon name={'edit'} w={'18px'} />
</Flex>
)}
</Flex>
<EditIntroModal maxLength={500} />
</>

View File

@@ -115,7 +115,6 @@ function Reference({
content: workflowT('confirm_delete_field_tip')
});
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const keys = useMemo(() => {
return inputs.map((input) => input.key);
@@ -199,7 +198,7 @@ function Reference({
/>
<MyIcon
className="delete"
className={'delete'}
name={'delete'}
w={'14px'}
color={'myGray.500'}

View File

@@ -69,7 +69,7 @@ const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) =>
w={'100%'}
>
<Button
h={'36px'}
h={10}
leftIcon={<MyIcon name={'common/selectLight'} w={'14px'} />}
onClick={onOpenDatasetSelect}
>
@@ -79,19 +79,20 @@ const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) =>
<Flex
key={item._id}
alignItems={'center'}
h={'36px'}
h={10}
border={theme.borders.base}
borderColor={'myGray.200'}
px={2}
borderRadius={'md'}
>
<Avatar src={item.avatar} w={'24px'}></Avatar>
<Avatar src={item.avatar} w={'18px'} borderRadius={'xs'} />
<Box
ml={3}
ml={1.5}
flex={'1 0 0'}
w={0}
className="textEllipsis"
fontWeight={'bold'}
fontSize={['md', 'lg']}
fontSize={['sm', 'sm']}
>
{item.name}
</Box>

View File

@@ -4,7 +4,6 @@ import { Box, Button, Flex } from '@chakra-ui/react';
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import OutputLabel from './Label';
import { RenderOutputProps } from './type';
import { useTranslation } from 'next-i18next';
import { SmallAddIcon } from '@chakra-ui/icons';
import VariableTable from '../../NodePluginIO/VariableTable';
@@ -18,11 +17,6 @@ import { defaultOutput } from './FieldEditModal';
const FieldEditModal = dynamic(() => import('./FieldEditModal'));
const RenderList: {
types: FlowNodeOutputTypeEnum[];
Component: React.ComponentType<RenderOutputProps>;
}[] = [];
const RenderOutput = ({
nodeId,
flowOutputList
@@ -77,7 +71,7 @@ const RenderOutput = ({
alignItems={'center'}
position={'relative'}
>
<Box position={'relative'} fontWeight={'medium'}>
<Box position={'relative'} fontWeight={'medium'} fontSize={'sm'}>
{t((addOutput.label || 'common:core.workflow.Custom outputs') as any)}
</Box>
{addOutput.description && <QuestionTip ml={1} label={addOutput.description} />}

View File

@@ -21,7 +21,8 @@ const ValueTypeLabel = ({
<Box
bg={'myGray.100'}
color={'myGray.500'}
border={'base'}
border={'1px solid'}
borderColor={'myGray.200'}
borderRadius={'sm'}
ml={2}
px={1}

View File

@@ -6,7 +6,7 @@ export const minZoom = 0.1;
export const maxZoom = 1.5;
export const connectionLineStyle: React.CSSProperties = {
strokeWidth: 2,
strokeWidth: 3,
stroke: '#487FFF'
};

View File

@@ -1,8 +1,7 @@
import { useUserStore } from '@/web/support/user/useUserStore';
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo } from 'react';
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
import { streamFetch } from '@/web/common/api/fetch';
import { getMaxHistoryLimitFromNodes } from '@fastgpt/global/core/workflow/runtime/utils';
import { useMemoizedFn } from 'ahooks';
import { useContextSelector } from 'use-context-selector';
import { AppContext } from './context';
@@ -11,43 +10,56 @@ import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import dynamic from 'next/dynamic';
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
import { Box, BoxProps } from '@chakra-ui/react';
import { Box } from '@chakra-ui/react';
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getInitChatInfo } from '@/web/core/chat/api';
import { useTranslation } from 'next-i18next';
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
export const useChatTest = ({
nodes,
edges,
chatConfig
chatConfig,
isReady
}: {
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
chatConfig: AppChatConfigType;
isReady: boolean;
}) => {
const { t } = useTranslation();
const { userInfo } = useUserStore();
const { appDetail } = useContextSelector(AppContext, (v) => v);
const [chatRecords, setChatRecords] = useState<ChatSiteItemType[]>([]);
const { setChatId, chatId, appId } = useChatStore();
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
const startChat = useMemoizedFn(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
/* get histories */
const historyMaxLen = getMaxHistoryLimitFromNodes(nodes);
async ({
messages,
responseChatItemId,
controller,
generatingMessage,
variables
}: StartChatFnProps) => {
const histories = messages.slice(-1);
// 流请求,获取数据
const { responseText, responseData } = await streamFetch({
url: '/api/core/chat/chatTest',
data: {
// Send histories and user messages
messages: messages.slice(-historyMaxLen - 2),
messages: histories,
nodes,
edges,
variables,
appId: appDetail._id,
appName: `调试-${appDetail.name}`,
responseChatItemId,
appId,
appName: t('chat:chat_test_app', { name: appDetail.name }),
chatId,
chatConfig
},
onMessage: generatingMessage,
@@ -58,77 +70,84 @@ export const useChatTest = ({
}
);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const clearChatRecords = useContextSelector(ChatItemContext, (v) => v.clearChatRecords);
const pluginInputs = useMemo(() => {
return nodes.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs || [];
}, [nodes]);
const { ChatBoxRef, variablesForm, pluginRunTab, setPluginRunTab, clearChatRecords } = useChat();
// Set chat box data
useEffect(() => {
setChatBoxData({
userAvatar: userInfo?.avatar,
app: {
chatConfig,
name: appDetail.name,
avatar: appDetail.avatar,
type: appDetail.type,
pluginInputs
}
});
}, [
appDetail.avatar,
appDetail.name,
appDetail.type,
chatConfig,
pluginInputs,
setChatBoxData,
userInfo?.avatar
]);
// Mock ScrollData
const ScrollData = useCallback(
({
children,
ScrollContainerRef,
...props
}: {
ScrollContainerRef?: React.RefObject<HTMLDivElement>;
children: React.ReactNode;
} & BoxProps) => {
return (
<Box ref={ScrollContainerRef} {...props} overflow={'overlay'}>
{children}
</Box>
);
// init chat data
const { loading } = useRequest2(
async () => {
if (!appId || !chatId) return;
const res = await getInitChatInfo({ appId, chatId });
resetVariables({
variables: res.variables
});
},
[]
{
manual: false,
refreshDeps: [appId, chatId]
}
);
const restartChat = useCallback(() => {
clearChatRecords();
setChatId();
}, [clearChatRecords, setChatId]);
const CustomChatContainer = useMemoizedFn(() =>
appDetail.type === AppTypeEnum.plugin ? (
<Box p={5}>
<PluginRunBox
pluginInputs={pluginInputs}
variablesForm={variablesForm}
histories={chatRecords}
setHistories={setChatRecords}
appId={appDetail._id}
chatConfig={appDetail.chatConfig}
tab={pluginRunTab}
setTab={setPluginRunTab}
onNewChat={() => {
clearChatRecords();
setChatRecords([]);
}}
appId={appId}
chatId={chatId}
onNewChat={restartChat}
onStartChat={startChat}
/>
</Box>
) : (
<ChatBox
ref={ChatBoxRef}
ScrollData={ScrollData}
chatHistories={chatRecords}
setChatHistories={setChatRecords}
variablesForm={variablesForm}
appId={appDetail._id}
appAvatar={appDetail.avatar}
userAvatar={userInfo?.avatar}
isReady={isReady}
appId={appId}
chatId={chatId}
showMarkIcon
chatType="chat"
showRawSource
showNodeStatus
chatConfig={chatConfig}
onStartChat={startChat}
onDelMessage={() => {}}
/>
)
);
return {
restartChat: clearChatRecords,
ChatContainer: CustomChatContainer,
chatRecords,
pluginRunTab,
setPluginRunTab
restartChat,
loading
};
};

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect } from 'react';
import { Box } from '@chakra-ui/react';
import dynamic from 'next/dynamic';
import Loading from '@fastgpt/web/components/common/MyLoading';
@@ -7,6 +7,7 @@ import NextHead from '@/components/common/NextHead';
import { useContextSelector } from 'use-context-selector';
import AppContextProvider, { AppContext } from './components/context';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
const SimpleEdit = dynamic(() => import('./components/SimpleApp'), {
ssr: false,
@@ -22,7 +23,13 @@ const Plugin = dynamic(() => import('./components/Plugin'), {
});
const AppDetail = () => {
const { appDetail } = useContextSelector(AppContext, (e) => e);
const { setAppId, setSource } = useChatStore();
const appDetail = useContextSelector(AppContext, (e) => e.appDetail);
useEffect(() => {
setSource('test');
appDetail._id && setAppId(appDetail._id);
}, [appDetail._id, setSource, setAppId]);
return (
<>

View File

@@ -16,7 +16,7 @@ import { AppFolderTypeList, AppTypeEnum } from '@fastgpt/global/core/app/constan
import { useFolderDrag } from '@/components/common/folder/useFolderDrag';
import dynamic from 'next/dynamic';
import type { EditResourceInfoFormType } from '@/components/common/Modal/EditResourceModal';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import MyMenu, { MenuItemType } from '@fastgpt/web/components/common/MyMenu';
import { AppPermissionList } from '@fastgpt/global/support/permission/app/constant';
import {
deleteAppCollaborators,
@@ -33,7 +33,7 @@ import type { EditHttpPluginProps } from './HttpPluginEditModal';
import { postCopyApp } from '@/web/core/app/api/app';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { useUserStore } from '@/web/support/user/useUserStore';
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
const HttpEditModal = dynamic(() => import('./HttpPluginEditModal'));
@@ -45,7 +45,6 @@ const ListItem = () => {
const { isPc } = useSystem();
const { loadAndGetTeamMembers } = useUserStore();
const { lastChatAppId, setLastChatAppId } = useChatStore();
const { openConfirm: openMoveConfirm, ConfirmModal: MoveConfirmModal } = useConfirm({
type: 'common',
@@ -87,6 +86,8 @@ const ListItem = () => {
const { openConfirm: openConfirmDel, ConfirmModal: DelConfirmModal } = useConfirm({
type: 'delete'
});
const { lastChatAppId, setLastChatAppId } = useChatStore();
const { runAsync: onclickDelApp } = useRequest2(
(id: string) => {
if (id === lastChatAppId) {
@@ -259,6 +260,7 @@ const ListItem = () => {
: app.permission.hasWritePer) && (
<Box className="more" display={['', 'none']}>
<MyMenu
size={'xs'}
Button={
<IconButton
size={'xsSquare'}
@@ -274,6 +276,7 @@ const ListItem = () => {
children: [
{
icon: 'core/chat/chatLight',
type: 'grayBg' as MenuItemType,
label: t('app:go_to_chat'),
onClick: () => {
router.push(`/chat?appId=${app._id}`);
@@ -289,6 +292,7 @@ const ListItem = () => {
children: [
{
icon: 'core/chat/chatLight',
type: 'grayBg' as MenuItemType,
label: t('app:go_to_run'),
onClick: () => {
router.push(`/chat?appId=${app._id}`);
@@ -304,6 +308,7 @@ const ListItem = () => {
children: [
{
icon: 'edit',
type: 'grayBg' as MenuItemType,
label: t('common:dataset.Edit Info'),
onClick: () => {
if (app.type === AppTypeEnum.httpPlugin) {
@@ -331,6 +336,7 @@ const ListItem = () => {
: [
{
icon: 'common/file/move',
type: 'grayBg' as MenuItemType,
label: t('common:common.folder.Move to'),
onClick: () => setMoveAppId(app._id)
}
@@ -339,6 +345,7 @@ const ListItem = () => {
? [
{
icon: 'support/team/key',
type: 'grayBg' as MenuItemType,
label: t('common:permission.Permission'),
onClick: () => setEditPerAppIndex(index)
}
@@ -355,6 +362,7 @@ const ListItem = () => {
children: [
{
icon: 'copy',
type: 'grayBg' as MenuItemType,
label: t('app:copy_one_app'),
onClick: () =>
openConfirmCopy(() => onclickCopy({ appId: app._id }))()
@@ -394,9 +402,7 @@ const ListItem = () => {
);
})}
</Grid>
{myApps.length === 0 && <EmptyTip text={t('common:core.app.no_app')} pt={'30vh'} />}
<DelConfirmModal />
<ConfirmCopyModal />
{!!editedApp && (
@@ -463,5 +469,4 @@ const ListItem = () => {
</>
);
};
export default ListItem;

View File

@@ -1,13 +1,5 @@
import React, { useMemo, useState } from 'react';
import {
Box,
Flex,
Button,
useDisclosure,
Input,
InputGroup,
InputLeftElement
} from '@chakra-ui/react';
import { Box, Flex, Button, useDisclosure, Input, InputGroup } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useUserStore } from '@/web/support/user/useUserStore';
@@ -95,15 +87,23 @@ const MyApps = () => {
const RenderSearchInput = useMemo(
() => (
<InputGroup maxW={['auto', '250px']}>
<InputLeftElement h={'full'} alignItems={'center'} display={'flex'}>
<MyIcon name={'common/searchLight'} w={'1rem'} />
</InputLeftElement>
<InputGroup maxW={['auto', '250px']} position={'relative'}>
<MyIcon
position={'absolute'}
zIndex={10}
name={'common/searchLight'}
w={'1rem'}
color={'myGray.600'}
left={2.5}
top={'50%'}
transform={'translateY(-50%)'}
/>
<Input
value={searchKey}
onChange={(e) => setSearchKey(e.target.value)}
placeholder={appT('search_app')}
maxLength={30}
pl={8}
bg={'white'}
/>
</InputGroup>
@@ -179,66 +179,67 @@ const MyApps = () => {
{isPc && RenderSearchInput}
{userInfo?.team.permission.hasWritePer &&
folderDetail?.type !== AppTypeEnum.httpPlugin && (
<MyMenu
iconSize="2rem"
Button={
<Button variant={'primary'} leftIcon={<AddIcon />}>
<Box>{t('common:common.Create New')}</Box>
</Button>
{(folderDetail
? folderDetail.permission.hasWritePer && folderDetail?.type !== AppTypeEnum.httpPlugin
: userInfo?.team.permission.hasWritePer) && (
<MyMenu
size="md"
Button={
<Button variant={'primary'} leftIcon={<AddIcon />}>
<Box>{t('common:common.Create New')}</Box>
</Button>
}
menuList={[
{
children: [
{
icon: 'core/app/simpleBot',
label: t('app:type.Simple bot'),
description: t('app:type.Create simple bot tip'),
onClick: () => setCreateAppType(AppTypeEnum.simple)
},
{
icon: 'core/app/type/workflowFill',
label: t('app:type.Workflow bot'),
description: t('app:type.Create workflow tip'),
onClick: () => setCreateAppType(AppTypeEnum.workflow)
},
{
icon: 'core/app/type/pluginFill',
label: t('app:type.Plugin'),
description: t('app:type.Create one plugin tip'),
onClick: () => setCreateAppType(AppTypeEnum.plugin)
},
{
icon: 'core/app/type/httpPluginFill',
label: t('app:type.Http plugin'),
description: t('app:type.Create http plugin tip'),
onClick: onOpenCreateHttpPlugin
}
]
},
{
children: [
{
icon: '/imgs/app/templateFill.svg',
label: t('app:template_market'),
description: t('app:template_market_description'),
onClick: () => setTemplateModalType('all')
}
]
},
{
children: [
{
icon: FolderIcon,
label: t('common:Folder'),
onClick: () => setEditFolder({})
}
]
}
menuList={[
{
children: [
{
icon: 'core/app/simpleBot',
label: t('app:type.Simple bot'),
description: t('app:type.Create simple bot tip'),
onClick: () => setCreateAppType(AppTypeEnum.simple)
},
{
icon: 'core/app/type/workflowFill',
label: t('app:type.Workflow bot'),
description: t('app:type.Create workflow tip'),
onClick: () => setCreateAppType(AppTypeEnum.workflow)
},
{
icon: 'core/app/type/pluginFill',
label: t('app:type.Plugin'),
description: t('app:type.Create one plugin tip'),
onClick: () => setCreateAppType(AppTypeEnum.plugin)
},
{
icon: 'core/app/type/httpPluginFill',
label: t('app:type.Http plugin'),
description: t('app:type.Create http plugin tip'),
onClick: onOpenCreateHttpPlugin
}
]
},
{
children: [
{
icon: '/imgs/app/templateFill.svg',
label: t('app:template_market'),
description: t('app:template_market_description'),
onClick: () => setTemplateModalType('all')
}
]
},
{
children: [
{
icon: FolderIcon,
label: t('common:Folder'),
onClick: () => setEditFolder({})
}
]
}
]}
/>
)}
]}
/>
)}
</Flex>
{!isPc && <Box mt={2}>{RenderSearchInput}</Box>}

View File

@@ -14,6 +14,8 @@ import { useContextSelector } from 'use-context-selector';
import { ChatContext } from '@/web/core/chat/context/chatContext';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
type HistoryItemType = {
id: string;
@@ -23,25 +25,7 @@ type HistoryItemType = {
updateTime: Date;
};
const ChatHistorySlider = ({
appId,
appName,
appAvatar,
confirmClearText,
onDelHistory,
onClearHistory,
onSetHistoryTop,
onSetCustomTitle
}: {
appId?: string;
appName: string;
appAvatar: string;
confirmClearText: string;
onDelHistory: (e: { chatId: string }) => void;
onClearHistory: () => void;
onSetHistoryTop?: (e: { chatId: string; top: boolean }) => void;
onSetCustomTitle?: (e: { chatId: string; title: string }) => void;
}) => {
const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) => {
const theme = useTheme();
const router = useRouter();
const isUserChatPage = router.pathname === '/chat';
@@ -51,13 +35,17 @@ const ChatHistorySlider = ({
const { isPc } = useSystem();
const { userInfo } = useUserStore();
const {
onChangeChatId,
chatId: activeChatId,
isLoading,
ScrollData,
histories
} = useContextSelector(ChatContext, (v) => v);
const { appId, chatId: activeChatId } = useChatStore();
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
const isLoading = useContextSelector(ChatContext, (v) => v.isLoading);
const ScrollData = useContextSelector(ChatContext, (v) => v.ScrollData);
const histories = useContextSelector(ChatContext, (v) => v.histories);
const onDelHistory = useContextSelector(ChatContext, (v) => v.onDelHistory);
const onClearHistory = useContextSelector(ChatContext, (v) => v.onClearHistories);
const onUpdateHistory = useContextSelector(ChatContext, (v) => v.onUpdateHistory);
const appName = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.name);
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.avatar);
const concatHistory = useMemo(() => {
const formatHistories: HistoryItemType[] = histories.map((item) => {
@@ -169,14 +157,13 @@ const ChatHistorySlider = ({
size={'mdSquare'}
aria-label={''}
borderRadius={'50%'}
icon={<MyIcon name={'common/clearLight'} w={'16px'} />}
onClick={() =>
openConfirm(() => {
onClearHistory();
})()
}
>
<MyIcon name={'common/clearLight'} w={'16px'} />
</IconButton>
/>
)}
</Flex>
@@ -249,45 +236,38 @@ const ChatHistorySlider = ({
menuList={[
{
children: [
...(onSetHistoryTop
? [
{
label: item.top
? t('common:core.chat.Unpin')
: t('common:core.chat.Pin'),
icon: 'core/chat/setTopLight',
onClick: () => {
onSetHistoryTop({
chatId: item.id,
top: !item.top
});
}
}
]
: []),
...(onSetCustomTitle
? [
{
label: t('common:common.Custom Title'),
icon: 'common/customTitleLight',
onClick: () => {
onOpenModal({
defaultVal: item.customTitle || item.title,
onSuccess: (e) =>
onSetCustomTitle({
chatId: item.id,
title: e
})
});
}
}
]
: []),
{
label: item.top
? t('common:core.chat.Unpin')
: t('common:core.chat.Pin'),
icon: 'core/chat/setTopLight',
onClick: () => {
onUpdateHistory({
chatId: item.id,
top: !item.top
});
}
},
{
label: t('common:common.Custom Title'),
icon: 'common/customTitleLight',
onClick: () => {
onOpenModal({
defaultVal: item.customTitle || item.title,
onSuccess: (e) =>
onUpdateHistory({
chatId: item.id,
customTitle: e
})
});
}
},
{
label: t('common:common.Delete'),
icon: 'delete',
onClick: () => {
onDelHistory({ chatId: item.id });
onDelHistory(item.id);
if (item.id === activeChatId) {
onChangeChatId();
}

View File

@@ -6,12 +6,16 @@ import { Box, Grid, Stack } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import { useContextSelector } from 'use-context-selector';
const CustomPluginRunBox = (props: PluginRunBoxProps) => {
const { tab, setTab } = props;
const { isPc } = useSystem();
const { t } = useTranslation();
const tab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
const setTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
useEffect(() => {
if (isPc && tab === PluginRunBoxTabEnum.input) {
setTab(PluginRunBoxTabEnum.output);
@@ -24,7 +28,7 @@ const CustomPluginRunBox = (props: PluginRunBoxProps) => {
<Box color={'myGray.900'} mb={5}>
{t('common:common.Input')}
</Box>
<PluginRunBox {...props} tab={PluginRunBoxTabEnum.input} />
<PluginRunBox {...props} showTab={PluginRunBoxTabEnum.input} />
</Box>
<Stack px={3} py={4} h={'100%'} alignItems={'flex-start'} w={'100%'} overflow={'auto'}>
<Box display={'inline-block'}>

View File

@@ -41,7 +41,6 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
router.replace({
query: {
...router.query,
chatId: '',
appId
}
});

View File

@@ -1,10 +1,10 @@
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import NextHead from '@/components/common/NextHead';
import { useRouter } from 'next/router';
import { delChatRecordById, getInitChatInfo } from '@/web/core/chat/api';
import { getInitChatInfo } from '@/web/core/chat/api';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
import { streamFetch } from '@/web/common/api/fetch';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useTranslation } from 'next-i18next';
@@ -21,103 +21,75 @@ import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { getMyApps } from '@/web/core/app/api';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useCreation, useMount } from 'ahooks';
import { useMount } from 'ahooks';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { defaultChatData, GetChatTypeEnum } from '@/global/core/chat/constants';
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { useContextSelector } from 'use-context-selector';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import dynamic from 'next/dynamic';
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { InitChatResponse } from '@/global/core/chat/api';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider, {
ChatRecordContext
} from '@/web/core/chat/context/chatRecordContext';
import { InitChatResponse } from '@/global/core/chat/api';
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
type Props = { appId: string; chatId: string };
const Chat = ({
appId,
chatId,
myApps
}: Props & {
myApps: AppListItemType[];
}) => {
const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const router = useRouter();
const theme = useTheme();
const { t } = useTranslation();
const { userInfo } = useUserStore();
const { isPc } = useSystem();
const { setLastChatAppId } = useChatStore();
const { userInfo } = useUserStore();
const { setLastChatAppId, chatId, appId, outLinkAuthData } = useChatStore();
const {
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId,
onUpdateHistoryTitle
} = useContextSelector(ChatContext, (v) => v);
const isOpenSlider = useContextSelector(ChatContext, (v) => v.isOpenSlider);
const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider);
const forbidLoadChat = useContextSelector(ChatContext, (v) => v.forbidLoadChat);
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
const onUpdateHistoryTitle = useContextSelector(ChatContext, (v) => v.onUpdateHistoryTitle);
const params = useCreation(() => {
return {
chatId,
appId,
type: GetChatTypeEnum.normal
};
}, [appId, chatId]);
const {
ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
chatRecords,
ScrollData,
setChatRecords,
totalRecordsCount
} = useChat(params);
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const isPlugin = useContextSelector(ChatItemContext, (v) => v.isPlugin);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
// get chat app info
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const isPlugin = chatData.app.type === AppTypeEnum.plugin;
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
// Load chat init data
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const { loading: isLoading } = useRequest2(
async () => {
if (!appId || forbidLoadChat.current) return;
const res = await getInitChatInfo({ appId, chatId });
res.userAvatar = userInfo?.avatar;
setChatData(res);
setChatBoxData(res);
// reset chat variables
resetVariables({
variables: res.variables
});
setLastChatAppId(appId);
},
{
manual: false,
refreshDeps: [appId, chatId],
onError(e: any) {
setLastChatAppId('');
// reset all chat tore
if (e?.code === 501) {
setLastChatAppId('');
router.replace('/app/list');
} else {
router.replace({
query: {
...router.query,
appId: myApps[0]?._id,
chatId: ''
appId: myApps[0]?._id
}
});
}
@@ -136,7 +108,6 @@ const Chat = ({
generatingMessage,
variables
}: StartChatFnProps) => {
const completionChatId = chatId || getNanoid();
// Just send a user prompt
const histories = messages.slice(-1);
const { responseText, responseData } = await streamFetch({
@@ -145,7 +116,7 @@ const Chat = ({
variables,
responseChatItemId,
appId,
chatId: completionChatId
chatId
},
onMessage: generatingMessage,
abortCtrl: controller
@@ -154,10 +125,7 @@ const Chat = ({
const newTitle = getChatTitleFromChatMessage(GPTMessages2Chats(histories)[0]);
// new chat
if (completionChatId !== chatId && controller.signal.reason !== 'leave') {
onChangeChatId(completionChatId, true);
}
onUpdateHistoryTitle({ chatId: completionChatId, newTitle });
onUpdateHistoryTitle({ chatId, newTitle });
// update chat window
setChatData((state) => ({
...state,
@@ -166,32 +134,13 @@ const Chat = ({
return { responseText, responseData, isNewChat: forbidLoadChat.current };
},
[chatId, appId, onUpdateHistoryTitle, forbidLoadChat, onChangeChatId]
[chatId, appId, onUpdateHistoryTitle, forbidLoadChat]
);
const loading = isLoading;
const RenderHistorySlider = useMemo(() => {
const Children = (
<ChatHistorySlider
confirmClearText={t('common:core.chat.Confirm to clear history')}
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
onDelHistory={(e) => onDelHistory({ ...e, appId })}
onClearHistory={() => {
onClearHistories({ appId });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({ ...e, appId });
}}
onSetCustomTitle={async (e) => {
onUpdateHistory({
appId,
chatId: e.chatId,
customTitle: e.title
});
}}
/>
<ChatHistorySlider confirmClearText={t('common:core.chat.Confirm to clear history')} />
);
return isPc || !appId ? (
@@ -208,18 +157,7 @@ const Chat = ({
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
</Drawer>
);
}, [
appId,
chatData.app.avatar,
chatData.app.name,
isOpenSlider,
isPc,
onClearHistories,
onCloseSlider,
onDelHistory,
onUpdateHistory,
t
]);
}, [appId, isOpenSlider, isPc, onCloseSlider, t]);
return (
<Flex h={'100%'}>
@@ -257,33 +195,20 @@ const Chat = ({
<Box flex={'1 0 0'} bg={'white'}>
{isPlugin ? (
<CustomPluginRunBox
pluginInputs={chatData.app.pluginInputs}
variablesForm={variablesForm}
histories={chatRecords}
setHistories={setChatRecords}
appId={chatData.appId}
chatConfig={chatData.app.chatConfig}
tab={pluginRunTab}
setTab={setPluginRunTab}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
onNewChat={() => onChangeChatId(getNanoid())}
onStartChat={onStartChat}
/>
) : (
<ChatBox
ScrollData={ScrollData}
ref={ChatBoxRef}
chatHistories={chatRecords}
setChatHistories={setChatRecords}
variablesForm={variablesForm}
showEmptyIntro
appAvatar={chatData.app.avatar}
userAvatar={userInfo?.avatar}
chatConfig={chatData.app?.chatConfig}
feedbackType={'user'}
onStartChat={onStartChat}
onDelMessage={({ contentId }) => delChatRecordById({ contentId, appId, chatId })}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
showEmptyIntro
feedbackType={'user'}
onStartChat={onStartChat}
chatType={'chat'}
showRawSource
showNodeStatus
@@ -297,13 +222,12 @@ const Chat = ({
);
};
const Render = (props: Props) => {
const Render = (props: { appId: string }) => {
const { appId } = props;
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { lastChatAppId, lastChatId } = useChatStore();
const { source, chatId, lastChatAppId, setSource, setAppId } = useChatStore();
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
() => getMyApps({ getRecentlyChat: true }),
@@ -316,16 +240,6 @@ const Render = (props: Props) => {
useMount(async () => {
// pc: redirect to latest model chat
if (!appId) {
if (lastChatAppId) {
return router.replace({
query: {
...router.query,
appId: lastChatAppId,
chatId: lastChatId
}
});
}
const apps = await loadMyApps();
if (apps.length === 0) {
toast({
@@ -337,27 +251,45 @@ const Render = (props: Props) => {
router.replace({
query: {
...router.query,
appId: apps[0]._id,
chatId: ''
appId: lastChatAppId || apps[0]._id
}
});
}
}
setSource('online');
});
// Watch appId
useEffect(() => {
setAppId(appId);
}, [appId, setAppId]);
const providerParams = useMemo(() => ({ appId, source: ChatSourceEnum.online }), [appId]);
return (
<ChatContextProvider params={providerParams}>
<Chat {...props} myApps={myApps} />
</ChatContextProvider>
const chatHistoryProviderParams = useMemo(
() => ({ appId, source: ChatSourceEnum.online }),
[appId]
);
const chatRecordProviderParams = useMemo(() => {
return {
appId,
type: GetChatTypeEnum.normal,
chatId: chatId
};
}, [appId, chatId]);
return source === ChatSourceEnum.online ? (
<ChatContextProvider params={chatHistoryProviderParams}>
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<Chat myApps={myApps} />
</ChatRecordContextProvider>
</ChatItemContextProvider>
</ChatContextProvider>
) : null;
};
export async function getServerSideProps(context: any) {
return {
props: {
appId: context?.query?.appId || '',
chatId: context?.query?.chatId || '',
...(await serviceSideProps(context, ['file', 'app', 'chat', 'workflow']))
}
};

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
import { streamFetch } from '@/web/common/api/fetch';
@@ -13,7 +13,7 @@ import ChatHeader from './components/ChatHeader';
import ChatHistorySlider from './components/ChatHistorySlider';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
import { delChatRecordById, getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
@@ -26,16 +26,21 @@ import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData, GetChatTypeEnum } from '@/global/core/chat/constants';
import { useMount } from 'ahooks';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import dynamic from 'next/dynamic';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useShareChatStore } from '@/web/core/chat/storeShareChat';
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider, {
ChatRecordContext
} from '@/web/core/chat/context/chatRecordContext';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
type Props = {
appId: string;
appName: string;
appIntro: string;
appAvatar: string;
@@ -46,17 +51,12 @@ type Props = {
showNodeStatus: boolean;
};
const OutLink = (
props: Props & {
outLinkUid: string;
}
) => {
const OutLink = (props: Props) => {
const { t } = useTranslation();
const router = useRouter();
const { outLinkUid, showRawSource, showNodeStatus } = props;
const { showRawSource, showNodeStatus } = props;
const {
shareId = '',
chatId = '',
showHistory = '1',
showHead = '1',
authToken,
@@ -64,49 +64,69 @@ const OutLink = (
...customVariables
} = router.query as {
shareId: string;
chatId: string;
showHistory: '0' | '1';
showHead: '0' | '1';
authToken: string;
[key: string]: string;
};
const { isPc } = useSystem();
const { outLinkAuthData, appId, chatId } = useChatStore();
const isOpenSlider = useContextSelector(ChatContext, (v) => v.isOpenSlider);
const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider);
const forbidLoadChat = useContextSelector(ChatContext, (v) => v.forbidLoadChat);
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
const onUpdateHistoryTitle = useContextSelector(ChatContext, (v) => v.onUpdateHistoryTitle);
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const isPlugin = useContextSelector(ChatItemContext, (v) => v.isPlugin);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
const initSign = useRef(false);
const [isEmbed, setIdEmbed] = useState(true);
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const { loading: isLoading } = useRequest2(
async () => {
const shareId = outLinkAuthData.shareId;
const outLinkUid = outLinkAuthData.outLinkUid;
if (!outLinkUid || !shareId || forbidLoadChat.current) return;
const {
onUpdateHistoryTitle,
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId
} = useContextSelector(ChatContext, (v) => v);
const res = await getInitOutLinkChatInfo({
chatId,
shareId,
outLinkUid
});
const params = useMemo(() => {
return {
chatId,
shareId,
outLinkUid,
appId: chatData.appId,
type: GetChatTypeEnum.outLink
};
}, [chatData.appId, chatId, outLinkUid, shareId]);
const {
ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
chatRecords,
ScrollData,
setChatRecords,
totalRecordsCount
} = useChat(params);
setChatData(res);
setChatBoxData(res);
resetVariables({
variables: res.variables
});
},
{
manual: false,
refreshDeps: [shareId, outLinkAuthData, chatId],
onSuccess() {
// send init message
if (!initSign.current) {
initSign.current = true;
if (window !== top) {
window.top?.postMessage({ type: 'shareChatReady' }, '*');
}
}
},
onError(e: any) {
if (chatId) {
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
}
);
const startChat = useCallback(
async ({
@@ -138,10 +158,8 @@ const OutLink = (
...customVariables
},
responseChatItemId,
shareId,
chatId: completionChatId,
appType: chatData.app.type,
outLinkUid
...outLinkAuthData
},
onMessage: generatingMessage,
abortCtrl: controller
@@ -175,57 +193,11 @@ const OutLink = (
return { responseText, responseData, isNewChat: forbidLoadChat.current };
},
[
chatId,
customVariables,
shareId,
chatData.app.type,
outLinkUid,
onUpdateHistoryTitle,
forbidLoadChat,
onChangeChatId
]
);
const { loading: isLoading } = useRequest2(
async () => {
if (!shareId || !outLinkUid || forbidLoadChat.current) return;
const res = await getInitOutLinkChatInfo({
chatId,
shareId,
outLinkUid
});
setChatData(res);
resetVariables({
variables: res.variables
});
},
{
manual: false,
refreshDeps: [shareId, outLinkUid, chatId],
onSuccess() {
// send init message
if (!initSign.current) {
initSign.current = true;
if (window !== top) {
window.top?.postMessage({ type: 'shareChatReady' }, '*');
}
}
},
onError(e: any) {
if (chatId) {
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
}
[chatId, customVariables, outLinkAuthData, onUpdateHistoryTitle, forbidLoadChat, onChangeChatId]
);
// window init
const [isEmbed, setIdEmbed] = useState(true);
useMount(() => {
setIdEmbed(window !== top);
});
@@ -233,32 +205,7 @@ const OutLink = (
const RenderHistoryList = useMemo(() => {
const Children = (
<ChatHistorySlider
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('common:core.chat.Confirm to clear share chat history')}
onDelHistory={({ chatId }) =>
onDelHistory({ appId: chatData.appId, chatId, shareId, outLinkUid })
}
onClearHistory={() => {
onClearHistories({ shareId, outLinkUid });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({
...e,
appId: chatData.appId,
shareId,
outLinkUid
});
}}
onSetCustomTitle={(e) => {
onUpdateHistory({
appId: chatData.appId,
chatId: e.chatId,
customTitle: e.title,
shareId,
outLinkUid
});
}}
/>
);
@@ -280,21 +227,7 @@ const OutLink = (
</DrawerContent>
</Drawer>
);
}, [
chatData.app.avatar,
chatData.app.name,
chatData.appId,
isOpenSlider,
isPc,
onClearHistories,
onCloseSlider,
onDelHistory,
onUpdateHistory,
outLinkUid,
shareId,
showHistory,
t
]);
}, [isOpenSlider, isPc, onCloseSlider, showHistory, t]);
const loading = isLoading;
@@ -329,43 +262,21 @@ const OutLink = (
) : null}
{/* chat box */}
<Box flex={1} bg={'white'}>
{chatData.app.type === AppTypeEnum.plugin ? (
{isPlugin ? (
<CustomPluginRunBox
pluginInputs={chatData.app.pluginInputs}
variablesForm={variablesForm}
histories={chatRecords}
setHistories={setChatRecords}
appId={chatData.appId}
tab={pluginRunTab}
setTab={setPluginRunTab}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
onNewChat={() => onChangeChatId(getNanoid())}
onStartChat={startChat}
/>
) : (
<ChatBox
ScrollData={ScrollData}
ref={ChatBoxRef}
chatHistories={chatRecords}
setChatHistories={setChatRecords}
variablesForm={variablesForm}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
chatConfig={chatData.app?.chatConfig}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
feedbackType={'user'}
onStartChat={startChat}
onDelMessage={({ contentId }) =>
delChatRecordById({
contentId,
appId: chatData.appId,
chatId,
shareId,
outLinkUid
})
}
appId={chatData.appId}
chatId={chatId}
shareId={shareId}
outLinkUid={outLinkUid}
chatType="share"
showRawSource={showRawSource}
showNodeStatus={showNodeStatus}
@@ -380,29 +291,57 @@ const OutLink = (
};
const Render = (props: Props) => {
const { shareId, authToken, customUid } = props;
const { shareId, authToken, customUid, appId } = props;
const { localUId, loaded } = useShareChatStore();
const { source, chatId, setSource, setAppId, setOutLinkAuthData } = useChatStore();
const [isLoaded, setIsLoaded] = useState(false);
const contextParams = useMemo(() => {
const chatHistoryProviderParams = useMemo(() => {
return { shareId, outLinkUid: authToken || customUid || localUId };
}, [authToken, customUid, localUId, shareId]);
const chatRecordProviderParams = useMemo(() => {
return {
appId,
shareId,
outLinkUid: chatHistoryProviderParams.outLinkUid,
chatId,
type: GetChatTypeEnum.outLink
};
}, [appId, chatHistoryProviderParams.outLinkUid, chatId, shareId]);
useMount(() => {
setIsLoaded(true);
});
const systemLoaded = isLoaded && loaded && contextParams.outLinkUid;
return (
<>
{systemLoaded ? (
<ChatContextProvider params={contextParams}>
<OutLink {...props} outLinkUid={contextParams.outLinkUid} />;
</ChatContextProvider>
) : (
<NextHead title="Loading..." />
)}
</>
setSource('share');
});
const systemLoaded = isLoaded && loaded && chatHistoryProviderParams.outLinkUid;
// Set outLinkAuthData
useEffect(() => {
setOutLinkAuthData({
shareId,
outLinkUid: chatHistoryProviderParams.outLinkUid
});
return () => {
setOutLinkAuthData({});
};
}, [chatHistoryProviderParams.outLinkUid, setOutLinkAuthData, shareId]);
// Watch appId
useEffect(() => {
setAppId(appId);
}, [appId, setAppId]);
return source === ChatSourceEnum.share ? (
<ChatContextProvider params={chatHistoryProviderParams}>
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<OutLink {...props} />
</ChatRecordContextProvider>
</ChatItemContextProvider>
</ChatContextProvider>
) : (
<NextHead title={props.appName} desc={props.appIntro} icon={props.appAvatar} />
);
};
@@ -433,6 +372,7 @@ export async function getServerSideProps(context: any) {
return {
props: {
appId: String(app?.appId?._id) ?? '',
appName: app?.appId?.name ?? 'AI',
appAvatar: app?.appId?.avatar ?? '',
appIntro: app?.appId?.intro ?? 'AI',

View File

@@ -1,9 +1,8 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import NextHead from '@/components/common/NextHead';
import { delChatRecordById, getTeamChatInfo } from '@/web/core/chat/api';
import { getTeamChatInfo } from '@/web/core/chat/api';
import { useRouter } from 'next/router';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
import { useToast } from '@fastgpt/web/hooks/useToast';
import SideBar from '@/components/SideBar';
import PageContainer from '@/components/PageContainer';
import { getMyTokensApps } from '@/web/core/chat/api';
@@ -25,10 +24,16 @@ import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData, GetChatTypeEnum } from '@/global/core/chat/constants';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useChat } from '@/components/core/chat/ChatContainer/useChat';
import dynamic from 'next/dynamic';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
import ChatRecordContextProvider, {
ChatRecordContext
} from '@/web/core/chat/context/chatRecordContext';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import { useMount } from 'ahooks';
import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
type Props = { appId: string; chatId: string; teamId: string; teamToken: string };
@@ -38,51 +43,59 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const router = useRouter();
const {
teamId = '',
appId = '',
chatId = '',
appId: appIdQuery = '',
teamToken,
...customVariables
} = router.query as Props & {
[key: string]: string;
};
const { toast } = useToast();
const theme = useTheme();
const { isPc } = useSystem();
const { outLinkAuthData, appId, chatId } = useChatStore();
const isOpenSlider = useContextSelector(ChatContext, (v) => v.isOpenSlider);
const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider);
const forbidLoadChat = useContextSelector(ChatContext, (v) => v.forbidLoadChat);
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
const onUpdateHistoryTitle = useContextSelector(ChatContext, (v) => v.onUpdateHistoryTitle);
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
// get chat app info
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const { loading: isLoading } = useRequest2(
async () => {
if (!appId || forbidLoadChat.current) return;
const {
onUpdateHistoryTitle,
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId
} = useContextSelector(ChatContext, (v) => v);
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken });
const params = useMemo(() => {
return {
appId,
chatId,
teamId,
teamToken,
type: GetChatTypeEnum.team
};
}, [appId, chatId, teamId, teamToken]);
const {
ChatBoxRef,
variablesForm,
pluginRunTab,
setPluginRunTab,
resetVariables,
chatRecords,
ScrollData,
setChatRecords,
totalRecordsCount
} = useChat(params);
setChatData(res);
setChatBoxData(res);
// reset chat records
resetVariables({
variables: res.variables
});
},
{
manual: false,
refreshDeps: [teamId, teamToken, appId, chatId],
onError(e: any) {
console.log(e);
if (chatId) {
onChangeChatId();
}
},
onFinally() {
forbidLoadChat.current = false;
}
}
);
const startChat = useCallback(
async ({
@@ -143,58 +156,9 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
]
);
// get chat app info
const { loading: isLoading } = useRequest2(
async () => {
if (!appId || forbidLoadChat.current) return;
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken });
setChatData(res);
// reset chat records
resetVariables({
variables: res.variables
});
},
{
manual: false,
refreshDeps: [teamId, teamToken, appId, chatId],
onError(e: any) {
console.log(e);
if (chatId) {
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
}
);
const RenderHistoryList = useMemo(() => {
const Children = (
<ChatHistorySlider
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('common:core.chat.Confirm to clear history')}
onDelHistory={(e) => onDelHistory({ ...e, appId, teamId, teamToken })}
onClearHistory={() => {
onClearHistories({ appId, teamId, teamToken });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({ ...e, teamId, teamToken, appId });
}}
onSetCustomTitle={async (e) => {
onUpdateHistory({
appId,
chatId: e.chatId,
customTitle: e.title,
teamId,
teamToken
});
}}
/>
<ChatHistorySlider confirmClearText={t('common:core.chat.Confirm to clear history')} />
);
return isPc || !appId ? (
@@ -211,20 +175,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
<DrawerContent maxWidth={'75vw'}>{Children}</DrawerContent>
</Drawer>
);
}, [
appId,
chatData.app.avatar,
chatData.app.name,
isOpenSlider,
isPc,
onClearHistories,
onCloseSlider,
onDelHistory,
onUpdateHistory,
t,
teamId,
teamToken
]);
}, [appId, isOpenSlider, isPc, onCloseSlider, t]);
const loading = isLoading;
@@ -261,41 +212,19 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
<Box flex={1}>
{chatData.app.type === AppTypeEnum.plugin ? (
<CustomPluginRunBox
pluginInputs={chatData.app.pluginInputs}
variablesForm={variablesForm}
histories={chatRecords}
setHistories={setChatRecords}
appId={chatData.appId}
tab={pluginRunTab}
setTab={setPluginRunTab}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
onNewChat={() => onChangeChatId(getNanoid())}
onStartChat={startChat}
/>
) : (
<ChatBox
ref={ChatBoxRef}
ScrollData={ScrollData}
chatHistories={chatRecords}
setChatHistories={setChatRecords}
variablesForm={variablesForm}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
chatConfig={chatData.app?.chatConfig}
appId={appId}
chatId={chatId}
outLinkAuthData={outLinkAuthData}
feedbackType={'user'}
onStartChat={startChat}
onDelMessage={({ contentId }) =>
delChatRecordById({
contentId,
appId: chatData.appId,
chatId,
teamId,
teamToken
})
}
appId={chatData.appId}
chatId={chatId}
teamId={teamId}
teamToken={teamToken}
chatType="team"
showRawSource
showNodeStatus
@@ -311,9 +240,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const Render = (props: Props) => {
const { teamId, appId, teamToken } = props;
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { source, chatId, setSource, setAppId, setOutLinkAuthData } = useChatStore();
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
async () => {
@@ -323,34 +251,61 @@ const Render = (props: Props) => {
return [];
},
{
manual: false
manual: true
}
);
// 初始化聊天框
useEffect(() => {
(async () => {
if (appId || myApps.length === 0) return;
useMount(async () => {
setSource('team');
router.replace({
query: {
...router.query,
appId: myApps[0]._id,
chatId: ''
}
});
})();
}, [appId, loadMyApps, myApps, router, t, toast]);
const apps = await loadMyApps();
if (appId || apps.length === 0) return;
router.replace({
query: {
...router.query,
appId: apps[0]._id
}
});
});
// Watch appId
useEffect(() => {
setAppId(appId);
}, [appId, setAppId]);
useEffect(() => {
setOutLinkAuthData({
teamId,
teamToken
});
return () => {
setOutLinkAuthData({});
};
}, [teamId, teamToken, setOutLinkAuthData]);
const contextParams = useMemo(() => {
return { teamId, appId, teamToken };
}, [teamId, appId, teamToken]);
const chatRecordProviderParams = useMemo(() => {
return {
appId,
chatId,
teamId,
teamToken,
type: GetChatTypeEnum.team
};
}, [appId, chatId, teamId, teamToken]);
return (
return source === ChatSourceEnum.team ? (
<ChatContextProvider params={contextParams}>
<Chat {...props} myApps={myApps} />
<ChatItemContextProvider>
<ChatRecordContextProvider params={chatRecordProviderParams}>
<Chat {...props} myApps={myApps} />
</ChatRecordContextProvider>
</ChatItemContextProvider>
</ChatContextProvider>
);
) : null;
};
export async function getServerSideProps(context: any) {

View File

@@ -14,6 +14,8 @@ import { uploadFile2DB } from '@/web/common/file/controller';
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
import { ImportSourceItemType } from '@/web/core/dataset/type';
import { useI18n } from '@/web/context/I18n';
import { useContextSelector } from 'use-context-selector';
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
export type SelectFileItemType = {
fileId: string;
@@ -41,6 +43,7 @@ const FileSelector = ({
const { toast } = useToast();
const { feConfigs } = useSystemStore();
const datasetId = useContextSelector(DatasetPageContext, (v) => v.datasetId);
const maxCount = feConfigs?.uploadFileMaxAmount || 1000;
const maxSize = (feConfigs?.uploadFileMaxSize || 1024) * 1024 * 1024;
@@ -92,6 +95,9 @@ const FileSelector = ({
const { fileId: uploadFileId } = await uploadFile2DB({
file,
bucketName: BucketNameEnum.dataset,
data: {
datasetId
},
percentListen: (e) => {
setSelectFiles((state) =>
state.map((item) =>

View File

@@ -136,14 +136,13 @@ const Dataset = () => {
{isPc && RenderSearchInput}
{userInfo?.team?.permission.hasWritePer && (
{(folderDetail
? folderDetail.permission.hasWritePer
: userInfo?.team?.permission.hasWritePer) && (
<Box pl={[0, 4]}>
<MyMenu
size="md"
offset={[0, 10]}
width={120}
iconSize="2rem"
iconRadius="6px"
placement="bottom-end"
Button={
<Button variant={'primary'} px="0">
<Flex alignItems={'center'} px={5}>

View File

@@ -1,7 +1,6 @@
import React, { useCallback, useEffect } from 'react';
import { useRouter } from 'next/router';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useUserStore } from '@/web/support/user/useUserStore';
import { clearToken, setToken } from '@/web/support/user/auth';
import { postFastLogin } from '@/web/support/user/api';
@@ -19,7 +18,6 @@ const FastLogin = ({
token: string;
callbackUrl: string;
}) => {
const { setLastChatId, setLastChatAppId } = useChatStore();
const { setUserInfo } = useUserStore();
const router = useRouter();
const { toast } = useToast();
@@ -29,15 +27,11 @@ const FastLogin = ({
setToken(res.token);
setUserInfo(res.user);
// init store
setLastChatId('');
setLastChatAppId('');
setTimeout(() => {
router.push(decodeURIComponent(callbackUrl));
}, 100);
},
[setLastChatId, setLastChatAppId, setUserInfo, router, callbackUrl]
[setUserInfo, router, callbackUrl]
);
const authCode = useCallback(

View File

@@ -15,7 +15,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useRouter } from 'next/router';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useChatStore } from '@/web/core/chat/context/useChatStore';
import LoginForm from './components/LoginForm/LoginForm';
import dynamic from 'next/dynamic';
import { serviceSideProps } from '@/web/common/utils/i18n';
@@ -44,7 +44,7 @@ const Login = ({ ChineseRedirectUrl }: { ChineseRedirectUrl: string }) => {
const { feConfigs } = useSystemStore();
const [pageType, setPageType] = useState<`${LoginPageTypeEnum}`>();
const { setUserInfo } = useUserStore();
const { setLastChatId, setLastChatAppId } = useChatStore();
const { setLastChatAppId } = useChatStore();
const { isOpen, onOpen, onClose } = useDisclosure();
const { isPc } = useSystem();
@@ -59,17 +59,13 @@ const Login = ({ ChineseRedirectUrl }: { ChineseRedirectUrl: string }) => {
const loginSuccess = useCallback(
(res: ResLogin) => {
// init store
setLastChatId('');
setLastChatAppId('');
setUserInfo(res.user);
setToken(res.token);
setTimeout(() => {
router.push(lastRoute ? decodeURIComponent(lastRoute) : '/app/list');
}, 300);
},
[lastRoute, router, setLastChatId, setLastChatAppId, setUserInfo]
[lastRoute, router, setUserInfo]
);
function DynamicComponent({ type }: { type: `${LoginPageTypeEnum}` }) {
@@ -95,6 +91,9 @@ const Login = ({ ChineseRedirectUrl }: { ChineseRedirectUrl: string }) => {
setPageType(
feConfigs?.oauth?.wechat ? LoginPageTypeEnum.wechat : LoginPageTypeEnum.passwordLogin
);
// init store
setLastChatAppId('');
}, [feConfigs.oauth]);
const {

View File

@@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useRef } from 'react';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useUserStore } from '@/web/support/user/useUserStore';
import { clearToken, setToken } from '@/web/support/user/auth';
import { oauthLogin } from '@/web/support/user/api';
@@ -17,7 +16,6 @@ let isOauthLogging = false;
const provider = () => {
const { t } = useTranslation();
const { loginStore } = useSystemStore();
const { setLastChatId, setLastChatAppId } = useChatStore();
const { setUserInfo } = useUserStore();
const router = useRouter();
const { code, state, error } = router.query as { code: string; state: string; error?: string };
@@ -28,13 +26,9 @@ const provider = () => {
setToken(res.token);
setUserInfo(res.user);
// init store
setLastChatId('');
setLastChatAppId('');
router.push(loginStore?.lastRoute ? decodeURIComponent(loginStore?.lastRoute) : '/app/list');
},
[setLastChatId, setLastChatAppId, setUserInfo, router, loginStore?.lastRoute]
[setUserInfo, router, loginStore?.lastRoute]
);
const authCode = useCallback(

View File

@@ -1,7 +1,6 @@
import React, { useCallback, useEffect } from 'react';
import { useRouter } from 'next/router';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useUserStore } from '@/web/support/user/useUserStore';
import { clearToken, setToken } from '@/web/support/user/auth';
import { ssoLogin } from '@/web/support/user/api';
@@ -15,7 +14,6 @@ let isOauthLogging = false;
const provider = () => {
const { t } = useTranslation();
const { setLastChatId, setLastChatAppId } = useChatStore();
const { setUserInfo } = useUserStore();
const router = useRouter();
const { query } = router;
@@ -27,11 +25,9 @@ const provider = () => {
setToken(res.token);
setUserInfo(res.user);
// init store
setLastChatId('');
setLastChatAppId('');
router.push('/app/list');
},
[setLastChatId, setLastChatAppId, setUserInfo, router]
[setUserInfo, router]
);
const handleSSO = useCallback(async () => {