From 14c7e013d31b92f6e0bd8d081303917b7b01a372 Mon Sep 17 00:00:00 2001 From: heheer Date: Tue, 26 Nov 2024 20:46:30 +0800 Subject: [PATCH] feat: add customize toolkit (#3205) * chaoyang * fix-auth * add toolkit * add order * plugin usage * fix * delete console: --- packages/global/core/app/type.d.ts | 6 + packages/global/core/plugin/type.d.ts | 4 + .../global/core/workflow/runtime/type.d.ts | 11 - packages/global/core/workflow/type/index.d.ts | 7 +- packages/global/core/workflow/type/node.d.ts | 8 +- packages/plugins/register.ts | 3 +- packages/plugins/type.d.ts | 2 + .../service/core/app/plugin/controller.ts | 60 +++- .../core/app/plugin/systemPluginSchema.ts | 8 + packages/service/core/app/plugin/type.d.ts | 7 +- packages/service/core/app/plugin/utils.ts | 24 +- .../core/app/store/pluginGroupSchema.ts | 31 ++ packages/service/core/app/store/type.d.ts | 11 + .../core/workflow/dispatch/plugin/run.ts | 7 - .../web/components/common/DndDrag/index.tsx | 2 +- .../web/components/common/Icon/constants.ts | 11 + .../web/components/common/Icon/icons/book.svg | 6 +- .../Icon/icons/common/administrator.svg | 5 + .../common/Icon/icons/common/billing.svg | 8 + .../common/Icon/icons/common/modal.svg | 6 + .../common/Icon/icons/common/more.svg | 6 + .../common/Icon/icons/common/moreFill.svg | 6 + .../Icon/icons/common/navbar/pluginLight.svg | 5 +- .../Icon/icons/common/solidChevronRight.svg | 3 + .../common/Icon/icons/common/toolkit.svg | 3 + .../common/Icon/icons/common/userInfo.svg | 3 + .../Icon/icons/core/app/aiLightSmall.svg | 6 + .../icons/core/dataset/datasetLightSmall.svg | 3 + .../Icon/icons/support/account/plans.svg | 10 +- .../Icon/icons/support/bill/priceLight.svg | 12 +- .../icons/support/user/userLightSmall.svg | 3 + packages/web/core/workflow/constants.ts | 23 ++ packages/web/i18n/en/app.json | 1 + packages/web/i18n/en/common.json | 1 + packages/web/i18n/zh-CN/app.json | 1 + packages/web/i18n/zh-CN/common.json | 1 + projects/app/src/components/Layout/index.tsx | 2 +- projects/app/src/components/Layout/navbar.tsx | 8 + .../app/src/components/Layout/navbarPhone.tsx | 18 +- .../api/core/app/plugin/getPluginGroups.ts | 34 ++ .../app/plugin/getSystemPluginTemplates.ts | 6 +- .../Flow/NodeTemplatesModal.tsx | 310 ++++++++++++------ projects/app/src/pages/app/list/index.tsx | 52 ++- .../app/src/pages/{tools => more}/index.tsx | 10 +- .../pages/toolkit/components/PluginCard.tsx | 132 ++++++++ projects/app/src/pages/toolkit/index.tsx | 226 +++++++++++++ projects/app/src/service/core/app/plugin.ts | 26 ++ projects/app/src/web/core/app/api/plugin.ts | 6 +- 48 files changed, 955 insertions(+), 189 deletions(-) create mode 100644 packages/service/core/app/store/pluginGroupSchema.ts create mode 100644 packages/service/core/app/store/type.d.ts create mode 100644 packages/web/components/common/Icon/icons/common/administrator.svg create mode 100644 packages/web/components/common/Icon/icons/common/billing.svg create mode 100644 packages/web/components/common/Icon/icons/common/modal.svg create mode 100644 packages/web/components/common/Icon/icons/common/more.svg create mode 100644 packages/web/components/common/Icon/icons/common/moreFill.svg create mode 100644 packages/web/components/common/Icon/icons/common/solidChevronRight.svg create mode 100644 packages/web/components/common/Icon/icons/common/toolkit.svg create mode 100644 packages/web/components/common/Icon/icons/common/userInfo.svg create mode 100644 packages/web/components/common/Icon/icons/core/app/aiLightSmall.svg create mode 100644 packages/web/components/common/Icon/icons/core/dataset/datasetLightSmall.svg create mode 100644 packages/web/components/common/Icon/icons/support/user/userLightSmall.svg create mode 100644 projects/app/src/pages/api/core/app/plugin/getPluginGroups.ts rename projects/app/src/pages/{tools => more}/index.tsx (92%) create mode 100644 projects/app/src/pages/toolkit/components/PluginCard.tsx create mode 100644 projects/app/src/pages/toolkit/index.tsx diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 91672059184..b735a1e30dc 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -170,3 +170,9 @@ export type AppFileSelectConfigType = { canSelectImg: boolean; maxFiles: number; }; + +export type SystemPluginListItemType = { + _id: string; + name: string; + avatar: string; +}; diff --git a/packages/global/core/plugin/type.d.ts b/packages/global/core/plugin/type.d.ts index 38215640e4e..37eab82a803 100644 --- a/packages/global/core/plugin/type.d.ts +++ b/packages/global/core/plugin/type.d.ts @@ -39,6 +39,7 @@ export type PluginTemplateType = PluginRuntimeType & { }; export type PluginRuntimeType = { + id: string; teamId?: string; name: string; avatar: string; @@ -46,4 +47,7 @@ export type PluginRuntimeType = { isTool?: boolean; nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[]; + currentCost?: number; + hasTokenFee?: boolean; + pluginOrder?: number; }; diff --git a/packages/global/core/workflow/runtime/type.d.ts b/packages/global/core/workflow/runtime/type.d.ts index ae0d29bda16..de469cfe14e 100644 --- a/packages/global/core/workflow/runtime/type.d.ts +++ b/packages/global/core/workflow/runtime/type.d.ts @@ -82,17 +82,6 @@ export type RuntimeNodeItemType = { version: string; }; -export type PluginRuntimeType = { - id: string; - teamId?: string; - name: string; - avatar: string; - showStatus?: boolean; - currentCost?: number; - nodes: StoreNodeItemType[]; - edges: StoreEdgeItemType[]; -}; - export type RuntimeEdgeItemType = StoreEdgeItemType & { status: 'waiting' | 'active' | 'skipped'; }; diff --git a/packages/global/core/workflow/type/index.d.ts b/packages/global/core/workflow/type/index.d.ts index 99b1f67a81a..5e246cb9c04 100644 --- a/packages/global/core/workflow/type/index.d.ts +++ b/packages/global/core/workflow/type/index.d.ts @@ -63,15 +63,20 @@ export type TemplateMarketListItemType = { // system plugin export type SystemPluginTemplateItemType = WorkflowTemplateType & { customWorkflow?: string; + associatedPluginId?: string; + userGuide?: string; - templateType: FlowNodeTemplateTypeEnum; + templateType: string; isTool?: boolean; // commercial plugin config originCost: number; // n points/one time currentCost: number; + hasTokenFee: boolean; + pluginOrder: number; isActive?: boolean; + isOfficial?: boolean; inputConfig?: { // Render config input form. Find the corresponding node and replace the variable directly key: string; diff --git a/packages/global/core/workflow/type/node.d.ts b/packages/global/core/workflow/type/node.d.ts index 227e9f4f26c..2359ac0ee70 100644 --- a/packages/global/core/workflow/type/node.d.ts +++ b/packages/global/core/workflow/type/node.d.ts @@ -54,7 +54,7 @@ type HandleType = { // system template export type FlowNodeTemplateType = FlowNodeCommonType & { id: string; // node id, unique - templateType: FlowNodeTemplateTypeEnum; + templateType: string; // show handle sourceHandle?: HandleType; @@ -76,7 +76,7 @@ export type NodeTemplateListItemType = { flowNodeType: FlowNodeTypeEnum; // render node card parentId?: ParentIdType; isFolder?: boolean; - templateType: FlowNodeTemplateTypeEnum; + templateType: string; avatar?: string; name: string; intro?: string; // template list intro @@ -85,10 +85,12 @@ export type NodeTemplateListItemType = { author?: string; unique?: boolean; // 唯一的 currentCost?: number; // 当前积分消耗 + hasTokenFee?: boolean; // 是否配置积分 + instructions?: string; // 使用说明 }; export type NodeTemplateListType = { - type: FlowNodeTemplateTypeEnum; + type: string; label: string; list: NodeTemplateListItemType[]; }[]; diff --git a/packages/plugins/register.ts b/packages/plugins/register.ts index 826a417218e..3940ff1a1f7 100644 --- a/packages/plugins/register.ts +++ b/packages/plugins/register.ts @@ -40,7 +40,8 @@ export const getCommunityPlugins = () => { id: `${PluginSourceEnum.community}-${name}`, isFolder, parentId, - isActive: true + isActive: true, + isOfficial: true }; }); }; diff --git a/packages/plugins/type.d.ts b/packages/plugins/type.d.ts index 201c6fdd0a7..a885fd8f108 100644 --- a/packages/plugins/type.d.ts +++ b/packages/plugins/type.d.ts @@ -1,6 +1,7 @@ import { PluginTemplateType } from '@fastgpt/global/core/plugin/type.d'; import { systemPluginResponseEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type'; +import { PluginGroupSchemaType } from '@fastgpt/service/core/app/store/type'; export type SystemPluginResponseType = Promise>; export type SystemPluginSpecialResponse = { @@ -10,6 +11,7 @@ export type SystemPluginSpecialResponse = { }; declare global { + var pluginGroups: PluginGroupSchemaType[]; var systemPlugins: SystemPluginTemplateItemType[]; var systemPluginCb: Record SystemPluginResponseType>; } diff --git a/packages/service/core/app/plugin/controller.ts b/packages/service/core/app/plugin/controller.ts index e94ce67a059..f0d510e58c0 100644 --- a/packages/service/core/app/plugin/controller.ts +++ b/packages/service/core/app/plugin/controller.ts @@ -2,7 +2,6 @@ import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d' import { FlowNodeTypeEnum, defaultNodeVersion } from '@fastgpt/global/core/workflow/node/constant'; import { appData2FlowNodeIO, pluginData2FlowNodeIO } from '@fastgpt/global/core/workflow/utils'; import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; -import type { PluginRuntimeType } from '@fastgpt/global/core/workflow/runtime/type'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { getHandleConfig } from '@fastgpt/global/core/workflow/template/utils'; import { getNanoid } from '@fastgpt/global/common/string/tools'; @@ -11,6 +10,7 @@ import { MongoApp } from '../schema'; import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type'; import { getSystemPluginTemplates } from '../../../../plugins/register'; import { getAppLatestVersion, getAppVersionById } from '../version/controller'; +import { PluginRuntimeType } from '@fastgpt/global/core/plugin/type'; /* plugin id rule: @@ -77,13 +77,29 @@ export async function getChildAppPreviewNode({ templateType: FlowNodeTemplateTypeEnum.teamApp, version: version.versionId, originCost: 0, - currentCost: 0 + currentCost: 0, + hasTokenFee: false, + pluginOrder: 0 }; } else { return getSystemPluginTemplateById(pluginId); } })(); + if (app.associatedPluginId) { + const item = await MongoApp.findById(app.associatedPluginId).lean(); + if (!item) return Promise.reject('plugin not found'); + + const version = await getAppLatestVersion(app.associatedPluginId, item); + + if (!version.versionId) return Promise.reject('App version not found'); + app.workflow = { + nodes: version.nodes, + edges: version.edges, + chatConfig: version.chatConfig + }; + } + const isPlugin = !!app.workflow.nodes.find( (node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput ); @@ -147,7 +163,41 @@ export async function getChildAppRuntimeById( // 用不到 version: item?.pluginData?.nodeVersion || defaultNodeVersion, originCost: 0, - currentCost: 0 + currentCost: 0, + hasTokenFee: false, + pluginOrder: 0 + }; + } else if (source === PluginSourceEnum.commercial) { + const pluginTemplate = getSystemPluginTemplates().find((plugin) => plugin.id === pluginId); + if (!pluginTemplate?.associatedPluginId) return Promise.reject('plugin not found'); + + const item = await MongoApp.findById(pluginTemplate.associatedPluginId).lean(); + if (!item) return Promise.reject('plugin not found'); + + const version = await getAppVersionById({ + appId: pluginTemplate.associatedPluginId, + versionId, + app: item + }); + + return { + id: String(item._id), + teamId: String(item.teamId), + name: item.name, + avatar: item.avatar, + intro: item.intro, + showStatus: true, + workflow: { + nodes: version.nodes, + edges: version.edges, + chatConfig: version.chatConfig + }, + templateType: FlowNodeTemplateTypeEnum.teamApp, + version: pluginTemplate.version, + originCost: pluginTemplate.originCost, + currentCost: pluginTemplate.currentCost, + hasTokenFee: pluginTemplate.hasTokenFee, + pluginOrder: pluginTemplate.pluginOrder }; } else { return getSystemPluginTemplateById(pluginId); @@ -162,6 +212,8 @@ export async function getChildAppRuntimeById( showStatus: app.showStatus, currentCost: app.currentCost, nodes: app.workflow.nodes, - edges: app.workflow.edges + edges: app.workflow.edges, + hasTokenFee: app.hasTokenFee, + pluginOrder: app.pluginOrder }; } diff --git a/packages/service/core/app/plugin/systemPluginSchema.ts b/packages/service/core/app/plugin/systemPluginSchema.ts index 56a165c414c..fe7629b9ec3 100644 --- a/packages/service/core/app/plugin/systemPluginSchema.ts +++ b/packages/service/core/app/plugin/systemPluginSchema.ts @@ -25,6 +25,14 @@ const SystemPluginSchema = new Schema({ type: Number, default: 0 }, + hasTokenFee: { + type: Boolean, + default: false + }, + pluginOrder: { + type: Number, + default: 0 + }, customConfig: Object }); diff --git a/packages/service/core/app/plugin/type.d.ts b/packages/service/core/app/plugin/type.d.ts index a72f3d29756..b310ce57e31 100644 --- a/packages/service/core/app/plugin/type.d.ts +++ b/packages/service/core/app/plugin/type.d.ts @@ -1,3 +1,4 @@ +import { SystemPluginListItemType } from '@fastgpt/global/core/app/type'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { SystemPluginTemplateItemType, @@ -9,7 +10,9 @@ export type SystemPluginConfigSchemaType = { originCost: number; // n points/one time currentCost: number; + hasTokenFee: boolean; isActive: boolean; + pluginOrder: number; inputConfig: SystemPluginTemplateItemType['inputConfig']; customConfig?: { @@ -19,6 +22,8 @@ export type SystemPluginConfigSchemaType = { version: string; weight?: number; workflow: WorkflowTemplateBasicType; - templateType: FlowNodeTemplateTypeEnum; + templateType: string; + associatedPluginId: string; + userGuide: string; }; }; diff --git a/packages/service/core/app/plugin/utils.ts b/packages/service/core/app/plugin/utils.ts index 4ea86b06eff..3ec4ecc5620 100644 --- a/packages/service/core/app/plugin/utils.ts +++ b/packages/service/core/app/plugin/utils.ts @@ -1,11 +1,11 @@ -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'; +import { PluginRuntimeType } from '@fastgpt/global/core/plugin/type'; /* - 1. Commercial plugin: n points per times - 2. Other plugin: sum of children points + Plugin points calculation: + 1. Return 0 if error + 2. Add configured points if commercial plugin + 3. Add sum of child nodes points */ export const computedPluginUsage = async ({ plugin, @@ -16,13 +16,13 @@ export const computedPluginUsage = async ({ childrenUsage: ChatNodeUsageType[]; error?: boolean; }) => { - const { source } = await splitCombinePluginId(plugin.id); - - // Commercial plugin: n points per times - if (source === PluginSourceEnum.commercial) { - if (error) return 0; - return plugin.currentCost ?? 0; + if (error) { + return 0; } - return childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0); + const childrenIUsages = childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0); + + const pluginCurrentCose = plugin.currentCost ?? 0; + + return plugin.hasTokenFee ? pluginCurrentCose + childrenIUsages : pluginCurrentCose; }; diff --git a/packages/service/core/app/store/pluginGroupSchema.ts b/packages/service/core/app/store/pluginGroupSchema.ts new file mode 100644 index 00000000000..d70e3935826 --- /dev/null +++ b/packages/service/core/app/store/pluginGroupSchema.ts @@ -0,0 +1,31 @@ +import { connectionMongo, getMongoModel } from '../../../common/mongo/index'; +import { PluginGroupSchemaType, TGroupType } from './type'; +const { Schema } = connectionMongo; + +export const collectionName = 'app_plugin_groups'; + +const PluginGroupSchema = new Schema({ + groupAvatar: { + type: String, + default: '' + }, + groupName: { + type: String, + required: true + }, + groupTypes: { + type: Array, + default: [] + }, + groupOrder: { + type: Number, + default: 0 + } +}); + +PluginGroupSchema.index({ groupName: 1 }); + +export const MongoPluginGroupsSchema = getMongoModel( + collectionName, + PluginGroupSchema +); diff --git a/packages/service/core/app/store/type.d.ts b/packages/service/core/app/store/type.d.ts new file mode 100644 index 00000000000..bf8d33e2825 --- /dev/null +++ b/packages/service/core/app/store/type.d.ts @@ -0,0 +1,11 @@ +export type PluginGroupSchemaType = { + groupAvatar: string; + groupName: string; + groupTypes: TGroupType[]; + groupOrder: number; +}; + +export type TGroupType = { + typeName: string; + typeId: string; +}; diff --git a/packages/service/core/workflow/dispatch/plugin/run.ts b/packages/service/core/workflow/dispatch/plugin/run.ts index 6d5fe3164a6..cd75aee1d62 100644 --- a/packages/service/core/workflow/dispatch/plugin/run.ts +++ b/packages/service/core/workflow/dispatch/plugin/run.ts @@ -23,7 +23,6 @@ type RunPluginProps = ModuleDispatchProps<{ [key: string]: any; }>; type RunPluginResponse = DispatchNodeResultType<{}>; - export const dispatchRunPlugin = async (props: RunPluginProps): Promise => { const { node: { pluginId, version }, @@ -31,7 +30,6 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise item.moduleType === FlowNodeTypeEnum.pluginOutput); - if (output) { output.moduleLogo = plugin.avatar; } @@ -117,7 +111,6 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise({ children, renderClone, onDragEndCb, dataList }: Props) return ( {children(provided, snapshot)} - {snapshot.isDraggingOver && } + {snapshot.isDraggingOver && } ); }} diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index e6d3d82bbc4..25f55ecf853 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -14,9 +14,11 @@ export const iconPaths = { 'common/addCircleLight': () => import('./icons/common/addCircleLight.svg'), 'common/addLight': () => import('./icons/common/addLight.svg'), 'common/addUser': () => import('./icons/common/addUser.svg'), + 'common/administrator': () => import('./icons/common/administrator.svg'), 'common/arrowLeft': () => import('./icons/common/arrowLeft.svg'), 'common/backFill': () => import('./icons/common/backFill.svg'), 'common/backLight': () => import('./icons/common/backLight.svg'), + 'common/billing': () => import('./icons/common/billing.svg'), 'common/check': () => import('./icons/common/check.svg'), 'common/clearLight': () => import('./icons/common/clearLight.svg'), 'common/closeLight': () => import('./icons/common/closeLight.svg'), @@ -52,7 +54,10 @@ export const iconPaths = { 'common/loading': () => import('./icons/common/loading.svg'), 'common/logLight': () => import('./icons/common/logLight.svg'), 'common/microsoft': () => import('./icons/common/microsoft.svg'), + 'common/modal': () => import('./icons/common/modal.svg'), 'common/monitor': () => import('./icons/common/monitor.svg'), + 'common/more': () => import('./icons/common/more.svg'), + 'common/moreFill': () => import('./icons/common/moreFill.svg'), 'common/navbar/pluginFill': () => import('./icons/common/navbar/pluginFill.svg'), 'common/navbar/pluginLight': () => import('./icons/common/navbar/pluginLight.svg'), 'common/openai': () => import('./icons/common/openai.svg'), @@ -74,12 +79,15 @@ export const iconPaths = { 'common/selectLight': () => import('./icons/common/selectLight.svg'), 'common/setting': () => import('./icons/common/setting.svg'), 'common/settingLight': () => import('./icons/common/settingLight.svg'), + 'common/solidChevronRight': () => import('./icons/common/solidChevronRight.svg'), 'common/subtract': () => import('./icons/common/subtract.svg'), 'common/text/t': () => import('./icons/common/text/t.svg'), 'common/tickFill': () => import('./icons/common/tickFill.svg'), + 'common/toolkit': () => import('./icons/common/toolkit.svg'), 'common/trash': () => import('./icons/common/trash.svg'), 'common/upRightArrowLight': () => import('./icons/common/upRightArrowLight.svg'), 'common/uploadFileFill': () => import('./icons/common/uploadFileFill.svg'), + 'common/userInfo': () => import('./icons/common/userInfo.svg'), 'common/viewLight': () => import('./icons/common/viewLight.svg'), 'common/voiceLight': () => import('./icons/common/voiceLight.svg'), 'common/warn': () => import('./icons/common/warn.svg'), @@ -88,6 +96,7 @@ export const iconPaths = { copy: () => import('./icons/copy.svg'), 'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'), 'core/app/aiLight': () => import('./icons/core/app/aiLight.svg'), + 'core/app/aiLightSmall': () => import('./icons/core/app/aiLightSmall.svg'), 'core/app/appApiLight': () => import('./icons/core/app/appApiLight.svg'), 'core/app/customFeedback': () => import('./icons/core/app/customFeedback.svg'), 'core/app/headphones': () => import('./icons/core/app/headphones.svg'), @@ -153,6 +162,7 @@ export const iconPaths = { import('./icons/core/dataset/commonDatasetOutline.svg'), 'core/dataset/datasetFill': () => import('./icons/core/dataset/datasetFill.svg'), 'core/dataset/datasetLight': () => import('./icons/core/dataset/datasetLight.svg'), + 'core/dataset/datasetLightSmall': () => import('./icons/core/dataset/datasetLightSmall.svg'), 'core/dataset/externalDataset': () => import('./icons/core/dataset/externalDataset.svg'), 'core/dataset/externalDatasetColor': () => import('./icons/core/dataset/externalDatasetColor.svg'), @@ -361,6 +371,7 @@ export const iconPaths = { 'support/user/informLight': () => import('./icons/support/user/informLight.svg'), 'support/user/userFill': () => import('./icons/support/user/userFill.svg'), 'support/user/userLight': () => import('./icons/support/user/userLight.svg'), + 'support/user/userLightSmall': () => import('./icons/support/user/userLightSmall.svg'), text: () => import('./icons/text.svg'), union: () => import('./icons/union.svg'), user: () => import('./icons/user.svg'), diff --git a/packages/web/components/common/Icon/icons/book.svg b/packages/web/components/common/Icon/icons/book.svg index 043acdc6538..a187b7ff4ce 100644 --- a/packages/web/components/common/Icon/icons/book.svg +++ b/packages/web/components/common/Icon/icons/book.svg @@ -1,3 +1,3 @@ - - - + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/administrator.svg b/packages/web/components/common/Icon/icons/common/administrator.svg new file mode 100644 index 00000000000..c0466f0449a --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/administrator.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/web/components/common/Icon/icons/common/billing.svg b/packages/web/components/common/Icon/icons/common/billing.svg new file mode 100644 index 00000000000..42824bd2203 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/billing.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/modal.svg b/packages/web/components/common/Icon/icons/common/modal.svg new file mode 100644 index 00000000000..02d68691cbc --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/modal.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/more.svg b/packages/web/components/common/Icon/icons/common/more.svg new file mode 100644 index 00000000000..d44c283f3ff --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/more.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/moreFill.svg b/packages/web/components/common/Icon/icons/common/moreFill.svg new file mode 100644 index 00000000000..0119b97b8a4 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/moreFill.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/navbar/pluginLight.svg b/packages/web/components/common/Icon/icons/common/navbar/pluginLight.svg index dc9d8da06a3..2a866c8758e 100644 --- a/packages/web/components/common/Icon/icons/common/navbar/pluginLight.svg +++ b/packages/web/components/common/Icon/icons/common/navbar/pluginLight.svg @@ -1,4 +1,3 @@ - - + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/solidChevronRight.svg b/packages/web/components/common/Icon/icons/common/solidChevronRight.svg new file mode 100644 index 00000000000..e621a8dd2e2 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/solidChevronRight.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/toolkit.svg b/packages/web/components/common/Icon/icons/common/toolkit.svg new file mode 100644 index 00000000000..93eaac1faba --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/toolkit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/common/userInfo.svg b/packages/web/components/common/Icon/icons/common/userInfo.svg new file mode 100644 index 00000000000..48af4a49b22 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/userInfo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/app/aiLightSmall.svg b/packages/web/components/common/Icon/icons/core/app/aiLightSmall.svg new file mode 100644 index 00000000000..1db1e20cfc7 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/aiLightSmall.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/dataset/datasetLightSmall.svg b/packages/web/components/common/Icon/icons/core/dataset/datasetLightSmall.svg new file mode 100644 index 00000000000..1cec156fbf2 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/dataset/datasetLightSmall.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/support/account/plans.svg b/packages/web/components/common/Icon/icons/support/account/plans.svg index e8aa856725d..4cfc42f19b9 100644 --- a/packages/web/components/common/Icon/icons/support/account/plans.svg +++ b/packages/web/components/common/Icon/icons/support/account/plans.svg @@ -1,5 +1,5 @@ - - - - - + + + + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/support/bill/priceLight.svg b/packages/web/components/common/Icon/icons/support/bill/priceLight.svg index c7f83cd7e9d..0dceea7a254 100644 --- a/packages/web/components/common/Icon/icons/support/bill/priceLight.svg +++ b/packages/web/components/common/Icon/icons/support/bill/priceLight.svg @@ -1,11 +1,3 @@ - - - - + + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/support/user/userLightSmall.svg b/packages/web/components/common/Icon/icons/support/user/userLightSmall.svg new file mode 100644 index 00000000000..1b5f0a814f6 --- /dev/null +++ b/packages/web/components/common/Icon/icons/support/user/userLightSmall.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/web/core/workflow/constants.ts b/packages/web/core/workflow/constants.ts index 7fde865b505..140967edc69 100644 --- a/packages/web/core/workflow/constants.ts +++ b/packages/web/core/workflow/constants.ts @@ -48,3 +48,26 @@ export const workflowNodeTemplateList = [ list: [] } ]; + +export const systemPluginTemplateList = [ + { + typeId: FlowNodeTemplateTypeEnum.tools as string, + typeName: i18nT('common:navbar.Tools') + }, + { + typeId: FlowNodeTemplateTypeEnum.search as string, + typeName: i18nT('common:common.Search') + }, + { + typeId: FlowNodeTemplateTypeEnum.multimodal as string, + typeName: i18nT('common:core.workflow.template.Multimodal') + }, + { + typeId: FlowNodeTemplateTypeEnum.communication as string, + typeName: i18nT('app:workflow.template.communication') + }, + { + typeId: FlowNodeTemplateTypeEnum.other as string, + typeName: i18nT('common:common.Other') + } +]; diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index 713eedccbe5..8d6bccc9347 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -86,6 +86,7 @@ "permission.des.manage": "Based on write permissions, you can configure publishing channels, view conversation logs, and assign permissions to the application.", "permission.des.read": "Use the app to have conversations", "permission.des.write": "Can view and edit apps", + "plugin.Instructions": "Instructions", "plugin_cost_per_times": "{{cost}}/time", "plugin_dispatch": "Plugin Invocation", "plugin_dispatch_tip": "Adds extra capabilities to the model. The specific plugins to be invoked will be autonomously decided by the model.\nIf a plugin is selected, the Dataset invocation will automatically be treated as a special plugin.", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index c594859a43b..7d2888365fe 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -901,6 +901,7 @@ "navbar.Chat": "Chat", "navbar.Datasets": "Datasets", "navbar.Studio": "Studio", + "navbar.Toolkit": "Toolkit", "navbar.Tools": "Tools", "new_create": "Create New", "no": "No", diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index ce3341b945f..29223131be7 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -87,6 +87,7 @@ "permission.des.manage": "写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限", "permission.des.read": "可使用该应用进行对话", "permission.des.write": "可查看和编辑应用", + "plugin.Instructions": "使用说明", "plugin_cost_per_times": "{{cost}}/次", "plugin_dispatch": "插件调用", "plugin_dispatch_tip": "给模型附加获取外部数据的能力,具体调用哪些插件,将由模型自主决定,所有插件都将以非流模式运行。\n若选择了插件,知识库调用将自动作为一个特殊的插件。", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 47900073e3b..bf729495fcc 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -900,6 +900,7 @@ "navbar.Chat": "聊天", "navbar.Datasets": "知识库", "navbar.Studio": "工作台", + "navbar.Toolkit": "工具箱", "navbar.Tools": "工具", "new_create": "新建", "no": "否", diff --git a/projects/app/src/components/Layout/index.tsx b/projects/app/src/components/Layout/index.tsx index ff91e838dcf..293f27d8a84 100644 --- a/projects/app/src/components/Layout/index.tsx +++ b/projects/app/src/components/Layout/index.tsx @@ -82,7 +82,7 @@ const Layout = ({ children }: { children: JSX.Element }) => { - + {children} diff --git a/projects/app/src/components/Layout/navbar.tsx b/projects/app/src/components/Layout/navbar.tsx index 86c35589507..3bdfd51f89c 100644 --- a/projects/app/src/components/Layout/navbar.tsx +++ b/projects/app/src/components/Layout/navbar.tsx @@ -47,6 +47,13 @@ const Navbar = ({ unread }: { unread: number }) => { link: `/dataset/list`, activeLink: ['/dataset/list', '/dataset/detail'] }, + { + label: t('common:navbar.Toolkit'), + icon: 'phoneTabbar/tool', + activeIcon: 'phoneTabbar/toolFill', + link: `/toolkit`, + activeLink: ['/toolkit'] + }, { label: t('common:navbar.Account'), icon: 'support/user/userLight', @@ -85,6 +92,7 @@ const Navbar = ({ unread }: { unread: number }) => { w={'100%'} userSelect={'none'} pb={2} + bg={['/toolkit'].includes(router.pathname) ? 'myGray.25' : 'transparent'} > {/* logo */} { unread: 0 }, { - label: t('common:navbar.Tools'), - icon: 'phoneTabbar/tool', - activeIcon: 'phoneTabbar/toolFill', - link: '/tools', - activeLink: ['/tools'], + label: t('common:navbar.Datasets'), + icon: 'core/dataset/datasetLight', + activeIcon: 'core/dataset/datasetFill', + link: `/dataset/list`, + activeLink: ['/dataset/list', '/dataset/detail'], + unread: 0 + }, + { + label: t('common:common.More'), + icon: 'common/more', + activeIcon: 'common/moreFill', + link: '/more', + activeLink: ['/more'], unread: 0 }, { diff --git a/projects/app/src/pages/api/core/app/plugin/getPluginGroups.ts b/projects/app/src/pages/api/core/app/plugin/getPluginGroups.ts new file mode 100644 index 00000000000..ccbbf2cdad6 --- /dev/null +++ b/projects/app/src/pages/api/core/app/plugin/getPluginGroups.ts @@ -0,0 +1,34 @@ +import type { NextApiResponse } from 'next'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { NextAPI } from '@/service/middleware/entry'; +import { getPluginGroups, getSystemPlugins } from '@/service/core/app/plugin'; +import { ApiRequestProps } from '@fastgpt/service/type/next'; +import { PluginGroupSchemaType } from '@fastgpt/service/core/app/store/type'; +import { defaultGroup } from '@/pages/toolkit'; + +export type GetSystemPluginTemplatesBody = {}; + +async function handler( + req: ApiRequestProps, + res: NextApiResponse +): Promise { + await authCert({ req, authToken: true }); + const allGroups = await getPluginGroups(); + + const plugins = await getSystemPlugins(); + + const validGroups = allGroups.filter((group) => { + const groupTypes = group.groupTypes.filter((type) => + plugins.find((plugin) => plugin.templateType === type.typeId) + ); + return groupTypes.length > 0; + }); + + const sortedGroups = [defaultGroup, ...validGroups].sort( + (a, b) => (a.groupOrder ?? 0) - (b.groupOrder ?? 0) + ); + + return sortedGroups; +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/plugin/getSystemPluginTemplates.ts b/projects/app/src/pages/api/core/app/plugin/getSystemPluginTemplates.ts index 7b70463dd5d..5a1af4fee87 100644 --- a/projects/app/src/pages/api/core/app/plugin/getSystemPluginTemplates.ts +++ b/projects/app/src/pages/api/core/app/plugin/getSystemPluginTemplates.ts @@ -3,11 +3,11 @@ import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node.d'; import { NextAPI } from '@/service/middleware/entry'; import { getSystemPlugins } from '@/service/core/app/plugin'; -import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; import { ApiRequestProps } from '@fastgpt/service/type/next'; import { replaceRegChars } from '@fastgpt/global/common/string/tools'; +import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; export type GetSystemPluginTemplatesBody = { searchKey?: string; @@ -39,7 +39,9 @@ async function handler( intro: plugin.intro, isTool: plugin.isTool, currentCost: plugin.currentCost, - author: plugin.author + hasTokenFee: plugin.hasTokenFee, + author: plugin.author, + instructions: plugin.userGuide })) .filter((item) => { if (searchKey) { diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx index 737eefa737e..58aa2708e95 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx @@ -24,6 +24,7 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { getPreviewPluginNode, getSystemPlugTemplates, + getPluginGroups, getSystemPluginPaths } from '@/web/core/app/api/plugin'; import { useToast } from '@fastgpt/web/hooks/useToast'; @@ -45,12 +46,13 @@ import { useWorkflowUtils } from './hooks/useUtils'; import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants'; import { cloneDeep } from 'lodash'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; -import CostTooltip from '@/components/core/app/plugin/CostTooltip'; import { useUserStore } from '@/web/support/user/useUserStore'; import { LoopStartNode } from '@fastgpt/global/core/workflow/template/system/loop/loopStart'; import { LoopEndNode } from '@fastgpt/global/core/workflow/template/system/loop/loopEnd'; import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { WorkflowNodeEdgeContext } from '../context/workflowInitContext'; +import { defaultGroup } from '@/pages/toolkit'; +import CostTooltip from '@/components/core/app/plugin/CostTooltip'; type ModuleTemplateListProps = { isOpen: boolean; @@ -264,8 +266,8 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { value: TemplateTypeEnum.basic }, { - icon: 'core/modules/systemPlugin', - label: t('common:core.module.template.System Plugin'), + icon: 'phoneTabbar/tool', + label: t('common:navbar.Toolkit'), value: TemplateTypeEnum.systemPlugin }, { @@ -386,10 +388,10 @@ const RenderList = React.memo(function RenderList({ setParentId }: RenderListProps) { const { t } = useTranslation(); - const { feConfigs, setLoading } = useSystemStore(); + const { setLoading } = useSystemStore(); + const [collapsedGroups, setCollapsedGroups] = useState([]); const { isPc } = useSystem(); - const isSystemPlugin = type === TemplateTypeEnum.systemPlugin; const { screenToFlowPosition } = useReactFlow(); const { computedNewNodeName } = useWorkflowUtils(); @@ -398,14 +400,46 @@ const RenderList = React.memo(function RenderList({ const setNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setNodes); const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); - const formatTemplates = useMemo(() => { + const { data: pluginGroups = [] } = useRequest2(getPluginGroups, { + manual: false, + onSuccess(res) { + setCollapsedGroups(res.map((item) => item.groupName)); + } + }); + + const formatTemplatesArray = useMemo<{ list: NodeTemplateListType; label: string }[]>(() => { + if (type === TemplateTypeEnum.systemPlugin) { + const result = pluginGroups.map((group) => { + const copy: NodeTemplateListType = group.groupTypes.map((type) => ({ + list: [], + type: type.typeId, + label: type.typeName + })); + templates.forEach((item) => { + const index = copy.findIndex((template) => template.type === item.templateType); + if (index === -1) return; + copy[index].list.push(item); + }); + return { + label: group.groupName, + list: copy + }; + }); + + return result; + } const copy: NodeTemplateListType = cloneDeep(workflowNodeTemplateList); templates.forEach((item) => { const index = copy.findIndex((template) => template.type === item.templateType); if (index === -1) return; copy[index].list.push(item); }); - return copy.filter((item) => item.list.length > 0); + return [ + { + label: '', + list: copy.filter((item) => item.list.length > 0) + } + ]; // eslint-disable-next-line react-hooks/exhaustive-deps }, [templates, parentId]); @@ -556,112 +590,182 @@ const RenderList = React.memo(function RenderList({ ) : ( - {formatTemplates.map((item, i) => ( - - {item.label && formatTemplates.length > 1 && ( - - - {t(item.label as any)} - - - )} - - - {item.list.map((template) => ( - - - - - {t(template.name as any)} - - - - {t(template.intro as any) || t('common:core.workflow.Not intro')} - - {isSystemPlugin && } - - } + {formatTemplatesArray + .filter(({ list }) => list.length > 0) + .map(({ list, label }) => ( + + {!!label && ( + + {t(label as any)} { - if (e.clientX < sliderWidth) return; - onAddNode({ - template, - position: { x: e.clientX, y: e.clientY } - }); - }} - onClick={(e) => { - if (template.isFolder) { - return setParentId(template.id); - } - if (isPc) { - return onAddNode({ - template, - position: { x: sliderWidth * 1.5, y: 200 } - }); - } - onAddNode({ - template, - position: { x: e.clientX, y: e.clientY } + cursor={'pointer'} + onClick={() => { + setCollapsedGroups((state) => { + return state.includes(label) + ? state.filter((item) => item !== label) + : [...state, label]; }); - onClose(); }} > - - - - {t(template.name as any)} - - {gridStyle.authorInName && template.author !== undefined && ( - - {`by ${template.author || feConfigs.systemTitle}`} - - )} - - - {gridStyle.authorInRight && template.authorAvatar && template.author && ( - - - - {template.author} - - - )} - - ))} - - - ))} + + )} + {!collapsedGroups.includes(label) && ( + + {list.map((item, i) => { + return ( + + {item.label && item.list.length > 0 && ( + + + {t(item.label as any)} + + + )} + {item.list.length > 0 && ( + + {item.list.map((template) => { + return ( + + + + + {t(template.name as any)} + + + + {t(template.intro as any) || + t('common:core.workflow.Not intro')} + + {template.hasTokenFee && ( + + )} + + } + > + { + if (e.clientX < sliderWidth) return; + onAddNode({ + template, + position: { x: e.clientX, y: e.clientY } + }); + }} + onClick={(e) => { + if (template.isFolder) { + return setParentId(template.id); + } + if (isPc) { + return onAddNode({ + template, + position: { x: sliderWidth * 1.5, y: 200 } + }); + } + onAddNode({ + template, + position: { x: e.clientX, y: e.clientY } + }); + onClose(); + }} + whiteSpace={'nowrap'} + overflow={'hidden'} + textOverflow={'ellipsis'} + > + + + {t(template.name as any)} + + + {gridStyle.authorInRight && + template.authorAvatar && + template.author && ( + + + + {template.author} + + + )} + + + ); + })} + + )} + + ); + })} + + )} + + ))} ); diff --git a/projects/app/src/pages/app/list/index.tsx b/projects/app/src/pages/app/list/index.tsx index 8124e70fd79..f053892ae8e 100644 --- a/projects/app/src/pages/app/list/index.tsx +++ b/projects/app/src/pages/app/list/index.tsx @@ -30,6 +30,7 @@ import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import MyIcon from '@fastgpt/web/components/common/Icon'; import TemplateMarketModal from './components/TemplateMarketModal'; +import MyImage from '@fastgpt/web/components/common/Image/MyImage'; const CreateModal = dynamic(() => import('./components/CreateModal')); const EditFolderModal = dynamic( @@ -179,6 +180,33 @@ const MyApps = () => { {isPc && RenderSearchInput} + {isPc && ( + setTemplateModalType('all')} + > + + {t('app:template_market')} + + )} + {(folderDetail ? folderDetail.permission.hasWritePer && folderDetail?.type !== AppTypeEnum.httpPlugin : userInfo?.team.permission.hasWritePer) && ( @@ -218,16 +246,20 @@ const MyApps = () => { } ] }, - { - children: [ - { - icon: '/imgs/app/templateFill.svg', - label: t('app:template_market'), - description: t('app:template_market_description'), - onClick: () => setTemplateModalType('all') - } - ] - }, + ...(isPc + ? [] + : [ + { + children: [ + { + icon: '/imgs/app/templateFill.svg', + label: t('app:template_market'), + description: t('app:template_market_description'), + onClick: () => setTemplateModalType('all') + } + ] + } + ]), { children: [ { diff --git a/projects/app/src/pages/tools/index.tsx b/projects/app/src/pages/more/index.tsx similarity index 92% rename from projects/app/src/pages/tools/index.tsx rename to projects/app/src/pages/more/index.tsx index 7edaedc4b0b..13e4829c6c8 100644 --- a/projects/app/src/pages/tools/index.tsx +++ b/projects/app/src/pages/more/index.tsx @@ -8,15 +8,15 @@ import { serviceSideProps } from '@/web/common/utils/i18n'; import { useTranslation } from 'next-i18next'; import { getDocPath } from '@/web/common/system/doc'; -const Tools = () => { +const More = () => { const { t } = useTranslation(); const router = useRouter(); const { feConfigs } = useSystemStore(); const list = [ { - icon: 'core/dataset/datasetLight', - label: t('common:core.dataset.My Dataset'), - link: '/dataset/list' + icon: 'phoneTabbar/tool', + label: t('common:navbar.Toolkit'), + link: '/toolkit' }, ...(feConfigs?.show_git ? [ @@ -79,4 +79,4 @@ export async function getServerSideProps(content: any) { }; } -export default Tools; +export default More; diff --git a/projects/app/src/pages/toolkit/components/PluginCard.tsx b/projects/app/src/pages/toolkit/components/PluginCard.tsx new file mode 100644 index 00000000000..22161354183 --- /dev/null +++ b/projects/app/src/pages/toolkit/components/PluginCard.tsx @@ -0,0 +1,132 @@ +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import { Box, Flex, HStack, ModalBody } from '@chakra-ui/react'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import MyModal from '@fastgpt/web/components/common/MyModal'; +import Markdown from '@/components/Markdown'; +import { NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node'; +import { PluginGroupSchemaType } from '@fastgpt/service/core/app/store/type'; + +const PluginCard = ({ + item, + groups +}: { + item: NodeTemplateListItemType; + groups: PluginGroupSchemaType[]; +}) => { + const { t } = useTranslation(); + const { feConfigs } = useSystemStore(); + + const [currentPlugin, setCurrentPlugin] = useState(null); + + const type = groups.reduce((acc, group) => { + const foundType = group.groupTypes.find((type) => type.typeId === item.templateType); + return foundType ? foundType.typeName : acc; + }, undefined); + + return ( + + + + + {item.name} + + + + + {t(type as any)} + + + + + + {item.intro || t('app:templateMarket.no_intro')} + + + + + {item.instructions && ( + setCurrentPlugin(item)} + _hover={{ bg: 'myGray.100' }} + > + + {t('app:plugin.Instructions')} + + )} + + {`by ${item.author || feConfigs.systemTitle}`} + + {currentPlugin && ( + setCurrentPlugin(null)} /> + )} + + ); +}; + +const InstructionModal = ({ + currentPlugin, + onClose +}: { + currentPlugin: NodeTemplateListItemType; + onClose: () => void; +}) => { + return ( + + + + + + + + ); +}; + +export default React.memo(PluginCard); diff --git a/projects/app/src/pages/toolkit/index.tsx b/projects/app/src/pages/toolkit/index.tsx new file mode 100644 index 00000000000..864dabfc408 --- /dev/null +++ b/projects/app/src/pages/toolkit/index.tsx @@ -0,0 +1,226 @@ +import { serviceSideProps } from '@/web/common/utils/i18n'; +import { getPluginGroups, getSystemPlugTemplates } from '@/web/core/app/api/plugin'; +import { + Box, + Flex, + Grid, + Input, + InputGroup, + InputLeftElement, + useDisclosure +} from '@chakra-ui/react'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { useMemo, useState } from 'react'; +import PluginCard from './components/PluginCard'; +import { i18nT } from '@fastgpt/web/i18n/utils'; +import { useTranslation } from 'react-i18next'; +import { useRouter } from 'next/router'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useSystem } from '@fastgpt/web/hooks/useSystem'; +import { systemPluginTemplateList } from '@fastgpt/web/core/workflow/constants'; + +export const defaultGroup = { + groupId: 'systemPlugin', + groupAvatar: 'common/navbar/pluginLight', + groupName: i18nT('common:core.module.template.System Plugin'), + groupOrder: 0, + groupTypes: systemPluginTemplateList +}; + +export const allTypes = [ + { + typeId: 'all', + typeName: i18nT('common:common.All') + } +]; + +const Toolkit = () => { + const router = useRouter(); + const { isPc } = useSystem(); + + const { data: plugins = [] } = useRequest2(getSystemPlugTemplates, { + manual: false + }); + + const { data: pluginGroups = [] } = useRequest2(getPluginGroups, { + manual: false + }); + + const [search, setSearch] = useState(''); + + const { isOpen, onOpen, onClose } = useDisclosure(); + + const { group: selectedGroup = pluginGroups?.[0]?.groupName, type: selectedType = 'all' } = + router.query; + + const { t } = useTranslation(); + + const currentPluginGroupTypes = useMemo(() => { + const currentTypes = pluginGroups?.find( + (group) => group.groupName === selectedGroup + )?.groupTypes; + + return currentTypes?.filter((type) => + plugins.find((plugin) => plugin.templateType === type.typeId) + ); + }, [pluginGroups, plugins, selectedGroup]); + + const currentPlugins = useMemo(() => { + const typeArray = currentPluginGroupTypes?.map((type) => type.typeId); + return plugins + .filter( + (plugin) => + (selectedType === 'all' && typeArray?.includes(plugin.templateType)) || + selectedType === plugin.templateType + ) + .filter( + (plugin) => + plugin.name.includes(search) || + plugin.intro?.includes(search) || + plugin.instructions?.includes(search) + ); + }, [currentPluginGroupTypes, plugins, selectedType, search]); + + return ( + + {!isPc && isOpen && ( + + )} + {(isPc || isOpen) && ( + + {pluginGroups?.map((group) => { + const selected = group.groupName === selectedGroup; + console.log(currentPluginGroupTypes); + return ( + + + router.push({ + pathname: router.pathname, + query: { group: group.groupName, type: 'all' } + }) + } + > + + {t(group.groupName as any)} + + + + {selected && + [...allTypes, ...(currentPluginGroupTypes || [])]?.map((type) => { + const selected = type.typeId === selectedType; + return ( + { + router.push({ + pathname: router.pathname, + query: { ...router.query, type: type.typeId } + }); + }} + > + {t(type.typeName as any)} + + ); + })} + + ); + })} + + )} + + + + {!isPc && } + {t(selectedGroup as any)} + + + + + + + + + + {currentPlugins.map((item) => ( + + ))} + + + + ); +}; + +export default Toolkit; + +export async function getServerSideProps(context: any) { + return { + props: { + ...(await serviceSideProps(context, ['app', 'user'])) + } + }; +} diff --git a/projects/app/src/service/core/app/plugin.ts b/projects/app/src/service/core/app/plugin.ts index 218f29f0524..6f205242a77 100644 --- a/projects/app/src/service/core/app/plugin.ts +++ b/projects/app/src/service/core/app/plugin.ts @@ -5,11 +5,36 @@ import { GET, POST } from '@fastgpt/service/common/api/plusRequest'; import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type'; import { addLog } from '@fastgpt/service/common/system/log'; import { SystemPluginResponseType } from '@fastgpt/plugins/type'; +import { PluginGroupSchemaType } from '@fastgpt/service/core/app/store/type'; /* Get plugins */ const getCommercialPlugins = () => { return GET('/core/app/plugin/getSystemPlugins'); }; + +export const getPluginGroups = async (refresh = false) => { + if (isProduction && global.pluginGroups && global.pluginGroups.length > 0 && !refresh) + return cloneDeep(global.pluginGroups); + + try { + if (!global.pluginGroups) { + global.pluginGroups = []; + } + + global.pluginGroups = FastGPTProUrl + ? await GET('/core/app/plugin/getPluginGroups') + : []; + + addLog.info(`Load plugin groups successfully: ${global.pluginGroups.length}`); + + return cloneDeep(global.pluginGroups); + } catch (error) { + //@ts-ignore + global.pluginGroups = undefined; + return Promise.reject(error); + } +}; + export const getSystemPlugins = async (refresh = false) => { if (isProduction && global.systemPlugins && global.systemPlugins.length > 0 && !refresh) return cloneDeep(global.systemPlugins); @@ -68,6 +93,7 @@ export const getSystemPluginCb = async (refresh = false) => { try { global.systemPluginCb = {}; await getSystemPlugins(refresh); + await getPluginGroups(refresh); global.systemPluginCb = FastGPTProUrl ? await getCommercialCb() : await getCommunityCb(); return global.systemPluginCb; } catch (error) { diff --git a/projects/app/src/web/core/app/api/plugin.ts b/projects/app/src/web/core/app/api/plugin.ts index c7e3268f5c0..c5033aa92d8 100644 --- a/projects/app/src/web/core/app/api/plugin.ts +++ b/projects/app/src/web/core/app/api/plugin.ts @@ -1,4 +1,4 @@ -import { DELETE, GET, POST } from '@/web/common/api/request'; +import { GET, POST } from '@/web/common/api/request'; import type { createHttpPluginBody } from '@/pages/api/core/app/httpPlugin/create'; import type { UpdateHttpPluginBody } from '@/pages/api/core/app/httpPlugin/update'; import type { @@ -13,6 +13,7 @@ import type { GetPreviewNodeQuery } from '@/pages/api/core/app/plugin/getPreview import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { ParentIdType, ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; import { GetSystemPluginTemplatesBody } from '@/pages/api/core/app/plugin/getSystemPluginTemplates'; +import { PluginGroupSchemaType } from '@fastgpt/service/core/app/store/type'; /* ============ team plugin ============== */ export const getTeamPlugTemplates = (data?: ListAppBody) => @@ -40,6 +41,9 @@ export const getTeamPlugTemplates = (data?: ListAppBody) => export const getSystemPlugTemplates = (data: GetSystemPluginTemplatesBody) => POST('/core/app/plugin/getSystemPluginTemplates', data); +export const getPluginGroups = () => + GET('/core/app/plugin/getPluginGroups'); + export const getSystemPluginPaths = (parentId: ParentIdType) => { if (!parentId) return Promise.resolve([]); return GET('/core/app/plugin/path', { parentId });