App run node update (#2542)

* feat(workflow): allow apps to be invoked like plugins (#2521)

* feat(workflow): allow apps to be invoked like plugins

* fix type

* Encapsulate SSE response methods (#2530)

* perf: sse response fn

* perf: sse response

* fix: ts

* perf: not ssl copy

* perf: myselect auto scroll

* perf: run app code

* fix: app plugin (#2538)

---------

Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
Archer
2024-08-27 16:43:19 +08:00
committed by GitHub
parent 67445b40bc
commit 450167c951
67 changed files with 706 additions and 4899 deletions

View File

@@ -114,7 +114,7 @@ export const onCreateApp = async ({
type,
version: 'v2',
pluginData,
...(type === AppTypeEnum.plugin && { 'pluginData.nodeVersion': defaultNodeVersion })
'pluginData.nodeVersion': defaultNodeVersion
}
],
{ session }

View File

@@ -56,7 +56,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig?.cronString
? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig)
: null,
...(app.type === AppTypeEnum.plugin && { 'pluginData.nodeVersion': _id })
'pluginData.nodeVersion': _id
},
{
session

View File

@@ -73,7 +73,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
scheduledTriggerNextTime: scheduledTriggerConfig?.cronString
? getNextTimeByCronStringAndTimezone(scheduledTriggerConfig)
: null,
...(app.type === AppTypeEnum.plugin && { 'pluginData.nodeVersion': _id })
'pluginData.nodeVersion': _id
});
});

View File

@@ -28,6 +28,8 @@ import {
storeNodes2RuntimeNodes
} 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 { getNanoid } from '@fastgpt/global/common/string/tools';
export type Props = {
messages: ChatCompletionMessageParam[];
@@ -95,6 +97,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
runtimeNodes = rewriteNodeOutputByHistories(chatMessages, runtimeNodes);
const workflowResponseWrite = getWorkflowResponseWrite({
res,
detail: true,
streamResponse: true,
id: getNanoid(24)
});
/* start process */
const { flowResponses, flowUsages } = await dispatchWorkFlow({
@@ -112,8 +120,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatConfig,
histories: chatMessages,
stream: true,
detail: true,
maxRunTimes: 200
maxRunTimes: 200,
workflowStreamResponse: workflowResponseWrite
});
responseWrite({

View File

@@ -54,7 +54,6 @@ async function handler(
chatConfig: defaultApp.chatConfig,
histories: [],
stream: false,
detail: true,
maxRunTimes: 200
});

View File

@@ -48,8 +48,6 @@ import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { UserChatItemType } from '@fastgpt/global/core/chat/type';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { dispatchWorkFlowV1 } from '@fastgpt/service/core/workflow/dispatchV1';
import { setEntryEntries } from '@fastgpt/service/core/workflow/dispatchV1/utils';
import { NextAPI } from '@/service/middleware/entry';
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
@@ -65,6 +63,7 @@ import {
} from '@fastgpt/global/core/app/plugin/utils';
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
import { rewriteNodeOutputByHistories } from '@fastgpt/global/core/workflow/runtime/utils';
import { getWorkflowResponseWrite } from '@fastgpt/service/core/workflow/dispatch/utils';
type FastGptWebChatProps = {
chatId?: string; // undefined: get histories from messages, '': new chat, 'xxxxx': get histories from db
@@ -243,6 +242,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
runtimeNodes = rewriteNodeOutputByHistories(newHistories, runtimeNodes);
const workflowResponseWrite = getWorkflowResponseWrite({
res,
detail,
streamResponse: stream,
id: chatId || getNanoid(24)
});
/* start flow controller */
const { flowResponses, flowUsages, assistantResponses, newVariables } = await (async () => {
if (app.version === 'v2') {
@@ -263,31 +269,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
chatConfig,
histories: newHistories,
stream,
detail,
maxRunTimes: 200
maxRunTimes: 200,
workflowStreamResponse: workflowResponseWrite
});
}
return dispatchWorkFlowV1({
res,
mode: 'chat',
user,
teamId: String(teamId),
tmbId: String(tmbId),
appId: String(app._id),
chatId,
responseChatItemId,
//@ts-ignore
modules: setEntryEntries(app.modules),
variables,
inputFiles: files,
histories: newHistories,
startParams: {
userChatInput: text
},
stream,
detail,
maxRunTimes: 200
});
return Promise.reject('请升级工作流');
})();
// save chat
@@ -346,9 +332,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
: filterPublicNodeResponseData({ flowResponses });
if (stream) {
responseWrite({
res,
event: detail ? SseResponseEventEnum.answer : undefined,
workflowResponseWrite({
event: SseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: null,
finish_reason: 'stop'
@@ -362,10 +347,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (detail) {
if (responseDetail || isPlugin) {
responseWrite({
res,
workflowResponseWrite({
event: SseResponseEventEnum.flowResponses,
data: JSON.stringify(feResponseData)
data: feResponseData
});
}
}

View File

@@ -142,16 +142,15 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
searchVal?: string;
}) => {
if (type === TemplateTypeEnum.teamPlugin) {
const plugins = await getTeamPlugTemplates({
const teamApps = await getTeamPlugTemplates({
parentId,
searchKey: searchVal,
type: [AppTypeEnum.folder, AppTypeEnum.httpPlugin, AppTypeEnum.plugin]
searchKey: searchVal
}).then((res) => res.filter((app) => app.id !== appId));
return plugins.map<NodeTemplateListItemType>((plugin) => {
const member = members.find((member) => member.tmbId === plugin.tmbId);
return teamApps.map<NodeTemplateListItemType>((app) => {
const member = members.find((member) => member.tmbId === app.tmbId);
return {
...plugin,
...app,
author: member?.memberName,
authorAvatar: member?.avatar
};
@@ -266,7 +265,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
},
{
icon: 'core/modules/teamPlugin',
label: t('common:core.module.template.Team Plugin'),
label: t('common:core.module.template.Team app'),
value: TemplateTypeEnum.teamPlugin
}
]}
@@ -302,7 +301,11 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
<Input
h={'full'}
bg={'myGray.50'}
placeholder={t('common:plugin.Search plugin')}
placeholder={
templateType === TemplateTypeEnum.teamPlugin
? t('common:plugin.Search_app')
: t('common:plugin.Search plugin')
}
onChange={(e) => setSearchKey(e.target.value)}
/>
</InputGroup>
@@ -424,7 +427,10 @@ const RenderList = React.memo(function RenderList({
const templateNode = await (async () => {
try {
// get plugin preview module
if (template.flowNodeType === FlowNodeTypeEnum.pluginModule) {
if (
template.flowNodeType === FlowNodeTypeEnum.pluginModule ||
template.flowNodeType === FlowNodeTypeEnum.appModule
) {
setLoading(true);
const res = await getPreviewPluginNode({ appId: template.id });

View File

@@ -38,6 +38,7 @@ const nodeTypes: Record<FlowNodeTypeEnum, any> = {
[FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./nodes/NodeExtract')),
[FlowNodeTypeEnum.httpRequest468]: dynamic(() => import('./nodes/NodeHttp')),
[FlowNodeTypeEnum.runApp]: NodeSimple,
[FlowNodeTypeEnum.appModule]: NodeSimple,
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./nodes/NodePluginIO/PluginInput')),
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./nodes/NodePluginIO/PluginOutput')),
[FlowNodeTypeEnum.pluginModule]: NodeSimple,

View File

@@ -19,7 +19,6 @@ import { storeNode2FlowNode, getLatestNodeTemplate } from '@/web/core/workflow/u
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
import { useI18n } from '@/web/context/I18n';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
@@ -84,7 +83,10 @@ const NodeCard = (props: Props) => {
const { data: nodeTemplate, runAsync: getNodeLatestTemplate } = useRequest2(
async () => {
if (node?.flowNodeType === FlowNodeTypeEnum.pluginModule) {
if (
node?.flowNodeType === FlowNodeTypeEnum.pluginModule ||
node?.flowNodeType === FlowNodeTypeEnum.appModule
) {
if (!node?.pluginId) return;
const template = await getPreviewPluginNode({ appId: node.pluginId });
@@ -115,7 +117,10 @@ const NodeCard = (props: Props) => {
const template = moduleTemplatesFlat.find((item) => item.flowNodeType === node?.flowNodeType);
if (!node || !template) return;
if (node?.flowNodeType === FlowNodeTypeEnum.pluginModule) {
if (
node?.flowNodeType === FlowNodeTypeEnum.pluginModule ||
node?.flowNodeType === FlowNodeTypeEnum.appModule
) {
if (!node.pluginId) return;
onResetNode({
id: nodeId,
@@ -298,11 +303,6 @@ const MenuRender = React.memo(function MenuRender({
const { t } = useTranslation();
const { openDebugNode, DebugInputModal } = useDebug();
const { openConfirm: onOpenConfirmDeleteNode, ConfirmModal: ConfirmDeleteModal } = useConfirm({
content: t('common:core.module.Confirm Delete Node'),
type: 'delete'
});
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
const { computedNewNodeName } = useWorkflowUtils();
@@ -420,7 +420,6 @@ const MenuRender = React.memo(function MenuRender({
</Box>
))}
</Box>
<ConfirmDeleteModal />
<DebugInputModal />
</>
);
@@ -429,7 +428,6 @@ const MenuRender = React.memo(function MenuRender({
menuForbid?.copy,
menuForbid?.delete,
t,
ConfirmDeleteModal,
DebugInputModal,
openDebugNode,
nodeId,

View File

@@ -20,6 +20,10 @@ const RenderList: {
types: [FlowNodeInputTypeEnum.input],
Component: dynamic(() => import('./templates/TextInput'))
},
{
types: [FlowNodeInputTypeEnum.select],
Component: dynamic(() => import('./templates/Select'))
},
{
types: [FlowNodeInputTypeEnum.numberInput],
Component: dynamic(() => import('./templates/NumberInput'))

View File

@@ -52,7 +52,6 @@ export const getScheduleTriggerApp = async () => {
chatConfig: defaultApp.chatConfig,
histories: [],
stream: false,
detail: false,
maxRunTimes: 200
});
pushChatUsage({

View File

@@ -1,6 +1,7 @@
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useCallback } from 'react';
import { hasHttps } from '@fastgpt/web/common/system/utils';
/**
* copy text data
@@ -16,7 +17,7 @@ export const useCopyData = () => {
duration = 1000
) => {
try {
if (navigator.clipboard) {
if (hasHttps() && navigator.clipboard) {
await navigator.clipboard.writeText(data);
} else {
throw new Error('');

View File

@@ -7,7 +7,7 @@ import type {
} from '@fastgpt/global/core/workflow/type/node';
import { getMyApps } from '../api';
import type { ListAppBody } from '@/pages/api/core/app/list';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { defaultNodeVersion, FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import type { GetPreviewNodeQuery } from '@/pages/api/core/app/plugin/getPreviewNode';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
@@ -23,12 +23,15 @@ export const getTeamPlugTemplates = (data?: ListAppBody) =>
pluginId: app._id,
isFolder: app.type === AppTypeEnum.folder || app.type === AppTypeEnum.httpPlugin,
templateType: FlowNodeTemplateTypeEnum.teamApp,
flowNodeType: FlowNodeTypeEnum.pluginModule,
flowNodeType:
app.type === AppTypeEnum.workflow
? FlowNodeTypeEnum.appModule
: FlowNodeTypeEnum.pluginModule,
avatar: app.avatar,
name: app.name,
intro: app.intro,
showStatus: false,
version: app.pluginData?.nodeVersion || '481',
version: app.pluginData?.nodeVersion || defaultNodeVersion,
isTool: true
}))
);