Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: system plugin #2024

Merged
merged 1 commit into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/global/core/plugin/type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export type PluginItemSchema = {
export type PluginTemplateType = PluginRuntimeType & {
author?: string;
id: string;
source: `${PluginSourceEnum}`;
source: PluginSourceEnum;
templateType: FlowNodeTemplateType['templateType'];
intro: string;
version: string;
Expand Down
3 changes: 2 additions & 1 deletion packages/global/core/workflow/runtime/type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ export type RuntimeNodeItemType = {
};

export type PluginRuntimeType = {
id: string;
teamId?: string;
name: string;
avatar: string;
showStatus?: boolean;
isTool?: boolean;
currentCost?: number;
nodes: StoreNodeItemType[];
edges: StoreEdgeItemType[];
};
Expand Down
12 changes: 12 additions & 0 deletions packages/global/core/workflow/type/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,29 @@ export type WorkflowTemplateType = {

workflow: WorkflowTemplateBasicType;
};

// template market
export type TemplateMarketItemType = WorkflowTemplateType & {
tags?: { id: string; label: string }[];
};

// system plugin
export type SystemPluginTemplateItemType = WorkflowTemplateType & {
templateType: FlowNodeTemplateTypeEnum;
isTool?: boolean;

// commercial plugin config
originCost: number; // n points/one time
currentCost: number;

isActive?: boolean;
inputConfig?: {
// Render config input form. Find the corresponding node and replace the variable directly
key: string;
label: string;
description: string;
value?: any;
}[];

workflow: WorkflowTemplateBasicType;
};
90 changes: 64 additions & 26 deletions packages/plugins/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,39 @@ import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constant
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { SystemPluginResponseType } from './type';
import { NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node';
import { isProduction } from '../service/common/system/constants';
import { FastGPTProUrl, isProduction } from '../service/common/system/constants';
import { GET, POST } from '@fastgpt/service/common/api/plusRequest';
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';

let list = ['getTime', 'fetchUrl', 'mathExprVal'];

/* Get plugins */
export const getCommunityPlugins = () => {
if (isProduction && global.communitySystemPlugins) return global.communitySystemPlugins;

global.communitySystemPlugins = list.map((name) => ({
return list.map<SystemPluginTemplateItemType>((name) => ({
...require(`./src/${name}/template.json`),
id: `${PluginSourceEnum.community}-${name}`
id: `${PluginSourceEnum.community}-${name}`,
isActive: true
}));

return global.communitySystemPlugins;
};
const getCommercialPlugins = () => {
return GET<SystemPluginTemplateItemType[]>('/core/app/plugin/getSystemPlugins');
};
export const getSystemPluginTemplates = async () => {
if (global.systemPlugins) return global.systemPlugins;

export const getCommunityPluginsTemplateList = () => {
return getCommunityPlugins().map<NodeTemplateListItemType>((plugin) => ({
id: plugin.id,
templateType: plugin.templateType ?? FlowNodeTemplateTypeEnum.other,
flowNodeType: FlowNodeTypeEnum.pluginModule,
avatar: plugin.avatar,
name: plugin.name,
intro: plugin.intro,
isTool: plugin.isTool
}));
try {
global.systemPlugins = [];
global.systemPlugins = FastGPTProUrl ? await getCommercialPlugins() : getCommunityPlugins();

return global.systemPlugins;
} catch (error) {
//@ts-ignore
global.systemPlugins = undefined;
return Promise.reject(error);
}
};

export const getCommunityCb = async () => {
if (isProduction && global.communitySystemPluginCb) return global.communitySystemPluginCb;

// Do not modify the following code
const loadModule = async (name: string) => {
const module = await import(`./src/${name}/index`);
Expand All @@ -46,12 +49,47 @@ export const getCommunityCb = async () => {
}))
);

global.communitySystemPluginCb = result.reduce<
Record<string, (e: any) => SystemPluginResponseType>
>((acc, { name, cb }) => {
acc[name] = cb;
return acc;
}, {});
return result.reduce<Record<string, (e: any) => SystemPluginResponseType>>(
(acc, { name, cb }) => {
acc[name] = cb;
return acc;
},
{}
);
};
const getCommercialCb = async () => {
const plugins = await getSystemPluginTemplates();
const result = plugins.map((plugin) => {
const name = plugin.id.split('-')[1];

return {
name,
cb: (e: any) =>
POST<Record<string, any>>('/core/app/plugin/run', {
pluginName: name,
data: e
})
};
});

return result.reduce<Record<string, (e: any) => SystemPluginResponseType>>(
(acc, { name, cb }) => {
acc[name] = cb;
return acc;
},
{}
);
};
export const getSystemPluginCb = async () => {
if (isProduction && global.systemPluginCb) return global.systemPluginCb;

return global.communitySystemPluginCb;
try {
global.systemPluginCb = {};
global.systemPluginCb = FastGPTProUrl ? await getCommercialCb() : await getCommunityCb();
return global.systemPluginCb;
} catch (error) {
//@ts-ignore
global.systemPluginCb = undefined;
return Promise.reject(error);
}
};
6 changes: 4 additions & 2 deletions packages/plugins/type.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { PluginTemplateType } from '@fastgpt/global/core/plugin/type.d';
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';

export type SystemPluginResponseType = Promise<Record<string, any>>;

declare global {
var communitySystemPlugins: SystemPluginTemplateItemType[];
var communitySystemPluginCb: Record<string, (e: any) => SystemPluginResponseType>;
var systemPlugins: SystemPluginTemplateItemType[];
var systemPluginCb: Record<string, (e: any) => SystemPluginResponseType>;
}
22 changes: 11 additions & 11 deletions packages/service/core/app/plugin/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
import { cloneDeep } from 'lodash';
import { MongoApp } from '../schema';
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
import { getCommunityPlugins } from '@fastgpt/plugins/register';
import { getSystemPluginTemplates } from '@fastgpt/plugins/register';

/*
plugin id rule:
Expand All @@ -28,7 +28,7 @@ export async function splitCombinePluginId(id: string) {
};
}

const [source, pluginId] = id.split('-') as [`${PluginSourceEnum}`, string];
const [source, pluginId] = id.split('-') as [PluginSourceEnum, string];
if (!source || !pluginId) return Promise.reject('pluginId not found');

return { source, pluginId: id };
Expand All @@ -39,14 +39,6 @@ const getPluginTemplateById = async (
): Promise<SystemPluginTemplateItemType & { teamId?: string }> => {
const { source, pluginId } = await splitCombinePluginId(id);

if (source === PluginSourceEnum.community) {
const item = [...global.communityPlugins, ...getCommunityPlugins()].find(
(plugin) => plugin.id === pluginId
);
if (!item) return Promise.reject('plugin not found');

return cloneDeep(item);
}
if (source === PluginSourceEnum.personal) {
const item = await MongoApp.findById(id).lean();
if (!item) return Promise.reject('plugin not found');
Expand All @@ -68,8 +60,14 @@ const getPluginTemplateById = async (
originCost: 0,
currentCost: 0
};
} else {
const item = [...global.communityPlugins, ...(await getSystemPluginTemplates())].find(
(plugin) => plugin.id === pluginId
);
if (!item) return Promise.reject('plugin not found');

return cloneDeep(item);
}
return Promise.reject('plugin not found');
};

/* format plugin modules to plugin preview module */
Expand Down Expand Up @@ -98,10 +96,12 @@ export async function getPluginRuntimeById(id: string): Promise<PluginRuntimeTyp
const plugin = await getPluginTemplateById(id);

return {
id: plugin.id,
teamId: plugin.teamId,
name: plugin.name,
avatar: plugin.avatar,
showStatus: plugin.showStatus,
currentCost: plugin.currentCost,
nodes: plugin.workflow.nodes,
edges: plugin.workflow.edges
};
Expand Down
21 changes: 21 additions & 0 deletions packages/service/core/app/plugin/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PluginRuntimeType } from '@fastgpt/global/core/workflow/runtime/type';
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
import { splitCombinePluginId } from './controller';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';

/*
1. Commercial plugin: n points per times
2. Other plugin: sum of children points
*/
export const computedPluginUsage = async (
plugin: PluginRuntimeType,
childrenUsage: ChatNodeUsageType[]
) => {
const { source } = await splitCombinePluginId(plugin.id);

if (source === PluginSourceEnum.commercial) {
return plugin.currentCost ?? 0;
}

return childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
};
27 changes: 13 additions & 14 deletions packages/service/core/workflow/dispatch/plugin/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/
import { dispatchWorkFlow } from '../index';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { getPluginRuntimeById, splitCombinePluginId } from '../../../app/plugin/controller';
import { getPluginRuntimeById } from '../../../app/plugin/controller';
import {
getDefaultEntryNodeIds,
initWorkflowEdgeStatus,
storeNodes2RuntimeNodes
} from '@fastgpt/global/core/workflow/runtime/utils';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { updateToolInputValue } from '../agent/runTool/utils';
import { authAppByTmbId } from '../../../../support/permission/app/auth';
import { authPluginByTmbId } from '../../../../support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
import { computedPluginUsage } from '../../../app/plugin/utils';

type RunPluginProps = ModuleDispatchProps<{
[key: string]: any;
Expand All @@ -33,14 +33,12 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
}

// auth plugin
const { source } = await splitCombinePluginId(pluginId);
if (source === PluginSourceEnum.personal) {
await authAppByTmbId({
appId: pluginId,
tmbId: workflowApp.tmbId,
per: ReadPermissionVal
});
}
await authPluginByTmbId({
appId: pluginId,
tmbId: workflowApp.tmbId,
per: ReadPermissionVal
});

const plugin = await getPluginRuntimeById(pluginId);

// concat dynamic inputs
Expand Down Expand Up @@ -78,12 +76,14 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
output.moduleLogo = plugin.avatar;
}

const usagePoints = await computedPluginUsage(plugin, flowUsages);

return {
assistantResponses,
// responseData, // debug
[DispatchNodeResponseKeyEnum.nodeResponse]: {
moduleLogo: plugin.avatar,
totalPoints: flowResponses.reduce((sum, item) => sum + (item.totalPoints || 0), 0),
totalPoints: usagePoints,
pluginOutput: output?.pluginOutput,
pluginDetail:
mode === 'test' && plugin.teamId === teamId
Expand All @@ -96,8 +96,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{
moduleName: plugin.name,
totalPoints: flowUsages.reduce((sum, item) => sum + (item.totalPoints || 0), 0),
model: plugin.name,
totalPoints: usagePoints,
tokens: 0
}
],
Expand Down
4 changes: 2 additions & 2 deletions packages/service/core/workflow/dispatch/tools/http468.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/ty
import { getErrText } from '@fastgpt/global/common/error/utils';
import { responseWrite } from '../../../../common/response';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { getCommunityCb } from '@fastgpt/plugins/register';
import { getSystemPluginCb } from '@fastgpt/plugins/register';

type PropsArrType = {
key: string;
Expand Down Expand Up @@ -121,7 +121,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H

try {
const { formatResponse, rawResponse } = await (async () => {
const communityPluginCb = await getCommunityCb();
const communityPluginCb = await getSystemPluginCb();
if (communityPluginCb[httpReqUrl]) {
const pluginResult = await communityPluginCb[httpReqUrl](requestBody);
return {
Expand Down
21 changes: 21 additions & 0 deletions packages/service/support/permission/app/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ import { AuthResponseType } from '../type/auth.d';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { AppFolderTypeList } from '@fastgpt/global/core/app/constants';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { splitCombinePluginId } from '../../../core/app/plugin/controller';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';

export const authPluginByTmbId = async ({
tmbId,
appId,
per
}: {
tmbId: string;
appId: string;
per: PermissionValueType;
}) => {
const { source } = await splitCombinePluginId(appId);
if (source === PluginSourceEnum.personal) {
await authAppByTmbId({
appId,
tmbId,
per
});
}
};

export const authAppByTmbId = async ({
tmbId,
Expand Down
22 changes: 22 additions & 0 deletions packages/web/components/common/Avatar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { Image } from '@chakra-ui/react';
import type { ImageProps } from '@chakra-ui/react';
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';

const Avatar = ({ w = '30px', src, ...props }: ImageProps) => {
return (
<Image
fallbackSrc={LOGO_ICON}
fallbackStrategy={'onError'}
// borderRadius={'md'}
objectFit={'contain'}
alt=""
w={w}
h={w}
src={src || LOGO_ICON}
{...props}
/>
);
};

export default Avatar;
Loading
Loading