Plugin runtime (#2050)

* feat: plugin run (#1950)

* feat: plugin run

* fix

* ui

* fix

* change user input type

* fix

* fix

* temp

* split out plugin chat

* perf: chatbox

* perf: chatbox

* fix: plugin runtime (#2032)

* fix: plugin runtime

* fix

* fix build

* fix build

* perf: chat send prompt

* perf: chat log ux

* perf: chatbox context and share page plugin runtime

* perf: plugin run time config

* fix: ts

* feat: doc

* perf: isPc check

* perf: variable input render

* feat: app search

* fix: response box height

* fix: phone ui

* perf: lock

* perf: plugin route

* fix: chat (#2049)

---------

Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
This commit is contained in:
Archer
2024-07-15 22:50:48 +08:00
committed by GitHub
parent 090c880860
commit b5c98a4f63
126 changed files with 5012 additions and 4317 deletions

View File

@@ -3,7 +3,11 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { sseErrRes, jsonRes } from '@fastgpt/service/common/response';
import { addLog } from '@fastgpt/service/common/system/log';
import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
import {
ChatItemValueTypeEnum,
ChatRoleEnum,
ChatSourceEnum
} from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d';
@@ -28,10 +32,10 @@ import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
import {
concatHistories,
filterPublicNodeResponseData,
getChatTitleFromChatMessage,
removeEmptyUserInput
} from '@fastgpt/global/core/chat/utils';
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
import { connectToDatabase } from '@/service/mongo';
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoApp } from '@fastgpt/service/core/app/schema';
@@ -49,9 +53,17 @@ import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatchV1/utils
import { NextAPI } from '@/service/middleware/entry';
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { updatePluginInputByVariables } from '@fastgpt/global/core/workflow/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import {
getPluginInputsFromStoreNodes,
getPluginRunContent
} from '@fastgpt/global/core/app/plugin/utils';
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
type FastGptWebChatProps = {
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
chatId?: string; // undefined: get histories from messages, '': new chat, 'xxxxx': get histories from db
appId?: string;
};
@@ -59,14 +71,11 @@ export type Props = ChatCompletionCreateParams &
FastGptWebChatProps &
OutLinkChatAuthProps & {
messages: ChatCompletionMessageParam[];
responseChatItemId?: string;
stream?: boolean;
detail?: boolean;
variables: Record<string, any>;
variables: Record<string, any>; // Global variables or plugin inputs
};
export type ChatResponseType = {
newChatId: string;
quoteLen?: number;
};
type AuthResponseType = {
teamId: string;
@@ -89,7 +98,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
res.end();
});
const {
let {
chatId,
appId,
// share chat
@@ -98,41 +107,39 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// team chat
teamId: spaceTeamId,
teamToken,
stream = false,
detail = false,
messages = [],
variables = {}
variables = {},
responseChatItemId = getNanoid()
} = req.body as Props;
try {
const originIp = requestIp.getClientIp(req);
await connectToDatabase();
// body data check
if (!messages) {
throw new Error('Prams Error');
}
const originIp = requestIp.getClientIp(req);
const startTime = Date.now();
try {
if (!Array.isArray(messages)) {
throw new Error('messages is not array');
}
if (messages.length === 0) {
throw new Error('messages is empty');
}
let startTime = Date.now();
// Web chat params: [Human, AI]
/*
Web params: chatId + [Human]
API params: chatId + [Human]
API params: [histories, Human]
*/
const chatMessages = GPTMessages2Chats(messages);
if (chatMessages[chatMessages.length - 1].obj !== ChatRoleEnum.Human) {
chatMessages.pop();
}
// user question
const question = chatMessages.pop() as UserChatItemType;
if (!question) {
throw new Error('Question is empty');
}
// Computed start hook params
const startHookText = (() => {
// Chat
const userQuestion = chatMessages[chatMessages.length - 1] as UserChatItemType | undefined;
if (userQuestion) return chatValue2RuntimePrompt(userQuestion.value).text;
const { text, files } = chatValue2RuntimePrompt(question.value);
// plugin
return JSON.stringify(variables);
})();
/*
1. auth app permission
@@ -149,7 +156,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
outLinkUid,
chatId,
ip: originIp,
question: text
question: startHookText
});
}
// team space chat
@@ -169,8 +176,45 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatId
});
})();
const isPlugin = app.type === AppTypeEnum.plugin;
// 1. get and concat history; 2. get app workflow
// Check message type
if (isPlugin) {
detail = true;
} else {
if (messages.length === 0) {
throw new Error('messages is empty');
}
}
// Get obj=Human history
const userQuestion: UserChatItemType = (() => {
if (isPlugin) {
return {
dataId: getNanoid(24),
obj: ChatRoleEnum.Human,
value: [
{
type: ChatItemValueTypeEnum.text,
text: {
content: getPluginRunContent({
pluginInputs: getPluginInputsFromStoreNodes(app.modules)
})
}
}
]
};
}
const latestHumanChat = chatMessages.pop() as UserChatItemType | undefined;
if (!latestHumanChat) {
throw new Error('User question is empty');
}
return latestHumanChat;
})();
const { text, files } = chatValue2RuntimePrompt(userQuestion.value);
// Get and concat history;
const limit = getMaxHistoryLimitFromNodes(app.modules);
const [{ histories }, { nodes, edges, chatConfig }] = await Promise.all([
getChatItems({
@@ -182,7 +226,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
getAppLatestVersion(app._id, app)
]);
const newHistories = concatHistories(histories, chatMessages);
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId;
// Get runtimeNodes
const runtimeNodes = isPlugin
? updatePluginInputByVariables(
storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
variables
)
: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes));
/* start flow controller */
const { flowResponses, flowUsages, assistantResponses, newVariables } = await (async () => {
@@ -196,10 +247,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
app,
chatId,
responseChatItemId,
runtimeNodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
runtimeNodes,
runtimeEdges: initWorkflowEdgeStatus(edges),
variables,
query: removeEmptyUserInput(question.value),
query: removeEmptyUserInput(userQuestion.value),
histories: newHistories,
stream,
detail,
@@ -245,6 +296,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
return ChatSourceEnum.online;
})();
const newTitle = isPlugin
? variables.cTime ?? getSystemTime(user.timezone)
: getChatTitleFromChatMessage(userQuestion);
await saveChat({
chatId,
appId: app._id,
@@ -254,11 +309,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
appChatConfig: chatConfig,
variables: newVariables,
isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time
newTitle,
shareId,
outLinkUid: outLinkUserId,
source,
content: [
question,
userQuestion,
{
dataId: responseChatItemId,
obj: ChatRoleEnum.AI,
@@ -295,7 +351,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
});
if (detail) {
if (responseDetail) {
if (responseDetail || isPlugin) {
responseWrite({
res,
event: SseResponseEventEnum.flowResponses,
@@ -312,6 +368,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
return assistantResponses[0].text?.content;
return assistantResponses;
})();
res.json({
...(detail ? { responseData: feResponseData, newVariables } : {}),
id: chatId || '',