mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-28 09:03:53 +00:00
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:
@@ -114,7 +114,7 @@ export const onCreateApp = async ({
|
||||
type,
|
||||
version: 'v2',
|
||||
pluginData,
|
||||
...(type === AppTypeEnum.plugin && { 'pluginData.nodeVersion': defaultNodeVersion })
|
||||
'pluginData.nodeVersion': defaultNodeVersion
|
||||
}
|
||||
],
|
||||
{ session }
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -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({
|
||||
|
@@ -54,7 +54,6 @@ async function handler(
|
||||
chatConfig: defaultApp.chatConfig,
|
||||
histories: [],
|
||||
stream: false,
|
||||
detail: true,
|
||||
maxRunTimes: 200
|
||||
});
|
||||
|
||||
|
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -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 });
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -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'))
|
||||
|
@@ -52,7 +52,6 @@ export const getScheduleTriggerApp = async () => {
|
||||
chatConfig: defaultApp.chatConfig,
|
||||
histories: [],
|
||||
stream: false,
|
||||
detail: false,
|
||||
maxRunTimes: 200
|
||||
});
|
||||
pushChatUsage({
|
||||
|
@@ -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('');
|
||||
|
@@ -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
|
||||
}))
|
||||
);
|
||||
|
Reference in New Issue
Block a user