diff --git a/docSite/content/zh-cn/docs/development/upgrading/4910.md b/docSite/content/zh-cn/docs/development/upgrading/4910.md index 8f6e2a846..6cffae54f 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/4910.md +++ b/docSite/content/zh-cn/docs/development/upgrading/4910.md @@ -1,5 +1,5 @@ --- -title: 'V4.9.10(进行中)' +title: 'V4.9.10' description: 'FastGPT V4.9.10 更新说明' icon: 'upgrade' draft: false diff --git a/docSite/content/zh-cn/docs/development/upgrading/4911.md b/docSite/content/zh-cn/docs/development/upgrading/4911.md new file mode 100644 index 000000000..ae2098bfc --- /dev/null +++ b/docSite/content/zh-cn/docs/development/upgrading/4911.md @@ -0,0 +1,21 @@ +--- +title: 'V4.9.11(进行中)' +description: 'FastGPT V4.9.11 更新说明' +icon: 'upgrade' +draft: false +toc: true +weight: 789 +--- + + +## 🚀 新增内容 + +1. 工作流中,子流程版本控制,可选择“保持最新版本”,无需手动更新。 + +## ⚙️ 优化 + + + +## 🐛 修复 + +1. 工作流中,管理员声明的全局系统工具,无法进行版本管理。 \ No newline at end of file diff --git a/packages/global/core/app/utils.ts b/packages/global/core/app/utils.ts index f2b3a8895..e841941c4 100644 --- a/packages/global/core/app/utils.ts +++ b/packages/global/core/app/utils.ts @@ -10,6 +10,8 @@ import { AppTypeEnum } from './constants'; import { AppErrEnum } from '../../common/error/code/app'; import { PluginErrEnum } from '../../common/error/code/plugin'; import { i18nT } from '../../../web/i18n/utils'; +import appErrList from '../../common/error/code/app'; +import pluginErrList from '../../common/error/code/plugin'; export const getDefaultAppForm = (): AppSimpleEditFormType => { return { @@ -190,17 +192,10 @@ export const getAppType = (config?: WorkflowTemplateBasicType | AppSimpleEditFor return ''; }; -export const formatToolError = (error?: string) => { - const unExistError: Array = [ - AppErrEnum.unAuthApp, - AppErrEnum.unExist, - PluginErrEnum.unAuth, - PluginErrEnum.unExist - ]; +export const formatToolError = (error?: any) => { + if (!error || typeof error !== 'string') return; - if (error && unExistError.includes(error)) { - return i18nT('app:un_auth'); - } else { - return error; - } + const errorText = appErrList[error]?.message || pluginErrList[error]?.message; + + return errorText || error; }; diff --git a/packages/global/core/workflow/type/node.d.ts b/packages/global/core/workflow/type/node.d.ts index eb128795b..9e03094a9 100644 --- a/packages/global/core/workflow/type/node.d.ts +++ b/packages/global/core/workflow/type/node.d.ts @@ -59,7 +59,6 @@ export type FlowNodeCommonType = { }; export type PluginDataType = { - version?: string; diagram?: string; userGuide?: string; courseUrl?: string; diff --git a/packages/plugins/src/DingTalkWebhook/template.json b/packages/plugins/src/DingTalkWebhook/template.json index 5195cc9ab..056e13d5c 100644 --- a/packages/plugins/src/DingTalkWebhook/template.json +++ b/packages/plugins/src/DingTalkWebhook/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "4816", "name": "钉钉 webhook", "avatar": "plugins/dingding", "intro": "向钉钉机器人发起 webhook 请求。", diff --git a/packages/plugins/src/Doc2X/PDF2text/template.json b/packages/plugins/src/Doc2X/PDF2text/template.json index 8d2368967..fd7eee77a 100644 --- a/packages/plugins/src/Doc2X/PDF2text/template.json +++ b/packages/plugins/src/Doc2X/PDF2text/template.json @@ -1,6 +1,5 @@ { "author": "Menghuan1918", - "version": "488", "name": "PDF识别", "avatar": "plugins/doc2x", "intro": "将PDF文件发送至Doc2X进行解析,返回结构化的LaTeX公式的文本(markdown),支持传入String类型的URL或者流程输出中的文件链接变量", diff --git a/packages/plugins/src/Doc2X/template.json b/packages/plugins/src/Doc2X/template.json index 52610d1bb..598cc0f6b 100644 --- a/packages/plugins/src/Doc2X/template.json +++ b/packages/plugins/src/Doc2X/template.json @@ -1,6 +1,5 @@ { "author": "Menghuan1918", - "version": "488", "name": "Doc2X服务", "avatar": "plugins/doc2x", "intro": "将传入的图片或PDF文件发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本。", diff --git a/packages/plugins/src/WeWorkWebhook/template.json b/packages/plugins/src/WeWorkWebhook/template.json index 1e5f7c2ad..516052203 100644 --- a/packages/plugins/src/WeWorkWebhook/template.json +++ b/packages/plugins/src/WeWorkWebhook/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "4816", "name": "企业微信 webhook", "avatar": "plugins/qiwei", "intro": "向企业微信机器人发起 webhook 请求。只能内部群使用。", diff --git a/packages/plugins/src/bing/template.json b/packages/plugins/src/bing/template.json index 0cb410b49..12f51f9b1 100644 --- a/packages/plugins/src/bing/template.json +++ b/packages/plugins/src/bing/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "4811", "name": "Bing搜索", "avatar": "core/workflow/template/bing", "intro": "在Bing中搜索。", diff --git a/packages/plugins/src/databaseConnection/template.json b/packages/plugins/src/databaseConnection/template.json index cd0627bda..39691d7f8 100644 --- a/packages/plugins/src/databaseConnection/template.json +++ b/packages/plugins/src/databaseConnection/template.json @@ -1,6 +1,5 @@ { "author": "silencezhang", - "version": "4811", "name": "数据库连接", "avatar": "core/workflow/template/datasource", "intro": "可连接常用数据库,并执行sql", diff --git a/packages/plugins/src/delay/template.json b/packages/plugins/src/delay/template.json index 2678d9480..f3f5e385f 100644 --- a/packages/plugins/src/delay/template.json +++ b/packages/plugins/src/delay/template.json @@ -1,6 +1,5 @@ { "author": "collin", - "version": "4817", "name": "流程等待", "avatar": "core/workflow/template/sleep", "intro": "让工作流等待指定时间后运行", diff --git a/packages/plugins/src/drawing/baseChart/template.json b/packages/plugins/src/drawing/baseChart/template.json index 15402be49..7180c7707 100644 --- a/packages/plugins/src/drawing/baseChart/template.json +++ b/packages/plugins/src/drawing/baseChart/template.json @@ -1,6 +1,5 @@ { "author": "silencezhang", - "version": "4817", "name": "基础图表", "avatar": "core/workflow/template/baseChart", "intro": "根据数据生成图表,可根据chartType生成柱状图,折线图,饼图", diff --git a/packages/plugins/src/drawing/template.json b/packages/plugins/src/drawing/template.json index 62a4b1297..4f9c6b432 100644 --- a/packages/plugins/src/drawing/template.json +++ b/packages/plugins/src/drawing/template.json @@ -1,6 +1,5 @@ { "author": "silencezhang", - "version": "486", "name": "BI图表功能", "avatar": "core/workflow/template/BI", "intro": "BI图表功能,可以生成一些常用的图表,如饼图,柱状图,折线图等", diff --git a/packages/plugins/src/duckduckgo/search/template.json b/packages/plugins/src/duckduckgo/search/template.json index d3ef2c3f7..1aaef5bc7 100644 --- a/packages/plugins/src/duckduckgo/search/template.json +++ b/packages/plugins/src/duckduckgo/search/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "486", "name": "DuckDuckGo 网络搜索", "avatar": "core/workflow/template/duckduckgo", "intro": "使用 DuckDuckGo 进行网络搜索", diff --git a/packages/plugins/src/duckduckgo/searchImg/template.json b/packages/plugins/src/duckduckgo/searchImg/template.json index 6dc110d29..84dad380d 100644 --- a/packages/plugins/src/duckduckgo/searchImg/template.json +++ b/packages/plugins/src/duckduckgo/searchImg/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "486", "name": "DuckDuckGo 图片搜索", "avatar": "core/workflow/template/duckduckgo", "intro": "使用 DuckDuckGo 进行图片搜索", diff --git a/packages/plugins/src/duckduckgo/searchNews/template.json b/packages/plugins/src/duckduckgo/searchNews/template.json index 0c2ef03ad..6068ccbd7 100644 --- a/packages/plugins/src/duckduckgo/searchNews/template.json +++ b/packages/plugins/src/duckduckgo/searchNews/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "486", "name": "DuckDuckGo 新闻检索", "avatar": "core/workflow/template/duckduckgo", "intro": "使用 DuckDuckGo 进行新闻检索", diff --git a/packages/plugins/src/duckduckgo/searchVideo/template.json b/packages/plugins/src/duckduckgo/searchVideo/template.json index 4bb9ad0ec..bfb3efbd0 100644 --- a/packages/plugins/src/duckduckgo/searchVideo/template.json +++ b/packages/plugins/src/duckduckgo/searchVideo/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "486", "name": "DuckDuckGo 视频搜索", "avatar": "core/workflow/template/duckduckgo", "intro": "使用 DuckDuckGo 进行视频搜索", diff --git a/packages/plugins/src/duckduckgo/template.json b/packages/plugins/src/duckduckgo/template.json index c3365694f..8fbd890da 100644 --- a/packages/plugins/src/duckduckgo/template.json +++ b/packages/plugins/src/duckduckgo/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "486", "name": "DuckDuckGo服务", "avatar": "core/workflow/template/duckduckgo", "intro": "DuckDuckGo 服务,包含网络搜索、图片搜索、新闻搜索等。", diff --git a/packages/plugins/src/feishu/template.json b/packages/plugins/src/feishu/template.json index 3fca64acc..ee30e9f10 100644 --- a/packages/plugins/src/feishu/template.json +++ b/packages/plugins/src/feishu/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "488", "name": "飞书 webhook", "avatar": "core/app/templates/plugin-feishu", "intro": "向飞书机器人发起 webhook 请求。", diff --git a/packages/plugins/src/fetchUrl/template.json b/packages/plugins/src/fetchUrl/template.json index 571787c17..26fc48760 100644 --- a/packages/plugins/src/fetchUrl/template.json +++ b/packages/plugins/src/fetchUrl/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "486", "name": "网页内容抓取", "avatar": "core/workflow/template/fetchUrl", "intro": "可获取一个网页链接内容,并以 Markdown 格式输出,仅支持获取静态网站。", diff --git a/packages/plugins/src/getTime/template.json b/packages/plugins/src/getTime/template.json index a49d68617..11a2775fa 100644 --- a/packages/plugins/src/getTime/template.json +++ b/packages/plugins/src/getTime/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "481", "templateType": "tools", "name": "获取当前时间", "avatar": "core/workflow/template/getTime", diff --git a/packages/plugins/src/google/template.json b/packages/plugins/src/google/template.json index 7009049ae..a4167973e 100644 --- a/packages/plugins/src/google/template.json +++ b/packages/plugins/src/google/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "4811", "name": "Google搜索", "avatar": "core/workflow/template/google", "intro": "在google中搜索。", diff --git a/packages/plugins/src/mathExprVal/template.json b/packages/plugins/src/mathExprVal/template.json index 717915bc8..2d440e59c 100644 --- a/packages/plugins/src/mathExprVal/template.json +++ b/packages/plugins/src/mathExprVal/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "486", "name": "数学公式执行", "avatar": "core/workflow/template/mathCall", "intro": "用于执行数学表达式的工具,通过 js 的 expr-eval 库运行表达式并返回结果。", diff --git a/packages/plugins/src/searchXNG/template.json b/packages/plugins/src/searchXNG/template.json index 639c19813..5b8f43047 100644 --- a/packages/plugins/src/searchXNG/template.json +++ b/packages/plugins/src/searchXNG/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "4816", "name": "Search XNG 搜索", "avatar": "core/workflow/template/searxng", "intro": "使用 Search XNG 服务进行搜索。", diff --git a/packages/plugins/src/smtpEmail/template.json b/packages/plugins/src/smtpEmail/template.json index 1af9d2d8d..33cdbeae9 100644 --- a/packages/plugins/src/smtpEmail/template.json +++ b/packages/plugins/src/smtpEmail/template.json @@ -1,6 +1,5 @@ { "author": "cloudpense", - "version": "1.0.0", "name": "Email 邮件发送", "avatar": "plugins/email", "intro": "通过SMTP协议发送电子邮件(nodemailer)", diff --git a/packages/plugins/src/template/template.json b/packages/plugins/src/template/template.json index e4cdfd832..0b6c6dc04 100644 --- a/packages/plugins/src/template/template.json +++ b/packages/plugins/src/template/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "489", "name": "文本加工", "avatar": "/imgs/workflow/textEditor.svg", "intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。", diff --git a/packages/plugins/src/wiki/template.json b/packages/plugins/src/wiki/template.json index c52da3d9c..1be627064 100644 --- a/packages/plugins/src/wiki/template.json +++ b/packages/plugins/src/wiki/template.json @@ -1,6 +1,5 @@ { "author": "", - "version": "4811", "name": "Wiki搜索", "avatar": "core/workflow/template/wiki", "intro": "在Wiki中查询释义。", diff --git a/packages/service/core/app/plugin/controller.ts b/packages/service/core/app/plugin/controller.ts index d8fa45fe4..cf72269f0 100644 --- a/packages/service/core/app/plugin/controller.ts +++ b/packages/service/core/app/plugin/controller.ts @@ -30,8 +30,7 @@ import { Types } from 'mongoose'; community: community-id commercial: commercial-id */ - -export async function splitCombinePluginId(id: string) { +export function splitCombineToolId(id: string) { const splitRes = id.split('-'); if (splitRes.length === 1) { // app id @@ -42,7 +41,7 @@ export async function splitCombinePluginId(id: string) { } const [source, pluginId] = id.split('-') as [PluginSourceEnum, string]; - if (!source || !pluginId) return Promise.reject('pluginId not found'); + if (!source || !pluginId) throw new Error('pluginId not found'); return { source, pluginId: id }; } @@ -54,7 +53,7 @@ const getSystemPluginTemplateById = async ( versionId?: string ): Promise => { const item = getSystemPluginTemplates().find((plugin) => plugin.id === pluginId); - if (!item) return Promise.reject(PluginErrEnum.unAuth); + if (!item) return Promise.reject(PluginErrEnum.unExist); const plugin = cloneDeep(item); @@ -64,10 +63,10 @@ const getSystemPluginTemplateById = async ( { pluginId: plugin.id, 'customConfig.associatedPluginId': plugin.associatedPluginId }, 'associatedPluginId' ).lean(); - if (!systemPlugin) return Promise.reject(PluginErrEnum.unAuth); + if (!systemPlugin) return Promise.reject(PluginErrEnum.unExist); const app = await MongoApp.findById(plugin.associatedPluginId).lean(); - if (!app) return Promise.reject(PluginErrEnum.unAuth); + if (!app) return Promise.reject(PluginErrEnum.unExist); const version = versionId ? await getAppVersionById({ @@ -77,6 +76,12 @@ const getSystemPluginTemplateById = async ( }) : await getAppLatestVersion(plugin.associatedPluginId, app); if (!version.versionId) return Promise.reject('App version not found'); + const isLatest = version.versionId + ? await checkIsLatestVersion({ + appId: plugin.associatedPluginId, + versionId: version.versionId + }) + : true; return { ...plugin, @@ -85,12 +90,19 @@ const getSystemPluginTemplateById = async ( edges: version.edges, chatConfig: version.chatConfig }, - version: versionId || String(version.versionId), + version: versionId ? version?.versionId : '', + versionLabel: version?.versionName, + isLatestVersion: isLatest, teamId: String(app.teamId), tmbId: String(app.tmbId) }; } - return plugin; + + return { + ...plugin, + version: undefined, + isLatestVersion: true + }; }; /* Format plugin to workflow preview node data */ @@ -102,11 +114,11 @@ export async function getChildAppPreviewNode({ versionId?: string; }): Promise { const app: ChildAppType = await (async () => { - const { source, pluginId } = await splitCombinePluginId(appId); + const { source, pluginId } = splitCombineToolId(appId); if (source === PluginSourceEnum.personal) { const item = await MongoApp.findById(appId).lean(); - if (!item) return Promise.reject('plugin not found'); + if (!item) return Promise.reject(PluginErrEnum.unExist); const version = await getAppVersionById({ appId, versionId, app: item }); @@ -132,8 +144,8 @@ export async function getChildAppPreviewNode({ }, templateType: FlowNodeTemplateTypeEnum.teamApp, - version: version.versionId, - versionLabel: version?.versionName || '', + version: versionId ? version?.versionId : '', + versionLabel: version?.versionName, isLatestVersion: isLatest, originCost: 0, @@ -142,7 +154,7 @@ export async function getChildAppPreviewNode({ pluginOrder: 0 }; } else { - return getSystemPluginTemplateById(pluginId); + return getSystemPluginTemplateById(pluginId, versionId); } })(); @@ -216,12 +228,12 @@ export async function getChildAppRuntimeById( id: string, versionId?: string ): Promise { - const app: ChildAppType = await (async () => { - const { source, pluginId } = await splitCombinePluginId(id); + const app = await (async () => { + const { source, pluginId } = splitCombineToolId(id); if (source === PluginSourceEnum.personal) { const item = await MongoApp.findById(id).lean(); - if (!item) return Promise.reject('plugin not found'); + if (!item) return Promise.reject(PluginErrEnum.unExist); const version = await getAppVersionById({ appId: id, @@ -244,8 +256,6 @@ export async function getChildAppRuntimeById( }, templateType: FlowNodeTemplateTypeEnum.teamApp, - // 用不到 - version: item?.pluginData?.nodeVersion, originCost: 0, currentCost: 0, hasTokenFee: false, diff --git a/packages/service/core/app/plugin/utils.ts b/packages/service/core/app/plugin/utils.ts index 6c21a659c..db4914d55 100644 --- a/packages/service/core/app/plugin/utils.ts +++ b/packages/service/core/app/plugin/utils.ts @@ -1,6 +1,6 @@ import { type ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; import { type PluginRuntimeType } from '@fastgpt/global/core/plugin/type'; -import { splitCombinePluginId } from './controller'; +import { splitCombineToolId } from './controller'; import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; /* @@ -20,7 +20,7 @@ export const computedPluginUsage = async ({ childrenUsage: ChatNodeUsageType[]; error?: boolean; }) => { - const { source } = await splitCombinePluginId(plugin.id); + const { source } = splitCombineToolId(plugin.id); const childrenUsages = childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0); if (source !== PluginSourceEnum.personal) { diff --git a/packages/service/core/app/utils.ts b/packages/service/core/app/utils.ts index 23dab1b1b..faf6ea4d8 100644 --- a/packages/service/core/app/utils.ts +++ b/packages/service/core/app/utils.ts @@ -1,14 +1,13 @@ import { MongoDataset } from '../dataset/schema'; import { getEmbeddingModel } from '../ai/model'; -import { - AppNodeFlowNodeTypeMap, - FlowNodeTypeEnum -} from '@fastgpt/global/core/workflow/node/constant'; +import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node'; -import { MongoAppVersion } from './version/schema'; -import { checkIsLatestVersion } from './version/controller'; -import { Types } from '../../common/mongo'; +import { getChildAppPreviewNode, splitCombineToolId } from './plugin/controller'; +import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; +import { authAppByTmbId } from '../../support/permission/app/auth'; +import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; +import { getErrText } from '@fastgpt/global/common/error/utils'; export async function listAppDatasetDataByTeamIdAndDatasetIds({ teamId, @@ -33,53 +32,58 @@ export async function listAppDatasetDataByTeamIdAndDatasetIds({ export async function rewriteAppWorkflowToDetail({ nodes, teamId, - isRoot + isRoot, + ownerTmbId }: { nodes: StoreNodeItemType[]; teamId: string; isRoot: boolean; + ownerTmbId: string; }) { const datasetIdSet = new Set(); - // Add node(App Type) versionlabel and latest sign - const appNodes = nodes.filter((node) => AppNodeFlowNodeTypeMap[node.flowNodeType]); - const versionIds = appNodes - .filter((node) => node.version && Types.ObjectId.isValid(node.version)) - .map((node) => node.version); + /* Add node(App Type) versionlabel and latest sign ==== */ + await Promise.all( + nodes.map(async (node) => { + if (!node.pluginId) return; + const { source } = splitCombineToolId(node.pluginId); - if (versionIds.length > 0) { - const versionDataList = await MongoAppVersion.find( - { - _id: { $in: versionIds } - }, - '_id versionName appId time' - ).lean(); + try { + const [preview] = await Promise.all([ + getChildAppPreviewNode({ + appId: node.pluginId, + versionId: node.version + }), + ...(source === PluginSourceEnum.personal + ? [ + authAppByTmbId({ + tmbId: ownerTmbId, + appId: node.pluginId, + per: ReadPermissionVal + }) + ] + : []) + ]); - const versionMap: Record = {}; - - const isLatestChecks = await Promise.all( - versionDataList.map(async (version) => { - const isLatest = await checkIsLatestVersion({ - appId: version.appId, - versionId: version._id - }); - - return { versionId: String(version._id), isLatest }; - }) - ); - const isLatestMap = new Map(isLatestChecks.map((item) => [item.versionId, item.isLatest])); - versionDataList.forEach((version) => { - versionMap[String(version._id)] = version; - }); - appNodes.forEach((node) => { - if (!node.version) return; - const versionData = versionMap[String(node.version)]; - if (versionData) { - node.versionLabel = versionData.versionName; - node.isLatestVersion = isLatestMap.get(String(node.version)) || false; + node.pluginData = { + diagram: preview.diagram, + userGuide: preview.userGuide, + courseUrl: preview.courseUrl, + name: preview.name, + avatar: preview.avatar + }; + node.versionLabel = preview.versionLabel; + node.isLatestVersion = preview.isLatestVersion; + node.version = preview.version; + } catch (error) { + node.pluginData = { + error: getErrText(error) + }; } - }); - } + }) + ); + + /* Add node(App Type) versionlabel and latest sign ==== */ // Get all dataset ids from nodes nodes.forEach((node) => { diff --git a/packages/service/core/app/version/controller.ts b/packages/service/core/app/version/controller.ts index dbb008e26..d7bcb69b5 100644 --- a/packages/service/core/app/version/controller.ts +++ b/packages/service/core/app/version/controller.ts @@ -68,6 +68,9 @@ export const checkIsLatestVersion = async ({ appId: string; versionId: string; }) => { + if (!Types.ObjectId.isValid(versionId)) { + return false; + } const version = await MongoAppVersion.findOne( { appId, diff --git a/packages/service/support/permission/app/auth.ts b/packages/service/support/permission/app/auth.ts index 79b3f57f6..8f0bd1332 100644 --- a/packages/service/support/permission/app/auth.ts +++ b/packages/service/support/permission/app/auth.ts @@ -10,7 +10,7 @@ import { AppPermission } from '@fastgpt/global/support/permission/app/controller import { type PermissionValueType } from '@fastgpt/global/support/permission/type'; import { AppFolderTypeList } from '@fastgpt/global/core/app/constants'; import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type'; -import { splitCombinePluginId } from '../../../core/app/plugin/controller'; +import { splitCombineToolId } from '../../../core/app/plugin/controller'; import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; import { type AuthModeType, type AuthResponseType } from '../type'; import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant'; @@ -24,7 +24,7 @@ export const authPluginByTmbId = async ({ appId: string; per: PermissionValueType; }) => { - const { source } = await splitCombinePluginId(appId); + const { source } = splitCombineToolId(appId); if (source === PluginSourceEnum.personal) { const { app } = await authAppByTmbId({ appId, diff --git a/packages/web/components/common/Textarea/PromptEditor/plugins/VariablePlugin/components/Variable.tsx b/packages/web/components/common/Textarea/PromptEditor/plugins/VariablePlugin/components/Variable.tsx index aa7813e58..7b3630253 100644 --- a/packages/web/components/common/Textarea/PromptEditor/plugins/VariablePlugin/components/Variable.tsx +++ b/packages/web/components/common/Textarea/PromptEditor/plugins/VariablePlugin/components/Variable.tsx @@ -18,7 +18,7 @@ export default function Variable({ variableLabel }: { variableLabel: string }) { : { bg: 'red.50', color: 'red.600' })} > {variableLabel ? ( - {variableLabel} + {t(variableLabel as any)} ) : ( {t('common:invalid_variable')} )} diff --git a/packages/web/hooks/useScrollPagination.tsx b/packages/web/hooks/useScrollPagination.tsx index 93dcf1a10..7399771aa 100644 --- a/packages/web/hooks/useScrollPagination.tsx +++ b/packages/web/hooks/useScrollPagination.tsx @@ -187,7 +187,7 @@ export function useScrollPagination< scrollLoadType = 'bottom', pageSize = 10, - params = {}, + params, EmptyTip, showErrorToast = true, disalbed = false, @@ -196,7 +196,7 @@ export function useScrollPagination< scrollLoadType?: 'top' | 'bottom'; pageSize?: number; - params?: Record; + params?: Omit; EmptyTip?: React.JSX.Element; showErrorToast?: boolean; disalbed?: boolean; diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index fc9e8913e..7a7ecf51a 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -85,6 +85,7 @@ "interval.per_hour": "Every Hour", "intro": "A comprehensive model application orchestration system that offers out-of-the-box data processing and model invocation capabilities. It allows for rapid Dataset construction and workflow orchestration through Flow visualization, enabling complex Dataset scenarios!", "invalid_json_format": "JSON format error", + "keep_the_latest": "Keep the latest", "llm_not_support_vision": "This model does not support image recognition", "llm_use_vision": "Vision", "llm_use_vision_tip": "After clicking on the model selection, you can see whether the model supports image recognition and the ability to control whether to start image recognition. \nAfter starting image recognition, the model will read the image content in the file link, and if the user question is less than 500 words, it will automatically parse the image in the user question.", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 64a724b42..cc65ed973 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -145,8 +145,8 @@ "code_error.outlink_error.invalid_link": "Invalid Share Link", "code_error.outlink_error.link_not_exist": "Share Link Does Not Exist", "code_error.outlink_error.un_auth_user": "Identity Verification Failed", - "code_error.plugin_error.not_exist": "Plugin Does Not Exist", - "code_error.plugin_error.un_auth": "Unauthorized to Operate This Plugin", + "code_error.plugin_error.not_exist": "The tool does not exist", + "code_error.plugin_error.un_auth": "No permission to operate the tool", "code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://tryfastgpt.ai", "code_error.system_error.license_app_amount_limit": "Exceed the maximum number of applications in the system", "code_error.system_error.license_dataset_amount_limit": "Exceed the maximum number of knowledge bases in the system", diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index 1d49f4963..c88774b4c 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -85,6 +85,7 @@ "interval.per_hour": "每小时", "intro": "是一个大模型应用编排系统,提供开箱即用的数据处理、模型调用等能力,可以快速的构建知识库并通过 Flow 可视化进行工作流编排,实现复杂的知识库场景!", "invalid_json_format": "JSON 格式错误", + "keep_the_latest": "保持最新版本", "llm_not_support_vision": "该模型不支持图片识别", "llm_use_vision": "图片识别", "llm_use_vision_tip": "点击模型选择后,可以看到模型是否支持图片识别以及控制是否启动图片识别的能力。启动图片识别后,模型会读取文件链接里图片内容,并且如果用户问题少于 500 字,会自动解析用户问题中的图片。", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 5a0a8ab1e..f34667686 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -145,8 +145,8 @@ "code_error.outlink_error.invalid_link": "分享链接无效", "code_error.outlink_error.link_not_exist": "分享链接不存在", "code_error.outlink_error.un_auth_user": "身份校验失败", - "code_error.plugin_error.not_exist": "插件不存在", - "code_error.plugin_error.un_auth": "无权操作该插件", + "code_error.plugin_error.not_exist": "工具不存在", + "code_error.plugin_error.un_auth": "无权操作该工具", "code_error.system_error.community_version_num_limit": "超出开源版数量限制,请升级商业版: https://fastgpt.in", "code_error.system_error.license_app_amount_limit": "超出系统最大应用数量", "code_error.system_error.license_dataset_amount_limit": "超出系统最大知识库数量", diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index b9550c14e..cdc57c698 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -85,6 +85,7 @@ "interval.per_hour": "每小時", "intro": "FastGPT 是一個基於大型語言模型的知識庫平臺,提供開箱即用的資料處理、向量檢索和視覺化 AI 工作流程編排等功能,讓您可以輕鬆開發和部署複雜的問答系統,而無需繁瑣的設定或設定。", "invalid_json_format": "JSON 格式錯誤", + "keep_the_latest": "保持最新版本", "llm_not_support_vision": "這個模型不支援圖片辨識", "llm_use_vision": "圖片辨識", "llm_use_vision_tip": "點選模型選擇後,可以看到模型是否支援圖片辨識以及控制是否啟用圖片辨識的功能。啟用圖片辨識後,模型會讀取檔案連結中的圖片內容,並且如果使用者問題少於 500 字,會自動解析使用者問題中的圖片。", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index fd1927a5f..d7312e69d 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -145,8 +145,8 @@ "code_error.outlink_error.invalid_link": "分享連結無效", "code_error.outlink_error.link_not_exist": "分享連結不存在", "code_error.outlink_error.un_auth_user": "身份驗證失敗", - "code_error.plugin_error.not_exist": "外掛程式不存在", - "code_error.plugin_error.un_auth": "無權操作此外掛程式", + "code_error.plugin_error.not_exist": "工具不存在", + "code_error.plugin_error.un_auth": "無權操作該工具", "code_error.system_error.community_version_num_limit": "超出開源版數量限制,請升級商業版:https://tryfastgpt.ai", "code_error.system_error.license_app_amount_limit": "超出系統最大應用數量", "code_error.system_error.license_dataset_amount_limit": "超出系統最大知識庫數量", diff --git a/projects/app/src/global/core/chat/api.d.ts b/projects/app/src/global/core/chat/api.d.ts index 533901888..77dc6432d 100644 --- a/projects/app/src/global/core/chat/api.d.ts +++ b/projects/app/src/global/core/chat/api.d.ts @@ -24,7 +24,7 @@ export type GetChatRecordsProps = OutLinkChatAuthProps & { appId: string; chatId?: string; loadCustomFeedbacks?: boolean; - type: `${GetChatTypeEnum}`; + type?: `${GetChatTypeEnum}`; }; export type InitOutLinkChatProps = { diff --git a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx index 5771011da..1bb2f98a2 100644 --- a/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx +++ b/projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/NodeCard.tsx @@ -1,5 +1,5 @@ -import React, { useCallback, useMemo, useRef } from 'react'; -import { Box, Button, Flex, HStack, useDisclosure, type FlexProps } from '@chakra-ui/react'; +import React, { useCallback, useMemo } from 'react'; +import { Box, Button, Flex, useDisclosure, type FlexProps } from '@chakra-ui/react'; import MyIcon from '@fastgpt/web/components/common/Icon'; import Avatar from '@fastgpt/web/components/common/Avatar'; import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d'; @@ -15,7 +15,7 @@ import { ToolSourceHandle, ToolTargetHandle } from './Handle/ToolHandle'; import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea'; import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/ConnectionHandle'; import { useDebug } from '../../hooks/useDebug'; -import { getPreviewPluginNode } from '@/web/core/app/api/plugin'; +import { getPreviewPluginNode, getToolVersionList } from '@/web/core/app/api/plugin'; import { storeNode2FlowNode } from '@/web/core/workflow/utils'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { useContextSelector } from 'use-context-selector'; @@ -104,12 +104,9 @@ const NodeCard = (props: Props) => { }, [nodeList, nodeId]); const isAppNode = node && AppNodeFlowNodeTypeMap[node?.flowNodeType]; const showVersion = useMemo(() => { - if (!isAppNode || !node?.pluginId) return false; + if (!isAppNode || !node?.pluginId || node?.pluginData?.error) return false; if ([FlowNodeTypeEnum.tool, FlowNodeTypeEnum.toolSet].includes(node.flowNodeType)) return false; - if (node.pluginId.split('-').length > 1) { - return false; - } - return true; + return typeof node.version === 'string'; }, [isAppNode, node]); const { data: nodeTemplate } = useRequest2( @@ -617,11 +614,10 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt const { isOpen, onOpen, onClose } = useDisclosure(); // Load version list - const { ScrollData, data: versionList } = useScrollPagination(getAppVersionList, { + const { ScrollData, data: versionList } = useScrollPagination(getToolVersionList, { pageSize: 20, params: { - appId: node.pluginId, - isPublish: true + toolId: node.pluginId }, refreshDeps: [node.pluginId, isOpen], disalbed: !isOpen, @@ -653,18 +649,23 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt } ); - const renderList = useCreation( - () => - versionList.map((item) => ({ + const renderVersionList = useCreation( + () => [ + { + label: t('app:keep_the_latest'), + value: '' + }, + ...versionList.map((item) => ({ label: item.versionName, value: item._id - })), + })) + ], [node.isLatestVersion, node.version, t, versionList] ); const valueLabel = useMemo(() => { return ( - {node?.versionLabel} + {node?.version === '' ? t('app:keep_the_latest') : node?.versionLabel} {!node.isLatestVersion && ( {t('app:not_the_newest')} @@ -672,7 +673,7 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt )} ); - }, [node.isLatestVersion, node?.versionLabel, t]); + }, [node.isLatestVersion, node?.version, node?.versionLabel, t]); return ( ( {props.children} diff --git a/projects/app/src/pages/api/core/app/detail.ts b/projects/app/src/pages/api/core/app/detail.ts index 534e1fb50..f8f2515f5 100644 --- a/projects/app/src/pages/api/core/app/detail.ts +++ b/projects/app/src/pages/api/core/app/detail.ts @@ -3,7 +3,6 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { NextAPI } from '@/service/middleware/entry'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; -import { checkNode } from '@/service/core/app/utils'; import { rewriteAppWorkflowToDetail } from '@fastgpt/service/core/app/utils'; /* 获取应用详情 */ async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -23,6 +22,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { await rewriteAppWorkflowToDetail({ nodes: app.modules, teamId, + ownerTmbId: app.tmbId, isRoot }); @@ -34,12 +34,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { }; } - return { - ...app, - modules: await Promise.all( - app.modules.map((node) => checkNode({ node, ownerTmbId: app.tmbId })) - ) - }; + return app; } export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/plugin/getPreviewNode.ts b/projects/app/src/pages/api/core/app/plugin/getPreviewNode.ts index 65a57778f..f0451858d 100644 --- a/projects/app/src/pages/api/core/app/plugin/getPreviewNode.ts +++ b/projects/app/src/pages/api/core/app/plugin/getPreviewNode.ts @@ -4,7 +4,7 @@ import type { NextApiResponse } from 'next'; import { getChildAppPreviewNode, - splitCombinePluginId + splitCombineToolId } from '@fastgpt/service/core/app/plugin/controller'; import { type FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d'; import { NextAPI } from '@/service/middleware/entry'; @@ -21,7 +21,7 @@ async function handler( ): Promise { const { appId, versionId } = req.query; - const { source } = await splitCombinePluginId(appId); + const { source } = splitCombineToolId(appId); if (source === PluginSourceEnum.personal) { await authApp({ req, authToken: true, appId, per: ReadPermissionVal }); diff --git a/projects/app/src/pages/api/core/app/plugin/getVersionList.ts b/projects/app/src/pages/api/core/app/plugin/getVersionList.ts new file mode 100644 index 000000000..8b483c9f0 --- /dev/null +++ b/projects/app/src/pages/api/core/app/plugin/getVersionList.ts @@ -0,0 +1,86 @@ +import type { NextApiResponse } from 'next'; +import { NextAPI } from '@/service/middleware/entry'; +import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema'; +import { type PaginationProps, type PaginationResponse } from '@fastgpt/web/common/fetch/type'; +import { type ApiRequestProps } from '@fastgpt/service/type/next'; +import { authApp } from '@fastgpt/service/support/permission/app/auth'; +import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; +import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; +import { splitCombineToolId } from '@fastgpt/service/core/app/plugin/controller'; +import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; +import { getSystemPluginTemplates } from '@fastgpt/plugins/register'; +import { PluginErrEnum } from '@fastgpt/global/common/error/code/plugin'; +import { Types } from '@fastgpt/service/common/mongo'; + +export type getToolVersionListProps = PaginationProps<{ + toolId?: string; +}>; + +export type getToolVersionResponse = PaginationResponse<{ + _id: string; + versionName: string; +}>; + +async function handler( + req: ApiRequestProps, + _res: NextApiResponse +): Promise { + const { toolId } = req.body; + const { offset, pageSize } = parsePaginationRequest(req); + + if (!toolId) { + return { + total: 0, + list: [] + }; + } + + const { source, pluginId: formatToolId } = splitCombineToolId(toolId); + + // Auth + const appId = await (async () => { + if (source === PluginSourceEnum.personal) { + const { app } = await authApp({ + appId: formatToolId, + req, + per: ReadPermissionVal, + authToken: true + }); + return app._id; + } else { + const item = getSystemPluginTemplates().find((plugin) => plugin.id === formatToolId); + if (!item) return Promise.reject(PluginErrEnum.unAuth); + return item.associatedPluginId; + } + })(); + + if (!appId || !Types.ObjectId.isValid(appId)) { + return { + total: 0, + list: [] + }; + } + + const match = { + appId, + isPublish: true + }; + + const [result, total] = await Promise.all([ + await MongoAppVersion.find(match, 'versionName') + .sort({ + time: -1 + }) + .skip(offset) + .limit(pageSize) + .lean(), + MongoAppVersion.countDocuments(match) + ]); + + return { + total, + list: result + }; +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/version/detail.ts b/projects/app/src/pages/api/core/app/version/detail.ts index 14e3256e5..836acdae3 100644 --- a/projects/app/src/pages/api/core/app/version/detail.ts +++ b/projects/app/src/pages/api/core/app/version/detail.ts @@ -5,7 +5,6 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { type AppVersionSchemaType } from '@fastgpt/global/core/app/version'; import { formatTime2YMDHM } from '@fastgpt/global/common/string/time'; -import { checkNode } from '@/service/core/app/utils'; import { rewriteAppWorkflowToDetail } from '@fastgpt/service/core/app/utils'; type Props = { @@ -34,14 +33,12 @@ async function handler( await rewriteAppWorkflowToDetail({ nodes: result.nodes, teamId, + ownerTmbId: app.tmbId, isRoot }); return { ...result, - nodes: await Promise.all( - result.nodes.map((n) => checkNode({ node: n, ownerTmbId: app.tmbId })) - ), versionName: result?.versionName || formatTime2YMDHM(result?.time) }; } diff --git a/projects/app/src/pages/api/core/app/version/latest.ts b/projects/app/src/pages/api/core/app/version/latest.ts index 8d36036ee..add116bfd 100644 --- a/projects/app/src/pages/api/core/app/version/latest.ts +++ b/projects/app/src/pages/api/core/app/version/latest.ts @@ -31,13 +31,16 @@ async function handler( per: WritePermissionVal }); + const version = await getAppLatestVersion(req.query.appId, app); + await rewriteAppWorkflowToDetail({ - nodes: app.modules, + nodes: version.nodes, teamId, - isRoot + isRoot, + ownerTmbId: app.tmbId }); - return getAppLatestVersion(req.query.appId, app); + return version; } export default NextAPI(handler); diff --git a/projects/app/src/service/core/app/utils.ts b/projects/app/src/service/core/app/utils.ts index 0377f96fe..f83268e29 100644 --- a/projects/app/src/service/core/app/utils.ts +++ b/projects/app/src/service/core/app/utils.ts @@ -24,7 +24,7 @@ import { saveChat } from '@fastgpt/service/core/chat/saveChat'; import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller'; import { getChildAppPreviewNode, - splitCombinePluginId + splitCombineToolId } from '@fastgpt/service/core/app/plugin/controller'; import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants'; import { authAppByTmbId } from '@fastgpt/service/support/permission/app/auth'; @@ -137,46 +137,3 @@ export const getScheduleTriggerApp = async () => { }) ); }; - -export const checkNode = async ({ - node, - ownerTmbId -}: { - node: StoreNodeItemType; - ownerTmbId: string; -}): Promise => { - const pluginId = node.pluginId; - if (!pluginId) return node; - - try { - const { source } = await splitCombinePluginId(pluginId); - - if (source === PluginSourceEnum.personal) { - await authAppByTmbId({ - tmbId: ownerTmbId, - appId: pluginId, - per: ReadPermissionVal - }); - } - - const preview = await getChildAppPreviewNode({ appId: pluginId }); - return { - ...node, - pluginData: { - version: preview.version, - diagram: preview.diagram, - userGuide: preview.userGuide, - courseUrl: preview.courseUrl, - name: preview.name, - avatar: preview.avatar - } - }; - } catch (error: any) { - return { - ...node, - pluginData: { - error: getErrText(error) - } - }; - } -}; diff --git a/projects/app/src/web/core/app/api/plugin.ts b/projects/app/src/web/core/app/api/plugin.ts index 67d6ad9af..635fadbfc 100644 --- a/projects/app/src/web/core/app/api/plugin.ts +++ b/projects/app/src/web/core/app/api/plugin.ts @@ -24,6 +24,10 @@ import { type McpToolConfigType } from '@fastgpt/global/core/app/type'; import type { updateMCPToolsBody } from '@/pages/api/core/app/mcpTools/update'; import type { RunMCPToolBody } from '@/pages/api/support/mcp/client/runTool'; import type { getMCPToolsBody } from '@/pages/api/support/mcp/client/getTools'; +import type { + getToolVersionListProps, + getToolVersionResponse +} from '@/pages/api/core/app/plugin/getVersionList'; /* ============ team plugin ============== */ export const getTeamPlugTemplates = (data?: ListAppBody) => @@ -71,6 +75,9 @@ export const getSystemPluginPaths = (data: GetPathProps) => { export const getPreviewPluginNode = (data: GetPreviewNodeQuery) => GET('/core/app/plugin/getPreviewNode', data); +export const getToolVersionList = (data: getToolVersionListProps) => + POST('/core/app/plugin/getVersionList', data); + /* ============ mcp tools ============== */ export const postCreateMCPTools = (data: createMCPToolsBody) => POST('/core/app/mcpTools/create', data); diff --git a/projects/app/src/web/core/chat/context/chatRecordContext.tsx b/projects/app/src/web/core/chat/context/chatRecordContext.tsx index 60cb73519..61e5ab429 100644 --- a/projects/app/src/web/core/chat/context/chatRecordContext.tsx +++ b/projects/app/src/web/core/chat/context/chatRecordContext.tsx @@ -51,7 +51,7 @@ const ChatRecordContextProvider = ({ params }: { children: ReactNode; - params: Record; + params: Omit; }) => { const ChatBoxRef = useContextSelector(ChatItemContext, (v) => v.ChatBoxRef); const [isChatRecordsLoaded, setIsChatRecordsLoaded] = useState(false);