mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-20 19:24:29 +00:00
Feat: Node latest version (#4905)
* node versions add keep the latest option (#4899) * node versions add keep the latest option * i18n * perf: version code * fix: ts * hide system version * hide system version * hide system version * fix: ts * fix: ts --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.9.10(进行中)'
|
||||
title: 'V4.9.10'
|
||||
description: 'FastGPT V4.9.10 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
21
docSite/content/zh-cn/docs/development/upgrading/4911.md
Normal file
21
docSite/content/zh-cn/docs/development/upgrading/4911.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: 'V4.9.11(进行中)'
|
||||
description: 'FastGPT V4.9.11 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 789
|
||||
---
|
||||
|
||||
|
||||
## 🚀 新增内容
|
||||
|
||||
1. 工作流中,子流程版本控制,可选择“保持最新版本”,无需手动更新。
|
||||
|
||||
## ⚙️ 优化
|
||||
|
||||
|
||||
|
||||
## 🐛 修复
|
||||
|
||||
1. 工作流中,管理员声明的全局系统工具,无法进行版本管理。
|
@@ -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<string> = [
|
||||
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;
|
||||
};
|
||||
|
1
packages/global/core/workflow/type/node.d.ts
vendored
1
packages/global/core/workflow/type/node.d.ts
vendored
@@ -59,7 +59,6 @@ export type FlowNodeCommonType = {
|
||||
};
|
||||
|
||||
export type PluginDataType = {
|
||||
version?: string;
|
||||
diagram?: string;
|
||||
userGuide?: string;
|
||||
courseUrl?: string;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "4816",
|
||||
"name": "钉钉 webhook",
|
||||
"avatar": "plugins/dingding",
|
||||
"intro": "向钉钉机器人发起 webhook 请求。",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "Menghuan1918",
|
||||
"version": "488",
|
||||
"name": "PDF识别",
|
||||
"avatar": "plugins/doc2x",
|
||||
"intro": "将PDF文件发送至Doc2X进行解析,返回结构化的LaTeX公式的文本(markdown),支持传入String类型的URL或者流程输出中的文件链接变量",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "Menghuan1918",
|
||||
"version": "488",
|
||||
"name": "Doc2X服务",
|
||||
"avatar": "plugins/doc2x",
|
||||
"intro": "将传入的图片或PDF文件发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本。",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "4816",
|
||||
"name": "企业微信 webhook",
|
||||
"avatar": "plugins/qiwei",
|
||||
"intro": "向企业微信机器人发起 webhook 请求。只能内部群使用。",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "4811",
|
||||
"name": "Bing搜索",
|
||||
"avatar": "core/workflow/template/bing",
|
||||
"intro": "在Bing中搜索。",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "silencezhang",
|
||||
"version": "4811",
|
||||
"name": "数据库连接",
|
||||
"avatar": "core/workflow/template/datasource",
|
||||
"intro": "可连接常用数据库,并执行sql",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "collin",
|
||||
"version": "4817",
|
||||
"name": "流程等待",
|
||||
"avatar": "core/workflow/template/sleep",
|
||||
"intro": "让工作流等待指定时间后运行",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "silencezhang",
|
||||
"version": "4817",
|
||||
"name": "基础图表",
|
||||
"avatar": "core/workflow/template/baseChart",
|
||||
"intro": "根据数据生成图表,可根据chartType生成柱状图,折线图,饼图",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "silencezhang",
|
||||
"version": "486",
|
||||
"name": "BI图表功能",
|
||||
"avatar": "core/workflow/template/BI",
|
||||
"intro": "BI图表功能,可以生成一些常用的图表,如饼图,柱状图,折线图等",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "486",
|
||||
"name": "DuckDuckGo 网络搜索",
|
||||
"avatar": "core/workflow/template/duckduckgo",
|
||||
"intro": "使用 DuckDuckGo 进行网络搜索",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "486",
|
||||
"name": "DuckDuckGo 图片搜索",
|
||||
"avatar": "core/workflow/template/duckduckgo",
|
||||
"intro": "使用 DuckDuckGo 进行图片搜索",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "486",
|
||||
"name": "DuckDuckGo 新闻检索",
|
||||
"avatar": "core/workflow/template/duckduckgo",
|
||||
"intro": "使用 DuckDuckGo 进行新闻检索",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "486",
|
||||
"name": "DuckDuckGo 视频搜索",
|
||||
"avatar": "core/workflow/template/duckduckgo",
|
||||
"intro": "使用 DuckDuckGo 进行视频搜索",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "486",
|
||||
"name": "DuckDuckGo服务",
|
||||
"avatar": "core/workflow/template/duckduckgo",
|
||||
"intro": "DuckDuckGo 服务,包含网络搜索、图片搜索、新闻搜索等。",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "488",
|
||||
"name": "飞书 webhook",
|
||||
"avatar": "core/app/templates/plugin-feishu",
|
||||
"intro": "向飞书机器人发起 webhook 请求。",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "486",
|
||||
"name": "网页内容抓取",
|
||||
"avatar": "core/workflow/template/fetchUrl",
|
||||
"intro": "可获取一个网页链接内容,并以 Markdown 格式输出,仅支持获取静态网站。",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "481",
|
||||
"templateType": "tools",
|
||||
"name": "获取当前时间",
|
||||
"avatar": "core/workflow/template/getTime",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "4811",
|
||||
"name": "Google搜索",
|
||||
"avatar": "core/workflow/template/google",
|
||||
"intro": "在google中搜索。",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "486",
|
||||
"name": "数学公式执行",
|
||||
"avatar": "core/workflow/template/mathCall",
|
||||
"intro": "用于执行数学表达式的工具,通过 js 的 expr-eval 库运行表达式并返回结果。",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "4816",
|
||||
"name": "Search XNG 搜索",
|
||||
"avatar": "core/workflow/template/searxng",
|
||||
"intro": "使用 Search XNG 服务进行搜索。",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "cloudpense",
|
||||
"version": "1.0.0",
|
||||
"name": "Email 邮件发送",
|
||||
"avatar": "plugins/email",
|
||||
"intro": "通过SMTP协议发送电子邮件(nodemailer)",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "489",
|
||||
"name": "文本加工",
|
||||
"avatar": "/imgs/workflow/textEditor.svg",
|
||||
"intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。",
|
||||
|
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"author": "",
|
||||
"version": "4811",
|
||||
"name": "Wiki搜索",
|
||||
"avatar": "core/workflow/template/wiki",
|
||||
"intro": "在Wiki中查询释义。",
|
||||
|
@@ -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<ChildAppType> => {
|
||||
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<FlowNodeTemplateType> {
|
||||
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<PluginRuntimeType> {
|
||||
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,
|
||||
|
@@ -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) {
|
||||
|
@@ -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<string>();
|
||||
|
||||
// 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<string, any> = {};
|
||||
|
||||
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) => {
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -18,7 +18,7 @@ export default function Variable({ variableLabel }: { variableLabel: string }) {
|
||||
: { bg: 'red.50', color: 'red.600' })}
|
||||
>
|
||||
{variableLabel ? (
|
||||
<Flex alignItems={'center'}>{variableLabel}</Flex>
|
||||
<Flex alignItems={'center'}>{t(variableLabel as any)}</Flex>
|
||||
) : (
|
||||
<Box>{t('common:invalid_variable')}</Box>
|
||||
)}
|
||||
|
@@ -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<string, any>;
|
||||
params?: Omit<TParams, 'offset' | 'pageSize'>;
|
||||
EmptyTip?: React.JSX.Element;
|
||||
showErrorToast?: boolean;
|
||||
disalbed?: boolean;
|
||||
|
@@ -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.",
|
||||
|
@@ -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",
|
||||
|
@@ -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 字,会自动解析用户问题中的图片。",
|
||||
|
@@ -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": "超出系统最大知识库数量",
|
||||
|
@@ -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 字,會自動解析使用者問題中的圖片。",
|
||||
|
@@ -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": "超出系統最大知識庫數量",
|
||||
|
2
projects/app/src/global/core/chat/api.d.ts
vendored
2
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -24,7 +24,7 @@ export type GetChatRecordsProps = OutLinkChatAuthProps & {
|
||||
appId: string;
|
||||
chatId?: string;
|
||||
loadCustomFeedbacks?: boolean;
|
||||
type: `${GetChatTypeEnum}`;
|
||||
type?: `${GetChatTypeEnum}`;
|
||||
};
|
||||
|
||||
export type InitOutLinkChatProps = {
|
||||
|
@@ -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 (
|
||||
<Flex alignItems={'center'} gap={0.5}>
|
||||
{node?.versionLabel}
|
||||
{node?.version === '' ? t('app:keep_the_latest') : node?.versionLabel}
|
||||
{!node.isLatestVersion && (
|
||||
<MyTag type="fill" colorSchema={'adora'} fontSize={'mini'} borderRadius={'lg'}>
|
||||
{t('app:not_the_newest')}
|
||||
@@ -672,7 +673,7 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}, [node.isLatestVersion, node?.versionLabel, t]);
|
||||
}, [node.isLatestVersion, node?.version, node?.versionLabel, t]);
|
||||
|
||||
return (
|
||||
<MySelect
|
||||
@@ -685,7 +686,7 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
|
||||
placeholder={node?.versionLabel}
|
||||
variant={'whitePrimaryOutline'}
|
||||
size={'sm'}
|
||||
list={renderList}
|
||||
list={renderVersionList}
|
||||
ScrollData={(props) => (
|
||||
<ScrollData minH={'100px'} maxH={'40vh'}>
|
||||
{props.children}
|
||||
|
@@ -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<any>) {
|
||||
@@ -23,6 +22,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
await rewriteAppWorkflowToDetail({
|
||||
nodes: app.modules,
|
||||
teamId,
|
||||
ownerTmbId: app.tmbId,
|
||||
isRoot
|
||||
});
|
||||
|
||||
@@ -34,12 +34,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...app,
|
||||
modules: await Promise.all(
|
||||
app.modules.map((node) => checkNode({ node, ownerTmbId: app.tmbId }))
|
||||
)
|
||||
};
|
||||
return app;
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
@@ -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<FlowNodeTemplateType> {
|
||||
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 });
|
||||
|
86
projects/app/src/pages/api/core/app/plugin/getVersionList.ts
Normal file
86
projects/app/src/pages/api/core/app/plugin/getVersionList.ts
Normal file
@@ -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<getToolVersionListProps>,
|
||||
_res: NextApiResponse<any>
|
||||
): Promise<getToolVersionResponse> {
|
||||
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);
|
@@ -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)
|
||||
};
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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<StoreNodeItemType> => {
|
||||
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)
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@@ -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<FlowNodeTemplateType>('/core/app/plugin/getPreviewNode', data);
|
||||
|
||||
export const getToolVersionList = (data: getToolVersionListProps) =>
|
||||
POST<getToolVersionResponse>('/core/app/plugin/getVersionList', data);
|
||||
|
||||
/* ============ mcp tools ============== */
|
||||
export const postCreateMCPTools = (data: createMCPToolsBody) =>
|
||||
POST('/core/app/mcpTools/create', data);
|
||||
|
@@ -51,7 +51,7 @@ const ChatRecordContextProvider = ({
|
||||
params
|
||||
}: {
|
||||
children: ReactNode;
|
||||
params: Record<string, any>;
|
||||
params: Omit<getPaginationRecordsBody, 'offset' | 'pageSize'>;
|
||||
}) => {
|
||||
const ChatBoxRef = useContextSelector(ChatItemContext, (v) => v.ChatBoxRef);
|
||||
const [isChatRecordsLoaded, setIsChatRecordsLoaded] = useState(false);
|
||||
|
Reference in New Issue
Block a user