mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 21:13:50 +00:00
Concat plugin to app (#1799)
This commit is contained in:
@@ -3,19 +3,10 @@ import { AppTTSConfigType, AppWhisperConfigType } from './type';
|
||||
export enum AppTypeEnum {
|
||||
folder = 'folder',
|
||||
simple = 'simple',
|
||||
advanced = 'advanced'
|
||||
workflow = 'advanced',
|
||||
plugin = 'plugin',
|
||||
httpPlugin = 'httpPlugin'
|
||||
}
|
||||
export const AppTypeMap = {
|
||||
[AppTypeEnum.folder]: {
|
||||
label: 'folder'
|
||||
},
|
||||
[AppTypeEnum.simple]: {
|
||||
label: 'simple'
|
||||
},
|
||||
[AppTypeEnum.advanced]: {
|
||||
label: 'advanced'
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultTTSConfig: AppTTSConfigType = { type: 'web' };
|
||||
|
||||
|
24
packages/global/core/app/controller.d.ts
vendored
Normal file
24
packages/global/core/app/controller.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ParentIdType } from 'common/parentFolder/type';
|
||||
import { AppSchema } from './type';
|
||||
import { AppTypeEnum } from './constants';
|
||||
|
||||
export type CreateAppProps = {
|
||||
parentId?: ParentIdType;
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
intro?: string;
|
||||
type?: AppTypeEnum;
|
||||
modules: AppSchema['modules'];
|
||||
edges?: AppSchema['edges'];
|
||||
};
|
||||
export type CreateHttpPluginChildrenPros = Omit<CreateAppProps, 'type'> & {
|
||||
parentId: ParentIdType;
|
||||
name: string;
|
||||
intro: string;
|
||||
avatar: string;
|
||||
modules: AppSchema['modules'];
|
||||
edges: AppSchema['edges'];
|
||||
pluginData: {
|
||||
pluginUniId: string;
|
||||
};
|
||||
};
|
14
packages/global/core/app/httpPlugin/type.d.ts
vendored
Normal file
14
packages/global/core/app/httpPlugin/type.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export type PathDataType = {
|
||||
name: string;
|
||||
description: string;
|
||||
method: string;
|
||||
path: string;
|
||||
params: any[];
|
||||
request: any;
|
||||
response: any;
|
||||
};
|
||||
|
||||
export type OpenApiJsonSchema = {
|
||||
pathData: PathDataType[];
|
||||
serverPath: string;
|
||||
};
|
432
packages/global/core/app/httpPlugin/utils.ts
Normal file
432
packages/global/core/app/httpPlugin/utils.ts
Normal file
@@ -0,0 +1,432 @@
|
||||
import { getNanoid } from '../../../common/string/tools';
|
||||
import { OpenApiJsonSchema } from './type';
|
||||
import yaml from 'js-yaml';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import { FlowNodeInputItemType, FlowNodeOutputItemType } from '../../workflow/type/io';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeOutputTypeEnum } from '../../workflow/node/constant';
|
||||
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '../../workflow/constants';
|
||||
import { PluginInputModule } from '../../workflow/template/system/pluginInput';
|
||||
import { PluginOutputModule } from '../../workflow/template/system/pluginOutput';
|
||||
import { HttpModule468 } from '../../workflow/template/system/http468';
|
||||
import { HttpParamAndHeaderItemType } from '../../workflow/api';
|
||||
import { StoreNodeItemType } from '../../workflow/type';
|
||||
import { HttpImgUrl } from '../../../common/file/image/constants';
|
||||
import SwaggerParser from '@apidevtools/swagger-parser';
|
||||
import { getHandleId } from '../../workflow/utils';
|
||||
import { CreateHttpPluginChildrenPros } from '../controller';
|
||||
import { AppTypeEnum } from '../constants';
|
||||
import type { StoreEdgeItemType } from '../../workflow/type/edge';
|
||||
|
||||
export const str2OpenApiSchema = async (yamlStr = ''): Promise<OpenApiJsonSchema> => {
|
||||
try {
|
||||
const data = (() => {
|
||||
try {
|
||||
return JSON.parse(yamlStr);
|
||||
} catch (jsonError) {
|
||||
return yaml.load(yamlStr, { schema: yaml.FAILSAFE_SCHEMA });
|
||||
}
|
||||
})();
|
||||
const jsonSchema = (await SwaggerParser.parse(data)) as OpenAPIV3.Document;
|
||||
|
||||
const serverPath = jsonSchema.servers?.[0].url || '';
|
||||
const pathData = Object.keys(jsonSchema.paths)
|
||||
.map((path) => {
|
||||
const methodData: any = data.paths[path];
|
||||
return Object.keys(methodData)
|
||||
.filter((method) =>
|
||||
['get', 'post', 'put', 'delete', 'patch'].includes(method.toLocaleLowerCase())
|
||||
)
|
||||
.map((method) => {
|
||||
const methodInfo = methodData[method];
|
||||
if (methodInfo.deprecated) return;
|
||||
const result = {
|
||||
path,
|
||||
method,
|
||||
name: methodInfo.operationId || path,
|
||||
description: methodInfo.description || methodInfo.summary,
|
||||
params: methodInfo.parameters,
|
||||
request: methodInfo?.requestBody,
|
||||
response: methodInfo.responses
|
||||
};
|
||||
return result;
|
||||
});
|
||||
})
|
||||
.flat()
|
||||
.filter(Boolean) as OpenApiJsonSchema['pathData'];
|
||||
return { pathData, serverPath };
|
||||
} catch (err) {
|
||||
throw new Error('Invalid Schema');
|
||||
}
|
||||
};
|
||||
|
||||
export const getType = (schema: { type: string; items?: { type: string } }) => {
|
||||
const typeMap: { [key: string]: WorkflowIOValueTypeEnum } = {
|
||||
string: WorkflowIOValueTypeEnum.arrayString,
|
||||
number: WorkflowIOValueTypeEnum.arrayNumber,
|
||||
integer: WorkflowIOValueTypeEnum.arrayNumber,
|
||||
boolean: WorkflowIOValueTypeEnum.arrayBoolean,
|
||||
object: WorkflowIOValueTypeEnum.arrayObject
|
||||
};
|
||||
|
||||
if (schema?.type === 'integer') {
|
||||
return WorkflowIOValueTypeEnum.number;
|
||||
}
|
||||
|
||||
if (schema?.type === 'array' && schema?.items) {
|
||||
const itemType = typeMap[schema.items.type];
|
||||
if (itemType) {
|
||||
return itemType;
|
||||
}
|
||||
}
|
||||
|
||||
return schema?.type as WorkflowIOValueTypeEnum;
|
||||
};
|
||||
|
||||
export const httpApiSchema2Plugins = async ({
|
||||
parentId,
|
||||
apiSchemaStr = '',
|
||||
customHeader = ''
|
||||
}: {
|
||||
parentId: string;
|
||||
apiSchemaStr?: string;
|
||||
customHeader?: string;
|
||||
}): Promise<CreateHttpPluginChildrenPros[]> => {
|
||||
const jsonSchema = await str2OpenApiSchema(apiSchemaStr);
|
||||
|
||||
const baseUrl = jsonSchema.serverPath;
|
||||
|
||||
return jsonSchema.pathData.map((item) => {
|
||||
const pluginOutputId = getNanoid();
|
||||
const httpId = getNanoid();
|
||||
const pluginInputId = getNanoid();
|
||||
const inputIdMap = new Map();
|
||||
|
||||
const pluginOutputKey = 'result';
|
||||
|
||||
const properties = item.request?.content?.['application/json']?.schema?.properties;
|
||||
const propsKeys = properties ? Object.keys(properties) : [];
|
||||
|
||||
const pluginInputs: FlowNodeInputItemType[] = [
|
||||
...(item.params?.map((param: any) => {
|
||||
return {
|
||||
key: param.name,
|
||||
valueType: getType(param.schema),
|
||||
label: param.name,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
required: param.required,
|
||||
description: param.description,
|
||||
toolDescription: param.description,
|
||||
canEdit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true,
|
||||
inputType: true,
|
||||
isToolInput: true
|
||||
}
|
||||
};
|
||||
}) || []),
|
||||
...(propsKeys?.map((key) => {
|
||||
const prop = properties[key];
|
||||
return {
|
||||
key,
|
||||
valueType: getType(prop),
|
||||
label: key,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
required: false,
|
||||
description: prop.description,
|
||||
toolDescription: prop.description,
|
||||
canEdit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true,
|
||||
inputType: true,
|
||||
isToolInput: true
|
||||
}
|
||||
};
|
||||
}) || [])
|
||||
];
|
||||
|
||||
const pluginOutputs: FlowNodeOutputItemType[] = [
|
||||
...(item.params?.map((param: any) => {
|
||||
const id = getNanoid();
|
||||
inputIdMap.set(param.name, id);
|
||||
return {
|
||||
id,
|
||||
key: param.name,
|
||||
valueType: getType(param.schema),
|
||||
label: param.name,
|
||||
type: FlowNodeOutputTypeEnum.source
|
||||
};
|
||||
}) || []),
|
||||
...(propsKeys?.map((key) => {
|
||||
const id = getNanoid();
|
||||
inputIdMap.set(key, id);
|
||||
return {
|
||||
id,
|
||||
key,
|
||||
valueType: getType(properties[key]),
|
||||
label: key,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true
|
||||
};
|
||||
}) || [])
|
||||
];
|
||||
|
||||
const httpInputs: FlowNodeInputItemType[] = [
|
||||
...(item.params?.map((param: any) => {
|
||||
return {
|
||||
key: param.name,
|
||||
valueType: getType(param.schema),
|
||||
label: param.name,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
canEdit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
valueType: true
|
||||
},
|
||||
value: [pluginInputId, inputIdMap.get(param.name)]
|
||||
};
|
||||
}) || []),
|
||||
...(propsKeys?.map((key) => {
|
||||
return {
|
||||
key,
|
||||
valueType: getType(properties[key]),
|
||||
label: key,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
canEdit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
valueType: true
|
||||
},
|
||||
value: [pluginInputId, inputIdMap.get(key)]
|
||||
};
|
||||
}) || [])
|
||||
];
|
||||
|
||||
/* http node setting */
|
||||
const httpNodeParams: HttpParamAndHeaderItemType[] = [];
|
||||
const httpNodeHeaders: HttpParamAndHeaderItemType[] = [];
|
||||
let httpNodeBody = '{}';
|
||||
const requestUrl = `${baseUrl}${item.path}`;
|
||||
|
||||
if (item.params && item.params.length > 0) {
|
||||
for (const param of item.params) {
|
||||
if (param.in === 'header') {
|
||||
httpNodeHeaders.push({
|
||||
key: param.name,
|
||||
type: getType(param.schema) || WorkflowIOValueTypeEnum.string,
|
||||
value: `{{${param.name}}}`
|
||||
});
|
||||
} else if (param.in === 'body') {
|
||||
httpNodeBody = JSON.stringify(
|
||||
{ ...JSON.parse(httpNodeBody), [param.name]: `{{${param.name}}}` },
|
||||
null,
|
||||
2
|
||||
);
|
||||
} else if (param.in === 'query') {
|
||||
httpNodeParams.push({
|
||||
key: param.name,
|
||||
type: getType(param.schema) || WorkflowIOValueTypeEnum.string,
|
||||
value: `{{${param.name}}}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.request) {
|
||||
const properties = item.request?.content?.['application/json']?.schema?.properties || {};
|
||||
const keys = Object.keys(properties);
|
||||
if (keys.length > 0) {
|
||||
httpNodeBody = JSON.stringify(
|
||||
keys.reduce((acc: any, key) => {
|
||||
acc[key] = `{{${key}}}`;
|
||||
return acc;
|
||||
}, {}),
|
||||
null,
|
||||
2
|
||||
);
|
||||
}
|
||||
}
|
||||
if (customHeader) {
|
||||
const headersObj = (() => {
|
||||
try {
|
||||
return JSON.parse(customHeader) as Record<string, string>;
|
||||
} catch (err) {
|
||||
return {};
|
||||
}
|
||||
})();
|
||||
for (const key in headersObj) {
|
||||
httpNodeHeaders.push({
|
||||
key,
|
||||
type: WorkflowIOValueTypeEnum.string,
|
||||
// @ts-ignore
|
||||
value: headersObj[key]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* Combine complete modules */
|
||||
const modules: StoreNodeItemType[] = [
|
||||
{
|
||||
nodeId: pluginInputId,
|
||||
name: PluginInputModule.name,
|
||||
intro: PluginInputModule.intro,
|
||||
avatar: PluginInputModule.avatar,
|
||||
flowNodeType: PluginInputModule.flowNodeType,
|
||||
showStatus: PluginInputModule.showStatus,
|
||||
position: {
|
||||
x: 616.4226348688949,
|
||||
y: -165.05298493910115
|
||||
},
|
||||
version: PluginInputModule.version,
|
||||
inputs: pluginInputs,
|
||||
outputs: pluginOutputs
|
||||
},
|
||||
{
|
||||
nodeId: pluginOutputId,
|
||||
name: PluginOutputModule.name,
|
||||
intro: PluginOutputModule.intro,
|
||||
avatar: PluginOutputModule.avatar,
|
||||
flowNodeType: PluginOutputModule.flowNodeType,
|
||||
showStatus: PluginOutputModule.showStatus,
|
||||
position: {
|
||||
x: 1607.7142331269126,
|
||||
y: -151.8669210746189
|
||||
},
|
||||
version: PluginOutputModule.version,
|
||||
inputs: [
|
||||
{
|
||||
key: pluginOutputKey,
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
label: pluginOutputKey,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
required: false,
|
||||
description: '',
|
||||
canEdit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
description: true,
|
||||
valueType: true
|
||||
},
|
||||
value: [httpId, 'httpRawResponse']
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
id: pluginOutputId,
|
||||
key: pluginOutputKey,
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
label: pluginOutputKey,
|
||||
type: FlowNodeOutputTypeEnum.static
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
nodeId: httpId,
|
||||
name: HttpModule468.name,
|
||||
intro: HttpModule468.intro,
|
||||
avatar: HttpModule468.avatar,
|
||||
flowNodeType: HttpModule468.flowNodeType,
|
||||
showStatus: true,
|
||||
position: {
|
||||
x: 1042.549746602742,
|
||||
y: -447.77496332641647
|
||||
},
|
||||
version: HttpModule468.version,
|
||||
inputs: [
|
||||
{
|
||||
key: NodeInputKeyEnum.addInputParam,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.addInputParam],
|
||||
valueType: WorkflowIOValueTypeEnum.dynamic,
|
||||
label: '',
|
||||
required: false,
|
||||
description: 'core.module.input.description.HTTP Dynamic Input',
|
||||
editField: {
|
||||
key: true,
|
||||
valueType: true
|
||||
}
|
||||
},
|
||||
...httpInputs,
|
||||
{
|
||||
key: 'system_httpMethod',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.custom],
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
label: '',
|
||||
value: item.method.toUpperCase(),
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'system_httpReqUrl',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
label: '',
|
||||
description: 'core.module.input.description.Http Request Url',
|
||||
placeholder: 'https://api.ai.com/getInventory',
|
||||
required: false,
|
||||
value: requestUrl
|
||||
},
|
||||
{
|
||||
key: 'system_httpHeader',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.custom],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
value: httpNodeHeaders,
|
||||
label: '',
|
||||
description: 'core.module.input.description.Http Request Header',
|
||||
placeholder: 'core.module.input.description.Http Request Header',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
key: 'system_httpParams',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
value: httpNodeParams,
|
||||
label: '',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
key: 'system_httpJsonBody',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.hidden],
|
||||
valueType: WorkflowIOValueTypeEnum.any,
|
||||
value: httpNodeBody,
|
||||
label: '',
|
||||
required: false
|
||||
}
|
||||
],
|
||||
outputs: HttpModule468.outputs
|
||||
}
|
||||
];
|
||||
|
||||
const edges: StoreEdgeItemType[] = [
|
||||
{
|
||||
source: pluginInputId,
|
||||
target: httpId,
|
||||
sourceHandle: getHandleId(pluginInputId, 'source', 'right'),
|
||||
targetHandle: getHandleId(httpId, 'target', 'left')
|
||||
},
|
||||
{
|
||||
source: httpId,
|
||||
target: pluginOutputId,
|
||||
sourceHandle: getHandleId(httpId, 'source', 'right'),
|
||||
targetHandle: getHandleId(pluginOutputId, 'target', 'left')
|
||||
}
|
||||
];
|
||||
|
||||
return {
|
||||
name: item.name,
|
||||
avatar: HttpImgUrl,
|
||||
intro: item.description,
|
||||
parentId,
|
||||
type: AppTypeEnum.plugin,
|
||||
modules,
|
||||
edges,
|
||||
pluginData: {
|
||||
pluginUniId: item.name
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
7
packages/global/core/app/type.d.ts
vendored
7
packages/global/core/app/type.d.ts
vendored
@@ -25,6 +25,12 @@ export type AppSchema = {
|
||||
|
||||
modules: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
pluginData?: {
|
||||
nodeVersion?: string;
|
||||
pluginUniId?: string; // plugin unique id(plugin name)
|
||||
apiSchemaStr?: string; // api schema string
|
||||
customHeaders?: string;
|
||||
};
|
||||
|
||||
// App system config
|
||||
chatConfig: AppChatConfigType;
|
||||
@@ -44,6 +50,7 @@ export type AppListItemType = {
|
||||
type: AppTypeEnum;
|
||||
defaultPermission: PermissionValueType;
|
||||
permission: AppPermission;
|
||||
pluginData?: AppSchema['pluginData'];
|
||||
};
|
||||
|
||||
export type AppDetailType = AppSchema & {
|
||||
|
@@ -99,7 +99,7 @@ export const appWorkflow2Form = ({
|
||||
if (!node.pluginId) return;
|
||||
|
||||
defaultAppForm.selectedTools.push({
|
||||
id: node.pluginId,
|
||||
id: node.nodeId,
|
||||
pluginId: node.pluginId,
|
||||
name: node.name,
|
||||
avatar: node.avatar,
|
||||
|
8
packages/global/core/app/version.d.ts
vendored
8
packages/global/core/app/version.d.ts
vendored
@@ -1,12 +1,12 @@
|
||||
import { StoreNodeItemType } from '../workflow/type';
|
||||
import { StoreEdgeItemType } from '../workflow/type/edge';
|
||||
import { AppChatConfigType } from './type';
|
||||
import { AppChatConfigType, AppSchema } from './type';
|
||||
|
||||
export type AppVersionSchemaType = {
|
||||
_id: string;
|
||||
appId: string;
|
||||
time: Date;
|
||||
nodes: StoreNodeItemType[];
|
||||
edges: StoreEdgeItemType[];
|
||||
chatConfig: AppChatConfigType;
|
||||
nodes: AppSchema['modules'];
|
||||
edges: AppSchema['edges'];
|
||||
chatConfig: AppSchema['chatConfig'];
|
||||
};
|
||||
|
Reference in New Issue
Block a user